fix: pagina que permite copiar links
All checks were successful
BCards Multi-Tenant Deployment Pipeline / Run Tests (push) Successful in 6s
BCards Multi-Tenant Deployment Pipeline / PR Validation (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Build and Push Image (push) Successful in 8m9s
BCards Multi-Tenant Deployment Pipeline / Deploy to Release Swarm (ARM) (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy bcards.site (push) Successful in 1m3s
BCards Multi-Tenant Deployment Pipeline / Deploy spicylinks.site (push) Successful in 1m3s
BCards Multi-Tenant Deployment Pipeline / Deploy luslinks.site (push) Successful in 1m2s
BCards Multi-Tenant Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deployment Summary (push) Successful in 1s

This commit is contained in:
Ricardo Carneiro 2026-04-27 20:43:01 -03:00
parent 0e7e3d552e
commit 6c2b618b92
6 changed files with 102 additions and 21 deletions

View File

@ -14,7 +14,7 @@ public class CreatePageViewModel
[Required(ErrorMessage = "Tipo de negócio é obrigatório")] [Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual"; public string BusinessType { get; set; } = "individual";
[StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")] [StringLength(3000, ErrorMessage = "Bio deve ter no máximo 3000 caracteres")]
public string Bio { get; set; } = string.Empty; public string Bio { get; set; } = string.Empty;
[Required(ErrorMessage = "Tema é obrigatório")] [Required(ErrorMessage = "Tema é obrigatório")]

View File

@ -18,7 +18,7 @@ public class ManagePageViewModel
[Required(ErrorMessage = "Tipo de negócio é obrigatório")] [Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual"; public string BusinessType { get; set; } = "individual";
[StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")] [StringLength(3000, ErrorMessage = "Bio deve ter no máximo 3000 caracteres")]
public string Bio { get; set; } = string.Empty; public string Bio { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty; public string Slug { get; set; } = string.Empty;

View File

@ -82,8 +82,9 @@
<div class="mb-3"> <div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label> <label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="3" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea> <textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
<span asp-validation-for="Bio" class="text-danger"></span> <span asp-validation-for="Bio" class="text-danger"></span>
<div class="form-text">Máximo 3000 caracteres</div>
</div> </div>
</div> </div>

View File

@ -115,9 +115,9 @@
<div class="mb-3"> <div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label> <label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="3" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea> <textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
<span asp-validation-for="Bio" class="text-danger"></span> <span asp-validation-for="Bio" class="text-danger"></span>
<div class="form-text">Máximo 200 caracteres</div> <div class="form-text">Máximo 3000 caracteres</div>
</div> </div>
<!-- Profile Image Upload --> <!-- Profile Image Upload -->

View File

@ -176,7 +176,16 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Seta de expansão */ /* Seta de expansão e Botão de copiar */
.link-actions-container {
display: flex;
align-items: center;
gap: 0.5rem;
margin-left: 0.5rem;
flex-shrink: 0;
}
.copy-link-btn,
.expand-arrow { .expand-arrow {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border: none; border: none;
@ -189,15 +198,15 @@
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
flex-shrink: 0;
margin-left: 0.5rem;
} }
.copy-link-btn:hover,
.expand-arrow:hover { .expand-arrow:hover {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
transform: scale(1.05); transform: scale(1.05);
} }
.copy-link-btn i,
.expand-arrow i { .expand-arrow i {
font-size: 0.9rem; font-size: 0.9rem;
transition: transform 0.3s ease; transition: transform 0.3s ease;
@ -282,6 +291,20 @@
gap: 0.25rem; gap: 0.25rem;
} }
.expanded-link {
color: var(--primary-color) !important;
text-decoration: underline !important;
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.2s ease;
}
.expanded-link:hover {
opacity: 0.8;
text-decoration: none !important;
}
/* ========== FOOTER ========== */ /* ========== FOOTER ========== */
.profile-footer { .profile-footer {
margin-top: 2rem; margin-top: 2rem;

View File

@ -376,7 +376,7 @@
var hasExpandableContent = (!string.IsNullOrEmpty(link.Description) || var hasExpandableContent = (!string.IsNullOrEmpty(link.Description) ||
(link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription))); (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription)));
<div class="universal-link" data-link-id="@i"> <div class="universal-link" id="link-@i" data-link-id="@i">
<a href="@NormalizeSocialUrl(link.Url, link.Icon)" <a href="@NormalizeSocialUrl(link.Url, link.Icon)"
class="universal-link-header" class="universal-link-header"
onclick="recordClick('@Model.Id', @i)" onclick="recordClick('@Model.Id', @i)"
@ -423,15 +423,23 @@
} }
</div> </div>
</div> </div>
@if (hasExpandableContent) <div class="link-actions-container">
{ <button class="copy-link-btn"
<button class="expand-arrow" type="button"
type="button" title="Copiar link"
onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails(@i)"> onclick="event.preventDefault(); event.stopPropagation(); copyAnchorLink('link-@i', this)">
<i class="fas fa-chevron-down"></i> <i class="fas fa-copy"></i>
</button> </button>
} @if (hasExpandableContent)
{
<button class="expand-arrow"
type="button"
onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails(@i)">
<i class="fas fa-chevron-down"></i>
</button>
}
</div>
</a> </a>
@if (hasExpandableContent) @if (hasExpandableContent)
@ -463,8 +471,10 @@
} }
} }
<div class="expanded-action"> <div class="expanded-action">
<i class="fas fa-external-link-alt"></i> <a href="@NormalizeSocialUrl(link.Url, link.Icon)" target="_blank" rel="noopener noreferrer" class="expanded-link">
Clique no título acima para abrir <i class="fas fa-external-link-alt"></i>
Clique aqui para abrir o link
</a>
</div> </div>
</div> </div>
} }
@ -517,8 +527,10 @@
<div class="universal-link-details" id="details-@uniqueId"> <div class="universal-link-details" id="details-@uniqueId">
<div class="expanded-description">@document.Description</div> <div class="expanded-description">@document.Description</div>
<div class="expanded-action"> <div class="expanded-action">
<i class="fas fa-external-link-alt"></i> <a href="/api/document/@document.FileId" target="_blank" rel="noopener noreferrer" class="expanded-link">
Clique no título acima para abrir o PDF <i class="fas fa-external-link-alt"></i>
Clique aqui para abrir o PDF
</a>
</div> </div>
</div> </div>
} }
@ -653,8 +665,53 @@
// Generate QR Code on page load // Generate QR Code on page load
generateQRCode(); generateQRCode();
// Auto-open link from hash
if (window.location.hash) {
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Small delay to ensure everything is rendered
setTimeout(() => {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// If it has a link-id, try to open the details
const linkId = targetElement.getAttribute('data-link-id');
const docId = targetElement.getAttribute('data-document-id');
if (linkId !== null) {
toggleLinkDetails(linkId);
} else if (docId !== null) {
toggleLinkDetails(docId);
}
}, 500);
}
}
}); });
function copyAnchorLink(id, btn) {
// Preserva a URL completa incluindo query strings (importante para o preview token)
// e apenas substitui/adiciona o hash
const url = new URL(window.location.href);
url.hash = id;
const fullUrl = url.toString();
navigator.clipboard.writeText(fullUrl).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'fas fa-check text-success';
btn.classList.add('copied');
setTimeout(() => {
icon.className = originalClass;
btn.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Erro ao copiar link:', err);
});
}
// QR Code Functions // QR Code Functions
let qrCodeGenerated = false; let qrCodeGenerated = false;