fix: ajustes de links
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m34s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m0s
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 0s
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m34s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m0s
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 0s
This commit is contained in:
parent
2eada5f44c
commit
378bcf54b6
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BCards.Web.Controllers;
|
||||
@ -203,7 +204,6 @@ public class AdminController : Controller
|
||||
string userId = "";
|
||||
try
|
||||
{
|
||||
|
||||
ViewBag.IsHomePage = false;
|
||||
|
||||
var user = await _authService.GetCurrentUserAsync(User);
|
||||
@ -214,13 +214,7 @@ public class AdminController : Controller
|
||||
|
||||
// Limpar campos de redes sociais que são apenas espaços (tratados como vazios)
|
||||
CleanSocialMediaFields(model);
|
||||
|
||||
_logger.LogInformation($"ManagePage POST: IsNewPage={model.IsNewPage}, DisplayName={model.DisplayName}, Category={model.Category}, Links={model.Links?.Count ?? 0}");
|
||||
|
||||
ModelState.Remove<ManagePageViewModel>(x => x.InstagramUrl);
|
||||
ModelState.Remove<ManagePageViewModel>(x => x.FacebookUrl);
|
||||
ModelState.Remove<ManagePageViewModel>(x => x.TwitterUrl);
|
||||
ModelState.Remove<ManagePageViewModel>(x => x.WhatsAppNumber);
|
||||
AdjustModelState(ModelState, model);
|
||||
|
||||
_logger.LogInformation($"ManagePage POST: IsNewPage={model.IsNewPage}, DisplayName={model.DisplayName}, Category={model.Category}, Links={model.Links?.Count ?? 0}");
|
||||
|
||||
@ -276,12 +270,19 @@ public class AdminController : Controller
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
_logger.LogWarning("ModelState is invalid:");
|
||||
var sbError = new StringBuilder();
|
||||
sbError.AppendLine("ModelState is invalid!");
|
||||
foreach (var error in ModelState)
|
||||
{
|
||||
_logger.LogWarning($"Key: {error.Key}, Errors: {string.Join(", ", error.Value.Errors.Select(e => e.ErrorMessage))}");
|
||||
var erroMsg = string.Join(", ", error.Value.Errors.Select(e => e.ErrorMessage));
|
||||
if (!string.IsNullOrEmpty(erroMsg))
|
||||
{
|
||||
sbError.AppendLine($"Key: {error.Key}, Errors: {erroMsg}");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning(sbError.ToString());
|
||||
|
||||
// Repopulate dropdowns
|
||||
var slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName);
|
||||
model.Slug = slug;
|
||||
@ -1143,4 +1144,35 @@ public class AdminController : Controller
|
||||
_logger.LogInformation("KeepAlive endpoint triggered for user {User}", User.Identity?.Name ?? "Anonymous");
|
||||
return Json(new { status = "session_extended" });
|
||||
}
|
||||
|
||||
private void AdjustModelState(ModelStateDictionary modelState, ManagePageViewModel model)
|
||||
{
|
||||
modelState.Remove<ManagePageViewModel>(x => x.InstagramUrl);
|
||||
modelState.Remove<ManagePageViewModel>(x => x.FacebookUrl);
|
||||
modelState.Remove<ManagePageViewModel>(x => x.TwitterUrl);
|
||||
modelState.Remove<ManagePageViewModel>(x => x.WhatsAppNumber);
|
||||
|
||||
// Remover validação de 'Description' para links do tipo 'Normal'
|
||||
if (model.Links != null)
|
||||
{
|
||||
for (int i = 0; i < model.Links.Count; i++)
|
||||
{
|
||||
if (model.Links[i].Type == LinkType.Normal)
|
||||
{
|
||||
string key = $"Links[{i}].Description";
|
||||
if (ModelState.ContainsKey(key))
|
||||
{
|
||||
ModelState.Remove(key);
|
||||
ModelState.MarkFieldValid(key);
|
||||
}
|
||||
key = $"Links[{i}].Url";
|
||||
if (ModelState.ContainsKey(key))
|
||||
{
|
||||
ModelState.Remove(key);
|
||||
ModelState.MarkFieldValid(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -381,10 +381,10 @@
|
||||
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/","") : "";
|
||||
var instagramUrl = instagram !=null ? instagram.Url : "";
|
||||
var facebookUrl = facebook !=null ? facebook.Url.Replace("https://facebook.com/","").Replace("https://www.facebook.com/","").Replace("https://fb.com/","") : "";
|
||||
var twitterUrl = twitter !=null ? twitter.Url.Replace("https://x.com/","").Replace("https://twitter.com/","").Replace("https://www.twitter.com/","") : "";
|
||||
var whatsappUrl = whatsapp !=null ? whatsapp.Url.Replace("https://wa.me/","").Replace("whatsapp://","") : "";
|
||||
var instagramUrl = instagram !=null ? instagram.Url.Replace("https://instagram.com/","").Replace("https://www.instagram.com/","") : "";
|
||||
}
|
||||
<!-- Passo 4: Redes Sociais (Opcional) -->
|
||||
<div class="accordion-item">
|
||||
@ -581,27 +581,11 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="linkUrl" class="form-label">URL <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-primary text-white fw-bold">https://</span>
|
||||
<input type="text" class="form-control" id="linkUrlInput" placeholder="exemplo.com" required>
|
||||
</div>
|
||||
<input type="hidden" id="linkUrl">
|
||||
<div class="form-text">Digite apenas o domínio e caminho (sem https://)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="linkDescription" class="form-label">Descrição (opcional)</label>
|
||||
<input type="text" class="form-control" id="linkDescription" placeholder="Breve descrição do link">
|
||||
<div class="form-text">Texto adicional que aparece abaixo do título</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="linkIcon" class="form-label">Ícone (opcional)</label>
|
||||
<select class="form-select" id="linkIcon">
|
||||
<option value="">Sem ícone</option>
|
||||
<option value="fas fa-globe">🌐 Site</option>
|
||||
<option value="fas fa-shopping-cart">🛒 Loja</option>
|
||||
<label for="linkIcon" class="form-label">Tipo de Link <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="linkIcon" required>
|
||||
<option value="">Selecione o tipo de link</option>
|
||||
<option value="fas fa-globe">🌐 Site Geral</option>
|
||||
<option value="fas fa-shopping-cart">🛒 Loja/E-commerce</option>
|
||||
<option value="fas fa-briefcase">💼 Portfólio</option>
|
||||
<option value="fas fa-envelope">✉️ Email</option>
|
||||
<option value="fas fa-phone">📞 Telefone</option>
|
||||
@ -613,6 +597,23 @@
|
||||
<option value="fas fa-calendar">📅 Agenda</option>
|
||||
<option value="fas fa-heart">❤️ Favorito</option>
|
||||
</select>
|
||||
<div class="form-text">Escolha o tipo para obter instruções específicas</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="linkUrl" class="form-label">URL/Contato <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-primary text-white fw-bold" id="urlPrefix">https://</span>
|
||||
<input type="text" class="form-control" id="linkUrlInput" placeholder="exemplo.com" required>
|
||||
</div>
|
||||
<input type="hidden" id="linkUrl">
|
||||
<div class="form-text" id="urlInstructions">Selecione primeiro o tipo de link acima</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="linkDescription" class="form-label">Descrição (opcional)</label>
|
||||
<input type="text" class="form-control" id="linkDescription" placeholder="Breve descrição do link">
|
||||
<div class="form-text">Texto adicional que aparece abaixo do título</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -995,12 +996,18 @@
|
||||
// Add link functionality via modal
|
||||
$('#addLinkBtn').on('click', function() {
|
||||
const maxlinks = @Model.MaxLinksAllowed;
|
||||
if (linkCount >= maxlinks+4) {
|
||||
alert('Você atingiu o limite de links para seu plano atual.');
|
||||
// Links do modal são limitados pelo MaxLinksAllowed (redes sociais são separadas e opcionais)
|
||||
if (linkCount >= maxlinks) {
|
||||
alert('Você atingiu o limite de ' + maxlinks + ' links para seu plano atual. As redes sociais do Passo 4 não contam neste limite.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic link type functionality
|
||||
$('#linkIcon').on('change', function() {
|
||||
updateLinkUrlField($(this).val());
|
||||
});
|
||||
|
||||
// Toggle between link types
|
||||
$('input[name="linkType"]').on('change', function() {
|
||||
const linkType = $(this).val();
|
||||
@ -1606,6 +1613,25 @@
|
||||
}
|
||||
|
||||
// Social Media Functions
|
||||
function cleanSocialPrefix(value, socialType) {
|
||||
const prefixes = {
|
||||
'Facebook': ['https://facebook.com/', 'https://www.facebook.com/', 'https://fb.com/', 'https://www.fb.com/'],
|
||||
'Instagram': ['https://instagram.com/', 'https://www.instagram.com/', 'https://instagr.am/'],
|
||||
'Twitter': ['https://x.com/', 'https://twitter.com/', 'https://www.twitter.com/', 'https://www.x.com/'],
|
||||
'WhatsApp': ['https://wa.me/', 'whatsapp://', 'https://api.whatsapp.com/', 'wa.me/']
|
||||
};
|
||||
|
||||
const typePrefixes = prefixes[socialType] || [];
|
||||
|
||||
for (let prefix of typePrefixes) {
|
||||
if (value.startsWith(prefix)) {
|
||||
return value.replace(prefix, '');
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function initializeSocialMedia() {
|
||||
// WhatsApp
|
||||
setupSocialField('WhatsApp', 'WhatsAppNumber', 'https://wa.me/', true);
|
||||
@ -1638,13 +1664,9 @@
|
||||
checkbox.prop('checked', true);
|
||||
group.show();
|
||||
|
||||
// Extrair username/número do valor atual
|
||||
if (currentValue.startsWith(prefix)) {
|
||||
const userPart = currentValue.replace(prefix, '');
|
||||
userInput.val(userPart);
|
||||
} else {
|
||||
userInput.val(currentValue);
|
||||
}
|
||||
// Limpar qualquer prefixo conhecido do valor atual
|
||||
const cleanValue = cleanSocialPrefix(currentValue, name);
|
||||
userInput.val(cleanValue);
|
||||
} else {
|
||||
// Se não tem valor, garantir que o hidden field seja espaço
|
||||
hiddenField.val(' ');
|
||||
@ -1666,10 +1688,11 @@
|
||||
userInput.on('input', function() {
|
||||
let value = $(this).val().trim();
|
||||
|
||||
// Se o usuário colou uma URL completa, extrair apenas a parte do usuário
|
||||
if (value.startsWith(prefix)) {
|
||||
value = value.replace(prefix, '');
|
||||
$(this).val(value);
|
||||
// Limpar qualquer prefixo conhecido
|
||||
const cleanValue = cleanSocialPrefix(value, name);
|
||||
if (cleanValue !== value) {
|
||||
$(this).val(cleanValue);
|
||||
value = cleanValue;
|
||||
}
|
||||
|
||||
if (isWhatsApp) {
|
||||
@ -1713,6 +1736,129 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic Link Type Functions
|
||||
function updateLinkUrlField(iconValue) {
|
||||
const $prefix = $('#urlPrefix');
|
||||
const $input = $('#linkUrlInput');
|
||||
const $instructions = $('#urlInstructions');
|
||||
|
||||
// Configurações por tipo de ícone
|
||||
const linkTypes = {
|
||||
'fas fa-envelope': {
|
||||
prefix: 'mailto:',
|
||||
placeholder: 'seuemail@@exemplo.com',
|
||||
instructions: 'Digite apenas o email (sem mailto:)',
|
||||
color: 'bg-success'
|
||||
},
|
||||
'fas fa-phone': {
|
||||
prefix: 'tel:',
|
||||
placeholder: '5511999999999',
|
||||
instructions: 'Digite o telefone com código do país e DDD (apenas números)',
|
||||
color: 'bg-success'
|
||||
},
|
||||
'fab fa-youtube': {
|
||||
prefix: 'https://youtube.com/',
|
||||
placeholder: 'watch?v=VIDEO_ID ou @@usuario ou c/CANAL',
|
||||
instructions: 'Digite o ID do vídeo, @@usuário ou c/canal',
|
||||
color: 'bg-danger'
|
||||
},
|
||||
'fab fa-linkedin': {
|
||||
prefix: 'https://linkedin.com/in/',
|
||||
placeholder: 'seu-perfil-linkedin',
|
||||
instructions: 'Digite apenas seu nome de usuário do LinkedIn',
|
||||
color: 'bg-primary'
|
||||
},
|
||||
'fab fa-github': {
|
||||
prefix: 'https://github.com/',
|
||||
placeholder: 'usuario ou usuario/repositorio',
|
||||
instructions: 'Digite seu usuário ou usuário/repositório',
|
||||
color: 'bg-dark'
|
||||
},
|
||||
'fas fa-map-marker-alt': {
|
||||
prefix: 'https://maps.google.com/?q=',
|
||||
placeholder: 'Rua das Flores, 123 - São Paulo, SP',
|
||||
instructions: 'Digite o endereço completo (acentos e espaços serão codificados automaticamente)',
|
||||
color: 'bg-warning'
|
||||
},
|
||||
'fas fa-globe': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'exemplo.com',
|
||||
instructions: 'Digite apenas o domínio e caminho (sem https://)',
|
||||
color: 'bg-primary'
|
||||
},
|
||||
'fas fa-shopping-cart': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'minhaloja.com/produto',
|
||||
instructions: 'Digite apenas o domínio e caminho da sua loja',
|
||||
color: 'bg-success'
|
||||
},
|
||||
'fas fa-briefcase': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'meuportifolio.com',
|
||||
instructions: 'Digite apenas o domínio do seu portfólio',
|
||||
color: 'bg-info'
|
||||
},
|
||||
'fas fa-download': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'exemplo.com/arquivo.pdf',
|
||||
instructions: 'Digite o link direto para download',
|
||||
color: 'bg-secondary'
|
||||
},
|
||||
'fas fa-calendar': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'calendly.com/seunome',
|
||||
instructions: 'Digite o link do seu calendário (Calendly, etc.)',
|
||||
color: 'bg-info'
|
||||
},
|
||||
'fas fa-heart': {
|
||||
prefix: 'https://',
|
||||
placeholder: 'exemplo.com',
|
||||
instructions: 'Digite qualquer link especial',
|
||||
color: 'bg-danger'
|
||||
}
|
||||
};
|
||||
|
||||
if (iconValue && linkTypes[iconValue]) {
|
||||
const config = linkTypes[iconValue];
|
||||
$prefix.text(config.prefix)
|
||||
.removeClass('bg-primary bg-success bg-danger bg-warning bg-info bg-secondary bg-dark')
|
||||
.addClass(config.color);
|
||||
$input.attr('placeholder', config.placeholder);
|
||||
$instructions.text(config.instructions);
|
||||
} else {
|
||||
// Default
|
||||
$prefix.text('https://')
|
||||
.removeClass('bg-success bg-danger bg-warning bg-info bg-secondary bg-dark')
|
||||
.addClass('bg-primary');
|
||||
$input.attr('placeholder', 'exemplo.com');
|
||||
$instructions.text('Selecione primeiro o tipo de link acima');
|
||||
}
|
||||
|
||||
// Limpar o campo de input quando trocar tipo
|
||||
$input.val('');
|
||||
$('#linkUrl').val('');
|
||||
}
|
||||
|
||||
function cleanAnyUrlPrefix(value) {
|
||||
const prefixes = [
|
||||
'https://', 'http://', 'mailto:', 'tel:', 'whatsapp://',
|
||||
'https://wa.me/', 'https://youtube.com/', 'https://linkedin.com/in/',
|
||||
'https://github.com/', 'https://instagram.com/', 'https://facebook.com/',
|
||||
'https://x.com/', 'https://twitter.com/', 'https://maps.google.com/?q=',
|
||||
'https://www.youtube.com/', 'https://www.linkedin.com/in/',
|
||||
'https://www.github.com/', 'https://www.instagram.com/',
|
||||
'https://www.facebook.com/', 'https://www.twitter.com/'
|
||||
];
|
||||
|
||||
for (let prefix of prefixes) {
|
||||
if (value.startsWith(prefix)) {
|
||||
return value.replace(prefix, '');
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// URL Input Functions
|
||||
function initializeUrlInputs() {
|
||||
// Setup para link normal
|
||||
@ -1730,20 +1876,37 @@
|
||||
|
||||
// Eventos para tratar entrada do usuário
|
||||
$input.on('input paste keyup', function() {
|
||||
let value = $(this).val().trim();
|
||||
let value = $(this).val();
|
||||
const currentPrefix = $('#urlPrefix').text() || 'https://';
|
||||
|
||||
// Remover https:// ou http:// se o usuário digitou
|
||||
if (value.startsWith('https://')) {
|
||||
value = value.substring(8);
|
||||
$(this).val(value);
|
||||
} else if (value.startsWith('http://')) {
|
||||
value = value.substring(7);
|
||||
$(this).val(value);
|
||||
// Apenas processar/limpar a URL para tipos que não sejam de mapa
|
||||
if (currentPrefix !== 'https://maps.google.com/?q=') {
|
||||
let processedValue = value.trim();
|
||||
processedValue = cleanAnyUrlPrefix(processedValue);
|
||||
|
||||
// Apenas atualiza o DOM se o valor foi alterado para não atrapalhar o cursor
|
||||
if (processedValue !== value) {
|
||||
$(this).val(processedValue);
|
||||
}
|
||||
value = processedValue;
|
||||
}
|
||||
|
||||
// Atualizar campo hidden com URL completa
|
||||
if (value) {
|
||||
$hidden.val('https://' + value);
|
||||
// Tratar casos especiais
|
||||
if (currentPrefix === 'https://maps.google.com/?q=') {
|
||||
// Para mapas, o usuário digita normalmente. O encoding é feito apenas na URL final.
|
||||
const encodedValue = encodeURIComponent(value);
|
||||
$hidden.val(currentPrefix + encodedValue);
|
||||
} else if (currentPrefix === 'tel:') {
|
||||
// Para telefone, apenas números
|
||||
const telValue = value.replace(/\D/g, '');
|
||||
$(this).val(telValue);
|
||||
$hidden.val(currentPrefix + telValue);
|
||||
} else {
|
||||
// Para outros tipos, a URL é concatenada diretamente
|
||||
$hidden.val(currentPrefix + value);
|
||||
}
|
||||
} else {
|
||||
$hidden.val('');
|
||||
}
|
||||
@ -1752,15 +1915,8 @@
|
||||
// Para modal de edição - detectar se já tem URL e separar
|
||||
if ($hidden.val()) {
|
||||
const existingUrl = $hidden.val();
|
||||
if (existingUrl.startsWith('https://')) {
|
||||
$input.val(existingUrl.substring(8));
|
||||
} else if (existingUrl.startsWith('http://')) {
|
||||
$input.val(existingUrl.substring(7));
|
||||
$hidden.val('https://' + existingUrl.substring(7)); // Converter para https
|
||||
} else {
|
||||
$input.val(existingUrl);
|
||||
$hidden.val('https://' + existingUrl);
|
||||
}
|
||||
const cleanValue = cleanAnyUrlPrefix(existingUrl);
|
||||
$input.val(cleanValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user