743 lines
32 KiB
Plaintext
743 lines
32 KiB
Plaintext
@model BCards.Web.Models.IPageDisplay
|
|
@{
|
|
var seo = ViewBag.SeoSettings as BCards.Web.Models.SeoSettings;
|
|
var category = ViewBag.Category as BCards.Web.Models.Category;
|
|
var isPreview = ViewBag.IsPreview as bool? ?? false;
|
|
var isLivePage = ViewBag.IsLivePage as bool? ?? false;
|
|
|
|
ViewData["Title"] = seo?.Title ?? $"{Model.DisplayName} - {category?.Name}";
|
|
// Lógica de layout corrigida para usar o _PreviewLayout
|
|
Layout = isPreview ? "_PreviewLayout" : "_UserPageLayout";
|
|
}
|
|
|
|
@functions {
|
|
/// <summary>
|
|
/// Normaliza URLs de redes sociais para garantir que sempre tenham protocolo HTTPS
|
|
/// Corrige URLs que foram salvas sem prefixo HTTP(S)
|
|
/// </summary>
|
|
string NormalizeSocialUrl(string url, string icon)
|
|
{
|
|
if (string.IsNullOrEmpty(url)) return "#";
|
|
|
|
// WhatsApp - sempre normalizar para evitar URLs malformadas
|
|
if (!string.IsNullOrEmpty(icon) && icon.Contains("whatsapp"))
|
|
{
|
|
// Remove qualquer prefixo conhecido (incluindo https://)
|
|
var cleanUrl = url
|
|
.Replace("https://wa.me/", "")
|
|
.Replace("http://wa.me/", "")
|
|
.Replace("https://api.whatsapp.com/send?phone=", "")
|
|
.Replace("https://api.whatsapp.com/", "")
|
|
.Replace("wa.me/", "")
|
|
.Replace("whatsapp://send?phone=", "")
|
|
.Replace("whatsapp://", "");
|
|
|
|
// Remove barras extras
|
|
cleanUrl = cleanUrl.TrimStart('/').Trim();
|
|
|
|
// Apenas números devem sobrar
|
|
return $"https://wa.me/{cleanUrl}";
|
|
}
|
|
|
|
// Se já tem protocolo correto para outras redes, retorna direto
|
|
if (url.StartsWith("http://") || url.StartsWith("https://"))
|
|
return url;
|
|
|
|
// Facebook
|
|
if (!string.IsNullOrEmpty(icon) && icon.Contains("facebook"))
|
|
{
|
|
var cleanUrl = url
|
|
.Replace("https://facebook.com/", "")
|
|
.Replace("https://www.facebook.com/", "")
|
|
.Replace("https://fb.com/", "")
|
|
.Replace("https://www.fb.com/", "")
|
|
.Replace("http://facebook.com/", "")
|
|
.Replace("http://www.facebook.com/", "")
|
|
.Replace("facebook.com/", "")
|
|
.Replace("fb.com/", "")
|
|
.TrimStart('/').Trim();
|
|
return $"https://facebook.com/{cleanUrl}";
|
|
}
|
|
|
|
// Instagram
|
|
if (!string.IsNullOrEmpty(icon) && icon.Contains("instagram"))
|
|
{
|
|
var cleanUrl = url
|
|
.Replace("https://instagram.com/", "")
|
|
.Replace("https://www.instagram.com/", "")
|
|
.Replace("https://instagr.am/", "")
|
|
.Replace("http://instagram.com/", "")
|
|
.Replace("http://www.instagram.com/", "")
|
|
.Replace("instagram.com/", "")
|
|
.Replace("instagr.am/", "")
|
|
.TrimStart('/').Trim();
|
|
return $"https://instagram.com/{cleanUrl}";
|
|
}
|
|
|
|
// Twitter/X
|
|
if (!string.IsNullOrEmpty(icon) && (icon.Contains("twitter") || icon.Contains("x-twitter")))
|
|
{
|
|
var cleanUrl = url
|
|
.Replace("https://x.com/", "")
|
|
.Replace("https://twitter.com/", "")
|
|
.Replace("https://www.twitter.com/", "")
|
|
.Replace("https://www.x.com/", "")
|
|
.Replace("http://x.com/", "")
|
|
.Replace("http://twitter.com/", "")
|
|
.Replace("x.com/", "")
|
|
.Replace("twitter.com/", "")
|
|
.TrimStart('/').Trim();
|
|
return $"https://x.com/{cleanUrl}";
|
|
}
|
|
|
|
// TikTok
|
|
if (!string.IsNullOrEmpty(icon) && icon.Contains("tiktok"))
|
|
{
|
|
var cleanUrl = url
|
|
.Replace("https://tiktok.com/@", "")
|
|
.Replace("https://www.tiktok.com/@", "")
|
|
.Replace("https://tiktok.com/", "")
|
|
.Replace("https://www.tiktok.com/", "")
|
|
.Replace("https://vm.tiktok.com/", "")
|
|
.Replace("http://tiktok.com/@", "")
|
|
.Replace("http://tiktok.com/", "")
|
|
.Replace("tiktok.com/@", "")
|
|
.Replace("tiktok.com/", "")
|
|
.TrimStart('/').Trim();
|
|
|
|
// 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("https://pinterest.com/", "")
|
|
.Replace("https://www.pinterest.com/", "")
|
|
.Replace("https://pin.it/", "")
|
|
.Replace("http://pinterest.com/", "")
|
|
.Replace("http://www.pinterest.com/", "")
|
|
.Replace("pinterest.com/", "")
|
|
.Replace("pin.it/", "")
|
|
.TrimStart('/').Trim();
|
|
return $"https://pinterest.com/{cleanUrl}";
|
|
}
|
|
|
|
// Discord
|
|
if (!string.IsNullOrEmpty(icon) && icon.Contains("discord"))
|
|
{
|
|
var cleanUrl = url
|
|
.Replace("https://discord.gg/", "")
|
|
.Replace("https://discord.com/invite/", "")
|
|
.Replace("http://discord.gg/", "")
|
|
.Replace("http://discord.com/invite/", "")
|
|
.Replace("discord.gg/", "")
|
|
.Replace("discord.com/invite/", "")
|
|
.TrimStart('/').Trim();
|
|
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)
|
|
{
|
|
<meta name="robots" content="index, follow">
|
|
@if (!string.IsNullOrEmpty(ViewBag.PageUrl as string))
|
|
{
|
|
<link rel="canonical" href="@ViewBag.PageUrl">
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A meta tag de noindex/nofollow para previews já está no _PreviewLayout
|
|
<meta name="robots" content="noindex, nofollow">
|
|
}
|
|
}
|
|
|
|
@section Styles {
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<style>
|
|
@{
|
|
// Renderiza os estilos do tema dinâmico
|
|
var partialOutput = await Html.PartialAsync("_ThemeStyles", Model.Theme);
|
|
using (var writer = new System.IO.StringWriter())
|
|
{
|
|
partialOutput.WriteTo(writer, HtmlEncoder);
|
|
@Html.Raw(writer.ToString())
|
|
}
|
|
}
|
|
|
|
/* QR Code Section Styles */
|
|
.qrcode-section {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.documents-section .list-group-item {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: inherit;
|
|
transition: all 0.2s ease;
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.documents-section .list-group-item:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.documents-section .list-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.qrcode-toggle {
|
|
width: 100%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
color: inherit;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.qrcode-toggle:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.qrcode-toggle i {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.qrcode-container {
|
|
margin-top: 1rem;
|
|
padding: 1.5rem;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
animation: slideDown 0.3s ease;
|
|
}
|
|
|
|
@@keyframes slideDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.qrcode-canvas {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin: 0 auto 1rem;
|
|
padding: 1rem;
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
max-width: 250px;
|
|
}
|
|
|
|
.qrcode-canvas > div {
|
|
display: flex !important;
|
|
justify-content: center !important;
|
|
align-items: center !important;
|
|
}
|
|
|
|
.qrcode-canvas img,
|
|
.qrcode-canvas canvas {
|
|
display: block !important;
|
|
margin: 0 auto !important;
|
|
max-width: 200px !important;
|
|
max-height: 200px !important;
|
|
}
|
|
|
|
/* Hide duplicate canvas/img if library generates both */
|
|
.qrcode-canvas img + canvas,
|
|
.qrcode-canvas canvas + img {
|
|
display: none !important;
|
|
}
|
|
|
|
.qrcode-hint {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.btn-download-qr {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 0.75rem 1.5rem;
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
}
|
|
|
|
.btn-download-qr:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.btn-download-qr:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Mobile Responsive */
|
|
@@media (max-width: 576px) {
|
|
.qrcode-toggle {
|
|
font-size: 0.9rem;
|
|
padding: 0.875rem;
|
|
}
|
|
|
|
.qrcode-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.qrcode-canvas {
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
.btn-download-qr {
|
|
padding: 0.625rem 1.25rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
}
|
|
</style>
|
|
}
|
|
|
|
<div class="user-page min-vh-100 d-flex align-items-center py-4">
|
|
<div class="container">
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-6 col-md-8">
|
|
<div class="profile-card mx-auto">
|
|
<!-- Profile Image & Info -->
|
|
<div class="profile-header text-center mb-4">
|
|
@if (!string.IsNullOrEmpty(Model.ProfileImageId))
|
|
{
|
|
<img src="@Model.ProfileImageUrl" alt="@Model.DisplayName" class="profile-image-large rounded-circle mb-3">
|
|
}
|
|
else
|
|
{
|
|
<div class="profile-icon-placeholder mb-3">
|
|
<i class="fas fa-id-card"></i>
|
|
</div>
|
|
}
|
|
<h1 class="profile-name mb-0">@Model.DisplayName</h1>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
|
|
@if (!string.IsNullOrEmpty(Model.Bio))
|
|
{
|
|
<p class="profile-bio">@Model.Bio</p>
|
|
}
|
|
|
|
<!-- Links Container -->
|
|
<div class="links-container">
|
|
@if (Model.Links?.Any(l => l.IsActive) == true)
|
|
{
|
|
@for (int i = 0; i < Model.Links.Count; i++)
|
|
{
|
|
var link = Model.Links[i];
|
|
if (link.IsActive)
|
|
{
|
|
var hasExpandableContent = (!string.IsNullOrEmpty(link.Description) ||
|
|
(link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription)));
|
|
|
|
<div class="universal-link" data-link-id="@i">
|
|
<a href="@NormalizeSocialUrl(link.Url, link.Icon)"
|
|
class="universal-link-header"
|
|
onclick="recordClick('@Model.Id', @i)"
|
|
target="_blank"
|
|
rel="noopener noreferrer">
|
|
|
|
<div class="universal-link-content">
|
|
@if (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductImage))
|
|
{
|
|
<img src="@link.ProductImage"
|
|
alt="@(link.ProductTitle ?? link.Title)"
|
|
class="link-thumbnail"
|
|
loading="lazy"
|
|
onerror="this.style.display='none'">
|
|
}
|
|
else if (!string.IsNullOrEmpty(link.Icon))
|
|
{
|
|
<div class="link-icon"><i class="@link.Icon"></i></div>
|
|
}
|
|
else
|
|
{
|
|
<div class="link-icon"><i class="fas fa-link"></i></div>
|
|
}
|
|
|
|
<div class="link-text-container">
|
|
<div class="link-title">
|
|
@if (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductTitle))
|
|
{
|
|
@link.ProductTitle
|
|
}
|
|
else
|
|
{
|
|
@link.Title
|
|
}
|
|
</div>
|
|
|
|
@if (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductPrice))
|
|
{
|
|
<div class="link-subtitle">@link.ProductPrice</div>
|
|
}
|
|
else if (!string.IsNullOrEmpty(link.Description) && link.Description.Length > 50)
|
|
{
|
|
<div class="link-subtitle">@(link.Description.Substring(0, 50))...</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (hasExpandableContent)
|
|
{
|
|
<button class="expand-arrow"
|
|
type="button"
|
|
onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails(@i)">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
}
|
|
</a>
|
|
|
|
@if (hasExpandableContent)
|
|
{
|
|
<div class="universal-link-details" id="details-@i">
|
|
@if (link.Type == BCards.Web.Models.LinkType.Product)
|
|
{
|
|
@if (!string.IsNullOrEmpty(link.ProductImage))
|
|
{
|
|
<img src="@link.ProductImage"
|
|
alt="@(link.ProductTitle ?? link.Title)"
|
|
class="expanded-image"
|
|
loading="lazy">
|
|
}
|
|
@if (!string.IsNullOrEmpty(link.ProductPrice))
|
|
{
|
|
<div class="expanded-price">@link.ProductPrice</div>
|
|
}
|
|
@if (!string.IsNullOrEmpty(link.ProductDescription))
|
|
{
|
|
<div class="expanded-description">@link.ProductDescription</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@if (!string.IsNullOrEmpty(link.Description))
|
|
{
|
|
<div class="expanded-description">@link.Description</div>
|
|
}
|
|
}
|
|
<div class="expanded-action">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
Clique no título acima para abrir
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
|
|
@* Documentos integrados no mesmo container de links *@
|
|
@if (Model.Documents?.Any() == true)
|
|
{
|
|
@for (int docIndex = 0; docIndex < Model.Documents.Count; docIndex++)
|
|
{
|
|
var document = Model.Documents[docIndex];
|
|
var hasDescription = !string.IsNullOrEmpty(document.Description);
|
|
var uniqueId = $"doc-{docIndex}";
|
|
|
|
<div class="universal-link" data-document-id="@uniqueId">
|
|
<a href="/api/document/@document.FileId"
|
|
class="universal-link-header"
|
|
target="_blank"
|
|
rel="noopener noreferrer">
|
|
|
|
<div class="universal-link-content">
|
|
<div class="link-icon">
|
|
<i class="fas fa-file-pdf"></i>
|
|
</div>
|
|
|
|
<div class="link-text-container">
|
|
<div class="link-title">@document.Title</div>
|
|
@if (hasDescription && document.Description.Length > 50)
|
|
{
|
|
<div class="link-subtitle">@(document.Description.Substring(0, 50))...</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (hasDescription)
|
|
{
|
|
<button class="expand-arrow"
|
|
type="button"
|
|
onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails('@uniqueId')">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
}
|
|
</a>
|
|
|
|
@if (hasDescription)
|
|
{
|
|
<div class="universal-link-details" id="details-@uniqueId">
|
|
<div class="expanded-description">@document.Description</div>
|
|
<div class="expanded-action">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
Clique no título acima para abrir o PDF
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@if ((Model.Links?.Any(l => l.IsActive) != true) && (Model.Documents?.Any() != true))
|
|
{
|
|
<div class="text-muted">
|
|
<p>Nenhum link disponível no momento.</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- QR Code Section -->
|
|
<div class="qrcode-section mt-4">
|
|
<button class="qrcode-toggle" onclick="toggleQRCode()" type="button">
|
|
<i class="fas fa-qrcode me-2"></i>
|
|
<span id="qrToggleText">Ocultar QR Code</span>
|
|
<i class="fas fa-chevron-up ms-auto" id="qrToggleIcon"></i>
|
|
</button>
|
|
|
|
<div class="qrcode-container" id="qrcodeContainer" style="display: block;">
|
|
<div class="qrcode-canvas" id="qrcode"></div>
|
|
<p class="qrcode-hint">Escaneie para compartilhar esta página</p>
|
|
<button class="btn-download-qr" onclick="downloadQR()" type="button">
|
|
<i class="fas fa-download me-1"></i> Baixar QR Code
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="profile-footer">
|
|
<div class="footer-promo" onclick="togglePromo(this)">
|
|
<div class="footer-promo-header">
|
|
<span>💡 Gostou desta página?</span>
|
|
<i class="fas fa-chevron-down"></i>
|
|
</div>
|
|
<div class="footer-promo-content">
|
|
Crie a sua própria página personalizada com <strong>BCards</strong>!
|
|
É rápido, fácil e profissional. Compartilhe todos os seus links em um só lugar.
|
|
<div class="mt-2">
|
|
<a href="@Url.Action("Index", "Home")" class="footer-promo-button">
|
|
<i class="fas fa-rocket"></i>
|
|
Criar Minha Página
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="footer-credits">
|
|
Criado com <a href="@Url.Action("Index", "Home")">BCards</a>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<!-- QRCode.js Library - Load FIRST -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
|
|
|
|
<script>
|
|
function recordClick(pageId, linkIndex) {
|
|
// Detectar se é LivePage ou UserPage
|
|
var isLivePage = @Json.Serialize(isLivePage);
|
|
|
|
if (isLivePage) {
|
|
// LivePages não registram cliques via fetch - usam redirecionamento direto
|
|
return;
|
|
} else {
|
|
// UserPages registram cliques via API
|
|
fetch('/page/click/' + pageId, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ linkIndex: linkIndex })
|
|
}).catch(function(error) {
|
|
console.log('Error recording click:', error);
|
|
});
|
|
}
|
|
}
|
|
|
|
function toggleLinkDetails(linkIndex) {
|
|
const currentDetails = document.getElementById('details-' + linkIndex);
|
|
// Suporta tanto data-link-id (links) quanto data-document-id (documentos)
|
|
let currentArrow = document.querySelector(`[data-link-id="${linkIndex}"] .expand-arrow`);
|
|
if (!currentArrow) {
|
|
currentArrow = document.querySelector(`[data-document-id="${linkIndex}"] .expand-arrow`);
|
|
}
|
|
if (!currentDetails || !currentArrow) return;
|
|
|
|
const isCurrentlyExpanded = currentDetails.classList.contains('show');
|
|
|
|
document.querySelectorAll('.universal-link-details').forEach(details => details.classList.remove('show'));
|
|
document.querySelectorAll('.expand-arrow').forEach(arrow => {
|
|
arrow.classList.remove('expanded');
|
|
arrow.querySelector('i').style.transform = 'rotate(0deg)';
|
|
});
|
|
|
|
if (!isCurrentlyExpanded) {
|
|
currentDetails.classList.add('show');
|
|
currentArrow.classList.add('expanded');
|
|
currentArrow.querySelector('i').style.transform = 'rotate(180deg)';
|
|
}
|
|
}
|
|
|
|
function togglePromo(element) {
|
|
const content = element.querySelector('.footer-promo-content');
|
|
const arrow = element.querySelector('.footer-promo-header i');
|
|
const isExpanded = content.classList.toggle('show');
|
|
arrow.style.transform = isExpanded ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
element.querySelector('.footer-promo-header').classList.toggle('expanded', isExpanded);
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('.universal-link-details, .footer-promo-content').forEach(d => d.classList.remove('show'));
|
|
document.querySelectorAll('.expand-arrow i, .footer-promo-header i').forEach(arrow => arrow.style.transform = 'rotate(0deg)');
|
|
document.querySelectorAll('.expand-arrow').forEach(button => {
|
|
button.addEventListener('keydown', e => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
button.click();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Generate QR Code on page load
|
|
generateQRCode();
|
|
});
|
|
|
|
// QR Code Functions
|
|
let qrCodeGenerated = false;
|
|
|
|
function toggleQRCode() {
|
|
const container = document.getElementById('qrcodeContainer');
|
|
const icon = document.getElementById('qrToggleIcon');
|
|
const text = document.getElementById('qrToggleText');
|
|
|
|
if (container.style.display === 'block') {
|
|
// Close
|
|
container.style.display = 'none';
|
|
icon.classList.remove('fa-chevron-up');
|
|
icon.classList.add('fa-chevron-down');
|
|
text.textContent = 'Mostrar QR Code';
|
|
} else {
|
|
// Open
|
|
container.style.display = 'block';
|
|
icon.classList.remove('fa-chevron-down');
|
|
icon.classList.add('fa-chevron-up');
|
|
text.textContent = 'Ocultar QR Code';
|
|
}
|
|
}
|
|
|
|
function generateQRCode() {
|
|
if (qrCodeGenerated) {
|
|
console.log('QR Code already generated, skipping...');
|
|
return;
|
|
}
|
|
|
|
const qrcodeElement = document.getElementById("qrcode");
|
|
if (!qrcodeElement) {
|
|
console.error('QR Code container not found');
|
|
return;
|
|
}
|
|
|
|
// Check if already has content (double-call prevention)
|
|
if (qrcodeElement.querySelector('canvas')) {
|
|
console.log('Canvas already exists, skipping generation');
|
|
qrCodeGenerated = true;
|
|
return;
|
|
}
|
|
|
|
// Mark as generated BEFORE creating to prevent race conditions
|
|
qrCodeGenerated = true;
|
|
|
|
// Clear any existing content
|
|
qrcodeElement.innerHTML = '';
|
|
|
|
const pageUrl = window.location.href.split('?')[0]; // Remove query params
|
|
|
|
try {
|
|
new QRCode(qrcodeElement, {
|
|
text: pageUrl,
|
|
width: 200,
|
|
height: 200,
|
|
colorDark: "#000000",
|
|
colorLight: "#ffffff",
|
|
correctLevel: QRCode.CorrectLevel.H
|
|
});
|
|
console.log('QR Code generated successfully for:', pageUrl);
|
|
} catch (error) {
|
|
console.error('Error generating QR Code:', error);
|
|
qrcodeElement.innerHTML = '<p class="text-danger small">Erro ao gerar QR Code</p>';
|
|
qrCodeGenerated = false; // Reset on error so it can retry
|
|
}
|
|
}
|
|
|
|
function downloadQR() {
|
|
try {
|
|
const canvas = document.querySelector('#qrcode canvas');
|
|
if (!canvas) {
|
|
alert('QR Code não está disponível');
|
|
return;
|
|
}
|
|
|
|
const url = canvas.toDataURL("image/png");
|
|
const link = document.createElement('a');
|
|
link.download = 'qrcode-@(Model.Slug ?? "page").png';
|
|
link.href = url;
|
|
link.click();
|
|
} catch (error) {
|
|
console.error('Error downloading QR Code:', error);
|
|
alert('Erro ao baixar QR Code');
|
|
}
|
|
}
|
|
</script>
|
|
}
|