diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b49a2be..33a6464 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -30,7 +30,11 @@ "Bash(ssh:*)", "Bash(cat:*)", "Bash(dig:*)", - "Bash(git commit:*)" + "Bash(git commit:*)", + "Bash(netstat:*)", + "Bash(ss:*)", + "Bash(lsof:*)", + "Bash(dotnet run:*)" ] }, "enableAllProjectMcpServers": false diff --git a/src/BCards.Web/Views/Admin/ManagePage.cshtml b/src/BCards.Web/Views/Admin/ManagePage.cshtml index bf0d22f..1d47fec 100644 --- a/src/BCards.Web/Views/Admin/ManagePage.cshtml +++ b/src/BCards.Web/Views/Admin/ManagePage.cshtml @@ -535,7 +535,7 @@ 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/","") : ""; - var tiktokUrl = tiktok !=null ? tiktok.Url.Replace("https://tiktok.com/@@","").Replace("https://www.tiktok.com/@@","").Replace("https://vm.tiktok.com/","") : ""; + var tiktokUrl = tiktok !=null ? tiktok.Url.Replace("https://tiktok.com/@","").Replace("https://www.tiktok.com/@","").Replace("https://vm.tiktok.com/","") : ""; var pinterestUrl = pinterest !=null ? pinterest.Url.Replace("https://pinterest.com/","").Replace("https://www.pinterest.com/","").Replace("https://pin.it/","") : ""; var discordUrl = discord !=null ? discord.Url.Replace("https://discord.gg/","").Replace("https://discord.com/invite/","") : ""; var kawaiUrl = kawai !=null ? kawai.Url.Replace("https://kawai.com/","").Replace("https://www.kawai.com/","") : ""; @@ -573,9 +573,12 @@ https://wa.me/ - + - Exemplo: 5511999999999 (código do país + DDD + número) + + Brasil: Digite apenas DDD + número (ex: 11987654321). O código 55 será adicionado automaticamente.
+ Outros países: Digite o código do país + número completo. +
@@ -1204,14 +1207,22 @@ checkServerImageErrors(); // Garantir que campos não marcados sejam string vazia ao submeter - $('form').on('submit', function() { + $('form').on('submit', function(e) { ensureUncheckedFieldsAreEmpty(); - + + // Validar URLs de redes sociais antes de submeter + const validationResult = validateSocialMediaUrls(); + if (!validationResult.valid) { + e.preventDefault(); + alert('Erro de validação:\n\n' + validationResult.errors.join('\n')); + return false; + } + // Debug: Verificar quais campos de links estão sendo enviados console.log('=== DEBUG FORM SUBMISSION ==='); const formData = new FormData(this); for (let [key, value] of formData.entries()) { - if (key.includes('Links[')) { + if (key.includes('Links[') || key.includes('Url') || key.includes('Number')) { console.log(`${key}: ${value}`); } } @@ -2014,7 +2025,7 @@ { checkbox: '#enableDiscord', hidden: 'input[name="DiscordUrl"]' }, { checkbox: '#enableKawai', hidden: 'input[name="KawaiUrl"]' } ]; - + socialFields.forEach(field => { if (!$(field.checkbox).is(':checked')) { $(field.hidden).val(' '); // Forçar espaço para campos não marcados @@ -2022,6 +2033,82 @@ }); } + // Validar URLs de redes sociais antes de submeter o formulário + function validateSocialMediaUrls() { + const errors = []; + const userLang = navigator.language || navigator.userLanguage; + const isBrazil = userLang.startsWith('pt-BR') || userLang.startsWith('pt'); + + // Validar WhatsApp + if ($('#enableWhatsApp').is(':checked')) { + const whatsappValue = $('input[name="WhatsAppNumber"]').val().trim(); + if (whatsappValue && whatsappValue !== ' ') { + const cleanNumber = whatsappValue.replace('https://wa.me/', '').replace(/\D/g, ''); + + if (isBrazil) { + // Brasil: deve ter 13 dígitos (55 + DDD + número) + if (cleanNumber.length !== 13 || !cleanNumber.startsWith('55')) { + errors.push('⚠️ WhatsApp: Número brasileiro deve ter 13 dígitos (55 + DDD + número).\nExemplo: 5511987654321'); + } + } else { + // Outros países: validar entre 10 e 15 dígitos + if (cleanNumber.length < 10 || cleanNumber.length > 15) { + errors.push('⚠️ WhatsApp: Número deve ter entre 10 e 15 dígitos (incluindo código do país).'); + } + } + } + } + + // Validar Facebook + if ($('#enableFacebook').is(':checked')) { + const facebookValue = $('input[name="FacebookUrl"]').val().trim(); + if (facebookValue && facebookValue !== ' ') { + const cleanUrl = facebookValue.replace('https://facebook.com/', '').replace('https://www.facebook.com/', ''); + if (cleanUrl.length < 3) { + errors.push('⚠️ Facebook: Nome de usuário deve ter pelo menos 3 caracteres.'); + } + } + } + + // Validar Instagram + if ($('#enableInstagram').is(':checked')) { + const instagramValue = $('input[name="InstagramUrl"]').val().trim(); + if (instagramValue && instagramValue !== ' ') { + const cleanUrl = instagramValue.replace('https://instagram.com/', '').replace('https://www.instagram.com/', ''); + if (cleanUrl.length < 3) { + errors.push('⚠️ Instagram: Nome de usuário deve ter pelo menos 3 caracteres.'); + } + } + } + + // Validar Twitter + if ($('#enableTwitter').is(':checked')) { + const twitterValue = $('input[name="TwitterUrl"]').val().trim(); + if (twitterValue && twitterValue !== ' ') { + const cleanUrl = twitterValue.replace('https://x.com/', '').replace('https://twitter.com/', ''); + if (cleanUrl.length < 3) { + errors.push('⚠️ Twitter/X: Nome de usuário deve ter pelo menos 3 caracteres.'); + } + } + } + + // Validar TikTok + if ($('#enableTiktok').is(':checked')) { + const tiktokValue = $('input[name="TiktokUrl"]').val().trim(); + if (tiktokValue && tiktokValue !== ' ') { + const cleanUrl = tiktokValue.replace('https://tiktok.com/@@', '').replace('https://www.tiktok.com/@@', ''); + if (cleanUrl.length < 3) { + errors.push('⚠️ TikTok: Nome de usuário deve ter pelo menos 3 caracteres.'); + } + } + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + // Social Media Functions function cleanSocialPrefix(value, socialType) { const prefixes = { @@ -2039,10 +2126,16 @@ for (let prefix of typePrefixes) { if (value.startsWith(prefix)) { - return value.replace(prefix, ''); + value = value.replace(prefix, ''); + break; } } + // TikTok: remover @@ do início (o prefixo já tem @@) + if (socialType === 'Tiktok' && value.startsWith('@@')) { + value = value.substring(1); + } + return value; } @@ -2124,9 +2217,19 @@ if (isWhatsApp) { // WhatsApp: apenas números value = value.replace(/\D/g, ''); + + // Auto-adicionar código +55 para números brasileiros (11 dígitos sem código país) + // Detecta cultura pt-BR para aplicar validação BR + const userLang = navigator.language || navigator.userLanguage; + const isBrazil = userLang.startsWith('pt-BR') || userLang.startsWith('pt'); + + if (isBrazil && value.length === 11 && !value.startsWith('55')) { + value = '55' + value; + } + $(this).val(value); } - + // Atualizar campo hidden - SEMPRE string, nunca null if (value) { hiddenField.val(prefix + value); @@ -2142,15 +2245,29 @@ function updateSocialFieldFeedback(input, value, isWhatsApp) { // Remover classes anteriores input.removeClass('is-valid is-invalid'); - + if (!value) return; - + if (isWhatsApp) { - // Validar WhatsApp (mínimo 10 dígitos) - if (value.length >= 10) { - input.addClass('is-valid'); + // Validar WhatsApp com detecção de idioma + const userLang = navigator.language || navigator.userLanguage; + const isBrazil = userLang.startsWith('pt-BR') || userLang.startsWith('pt'); + + if (isBrazil) { + // Brasil: validar 13 dígitos (55 + DDD + número) + if (value.length === 13 && value.startsWith('55')) { + input.addClass('is-valid'); + } else if (value.length >= 10) { + // Ainda está digitando ou formato incorreto + input.addClass('is-invalid'); + } } else { - input.addClass('is-invalid'); + // Outros países: validar mínimo 10 dígitos (genérico) + if (value.length >= 10 && value.length <= 15) { + input.addClass('is-valid'); + } else if (value.length > 0) { + input.addClass('is-invalid'); + } } } else { // Validar username (mínimo 3 caracteres, sem espaços) diff --git a/src/BCards.Web/Views/UserPage/Display.cshtml b/src/BCards.Web/Views/UserPage/Display.cshtml index 0991be7..0501289 100644 --- a/src/BCards.Web/Views/UserPage/Display.cshtml +++ b/src/BCards.Web/Views/UserPage/Display.cshtml @@ -10,6 +10,84 @@ Layout = isPreview ? "_PreviewLayout" : "_UserPageLayout"; } +@functions { + /// + /// Normaliza URLs de redes sociais para garantir que sempre tenham protocolo HTTPS + /// Corrige URLs que foram salvas sem prefixo HTTP(S) + /// + string NormalizeSocialUrl(string url, string icon) + { + if (string.IsNullOrEmpty(url)) return "#"; + + // Se já tem protocolo, retorna direto + if (url.StartsWith("http://") || url.StartsWith("https://")) + return url; + + // WhatsApp - garantir prefixo wa.me + if (!string.IsNullOrEmpty(icon) && icon.Contains("whatsapp")) + { + // Remove qualquer prefixo parcial que possa existir + var cleanUrl = url.Replace("wa.me/", "").Replace("whatsapp://", ""); + return $"https://wa.me/{cleanUrl}"; + } + + // Facebook + if (!string.IsNullOrEmpty(icon) && icon.Contains("facebook")) + { + var cleanUrl = url.Replace("facebook.com/", "").Replace("fb.com/", ""); + return $"https://facebook.com/{cleanUrl}"; + } + + // Instagram + if (!string.IsNullOrEmpty(icon) && icon.Contains("instagram")) + { + var cleanUrl = url.Replace("instagram.com/", "").Replace("instagr.am/", ""); + return $"https://instagram.com/{cleanUrl}"; + } + + // Twitter/X + if (!string.IsNullOrEmpty(icon) && (icon.Contains("twitter") || icon.Contains("x-twitter"))) + { + var cleanUrl = url.Replace("x.com/", "").Replace("twitter.com/", ""); + return $"https://x.com/{cleanUrl}"; + } + + // TikTok + if (!string.IsNullOrEmpty(icon) && icon.Contains("tiktok")) + { + var cleanUrl = url.Replace("tiktok.com/", "").Replace("tiktok.com/@", ""); + // Se não tem @, adiciona + if (!cleanUrl.StartsWith("@")) + cleanUrl = "@" + cleanUrl; + return $"https://tiktok.com/{cleanUrl}"; + } + + // Pinterest + if (!string.IsNullOrEmpty(icon) && icon.Contains("pinterest")) + { + var cleanUrl = url.Replace("pinterest.com/", "").Replace("pin.it/", ""); + return $"https://pinterest.com/{cleanUrl}"; + } + + // Discord + if (!string.IsNullOrEmpty(icon) && icon.Contains("discord")) + { + var cleanUrl = url.Replace("discord.gg/", "").Replace("discord.com/invite/", ""); + return $"https://discord.gg/{cleanUrl}"; + } + + // Kawai ou qualquer outra rede não identificada + // Se contém domínio, apenas adiciona https:// + if (url.Contains(".") || url.Contains("/")) + { + return $"https://{url.TrimStart('/')}"; + } + + // Fallback: assume que é uma URL completa que está faltando protocolo + return $"https://{url}"; + } +} + @section Head { @if (isLivePage) { @@ -233,7 +311,7 @@ (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription)));