feat: novo menu

This commit is contained in:
Ricardo Carneiro 2025-06-09 23:16:00 -03:00
parent f3de10cc4f
commit c25bf9dc94
4 changed files with 786 additions and 984 deletions

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using OnlyOneAccessTemplate.Models;
using OnlyOneAccessTemplate.Services; using OnlyOneAccessTemplate.Services;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
@ -7,10 +8,12 @@ namespace OnlyOneAccessTemplate.Controllers
public class MenuController : ControllerBase public class MenuController : ControllerBase
{ {
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly ILogger<MenuController> _logger;
public MenuController(IModuleService moduleService) public MenuController(IModuleService moduleService, ILogger<MenuController> logger)
{ {
_moduleService = moduleService; _moduleService = moduleService;
_logger = logger;
} }
[HttpGet("converters")] [HttpGet("converters")]
@ -21,31 +24,52 @@ namespace OnlyOneAccessTemplate.Controllers
var modules = await _moduleService.GetAllActiveModulesAsync(); var modules = await _moduleService.GetAllActiveModulesAsync();
var menuItems = modules var menuItems = modules
.Where(m => m.ShowInMenu && m.IsActive && m.IsHealthy) .Where(m => m.ShowInMenu && m.IsActive)
.OrderBy(m => m.MenuOrder) .OrderBy(m => m.MenuOrder)
.ThenBy(m => m.MenuTitle) .ThenBy(m => m.MenuTitle)
.GroupBy(m => m.MenuCategory) .GroupBy(m => m.MenuCategory ?? "Conversores")
.Select(g => new .Select(g => new
{ {
category = g.Key, category = g.Key,
items = g.Select(m => new items = g.Select(m => new
{ {
moduleId = m.ModuleId, moduleId = m.ModuleId,
title = m.SeoTitles.ContainsKey(language) ? m.SeoTitles[language] : m.MenuTitle, title = GetLocalizedTitle(m, language),
description = m.SeoDescriptions.ContainsKey(language) ? m.SeoDescriptions[language] : m.MenuDescription, description = GetLocalizedDescription(m, language),
icon = m.MenuIcon, icon = m.MenuIcon ?? "fas fa-exchange-alt",
url = $"/{language}/{m.RequestBy}", url = $"/{language}/{m.RequestBy}",
order = m.MenuOrder, order = m.MenuOrder,
isNew = m.CreatedAt > DateTime.UtcNow.AddDays(-7) // Novos nos últimos 7 dias isNew = m.CreatedAt > DateTime.UtcNow.AddDays(-7),
isHealthy = m.IsHealthy,
version = m.Version
}).ToList() }).ToList()
}).ToList(); })
.OrderBy(g => g.category)
.ToList();
return Ok(new { success = true, menu = menuItems }); return Ok(new { success = true, menu = menuItems });
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Erro ao buscar menu de conversores");
return StatusCode(500, new { success = false, message = ex.Message }); return StatusCode(500, new { success = false, message = ex.Message });
} }
} }
private string GetLocalizedTitle(ModuleConfig module, string language)
{
if (module.SeoTitles?.ContainsKey(language) == true)
return module.SeoTitles[language];
return module.MenuTitle ?? module.Name ?? "Conversor";
}
private string GetLocalizedDescription(ModuleConfig module, string language)
{
if (module.SeoDescriptions?.ContainsKey(language) == true)
return module.SeoDescriptions[language];
return module.MenuDescription ?? "Ferramenta de conversão";
}
} }
} }

View File

@ -4,359 +4,380 @@
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
<!-- Google AdSense - Script Global -->
@section Head { @section Head {
<!-- Google AdSense Script -->
@if (ViewBag.GoogleAdsEnabled == true && !string.IsNullOrEmpty(ViewBag.GoogleAdsPublisher)) @if (ViewBag.GoogleAdsEnabled == true && !string.IsNullOrEmpty(ViewBag.GoogleAdsPublisher))
{ {
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@ViewBag.GoogleAdsPublisher" <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@ViewBag.GoogleAdsPublisher"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
} }
<style>
.ad-container { margin: 15px 0; text-align: center; min-height: 50px; }
.ad-banner { min-height: 90px; }
.ad-rectangle { min-height: 250px; }
.ad-sidebar { min-height: 600px; }
.ad-sticky { position: sticky; top: 20px; z-index: 100; }
.converter-section { background: #f8f9fa; }
@@media (max-width: 768px) {
.ad-sidebar { display: none !important; }
.ad-rectangle { min-height: 200px; }
.main-content { padding: 0 10px; }
}
@@media (min-width: 1200px) {
.ad-sidebar { min-height: 600px; }
}
</style>
} }
<!-- Banner Superior --> <!-- Hero Section -->
@{ <section class="hero-section">
ViewBag.AdPosition = "banner-top"; <div class="container">
ViewBag.AdSlotId = ViewBag.AdSlots?.BannerTop ?? "1234567890"; <div class="hero-content text-center">
ViewBag.AdFormat = "auto"; <h1 class="hero-title">
ViewBag.AdCssClass = "ad-container ad-banner"; @(ViewBag.MainTitle ?? "FERRAMENTAS DE CONVERSÃO")
ViewBag.ShowOnMobile = true; </h1>
ViewBag.ShowOnDesktop = true; <p class="hero-subtitle">
} @(ViewBag.MainDescription ?? "Converta seus arquivos de forma rápida e segura")
@await Html.PartialAsync("_AdUnit") </p>
<div class="container-fluid"> <div class="d-flex flex-wrap justify-content-center gap-2 mb-4">
<span class="feature-badge">
<i class="fas fa-code me-2"></i> Standalone
</span>
<span class="feature-badge">
<i class="fas fa-globe me-2"></i> Multi-idioma
</span>
<span class="feature-badge">
<i class="fas fa-bolt me-2"></i> API Ready
</span>
<span class="feature-badge">
<i class="fas fa-shield-alt me-2"></i> Seguro
</span>
</div>
</div>
</div>
</section>
<!-- Banner Superior Ads -->
<div class="container-fluid mt-3">
<div class="ad-placeholder banner">
<i class="fas fa-rectangle-ad"></i>
<div>
<strong>Anúncio Banner Superior</strong><br>
<small>728x90 ou responsivo</small>
</div>
</div>
</div>
<!-- Layout Principal -->
<div class="container-fluid main-layout">
<div class="row"> <div class="row">
<!-- Sidebar Esquerda com Anúncios --> <!-- Sidebar Esquerda - Anúncios -->
<div class="col-xl-2 col-lg-2 d-none d-lg-block"> <div class="col-xl-2 col-lg-2 d-none d-lg-block">
@{ <div class="ad-placeholder sidebar">
ViewBag.AdPosition = "sidebar-left"; <i class="fas fa-rectangle-ad"></i>
ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarLeft ?? "2345678901"; <div>
ViewBag.AdFormat = "vertical"; <strong>Anúncio Vertical</strong><br>
ViewBag.AdSize = "; width: 300px; height: 600px;"; <small>160x600 ou 300x600</small>
ViewBag.AdCssClass = "ad-container ad-sidebar"; </div>
ViewBag.IsSticky = true; </div>
ViewBag.ShowOnMobile = false;
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
</div> </div>
<!-- Conteúdo Principal --> <!-- Menu de Módulos -->
<div class="col-xl-8 col-lg-8 col-md-12 main-content"> <div class="col-xl-3 col-lg-3 col-md-4">
<!-- Seção Principal do Conversor --> <div class="modules-sidebar">
<section class="converter-section py-4"> <h5 class="fw-bold mb-3 d-flex align-items-center">
<div class="container"> <i class="fas fa-puzzle-piece text-primary me-2"></i>
<div class="row justify-content-center"> Conversores Disponíveis
<div class="col-lg-11"> </h5>
<!-- Título e Descrição -->
<div class="text-center mb-4">
<h1 class="display-4 fw-bold text-gradient mb-3">
@(ViewBag.ConverterTitle ?? "CONVERSOR ONLINE")
</h1>
<p class="lead text-green-light mb-4">
@(ViewBag.ConverterDescription ?? "Converta seus arquivos de forma rápida e segura")
</p>
</div>
<!-- Anúncio Retangular Antes do Conversor --> <div id="modules-list" class="modules-list">
@{ <!-- Loading state -->
ViewBag.AdPosition = "rectangle-pre-converter"; <div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
ViewBag.AdSlotId = ViewBag.AdSlots?.RectanglePre ?? "3456789012"; <div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
ViewBag.AdFormat = "rectangle"; <div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
ViewBag.AdSize = "; width: 300px; height: 250px;";
ViewBag.AdCssClass = "ad-container ad-rectangle mb-4";
ViewBag.IsSticky = false;
ViewBag.ShowOnMobile = true;
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
<!-- Card do Conversor -->
<div class="card shadow-lg border-0 rounded-3">
<div class="card-body p-4">
<!-- Steps do Processo -->
<div class="row text-center mb-4">
<div class="col-md-4 mb-3">
<div class="step-indicator">
<span class="badge bg-gradient-green rounded-circle p-3 fs-5 mb-2 text-white">1</span>
<h6 class="fw-bold text-green-light">@(ViewBag.Step1Title ?? "Digite")</h6>
<small class="text-muted">@(ViewBag.Step1Description ?? "Digite seu texto")</small>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="step-indicator">
<span class="badge bg-gradient-green rounded-circle p-3 fs-5 mb-2 text-white">2</span>
<h6 class="fw-bold text-green-light">@(ViewBag.Step2Title ?? "Converter")</h6>
<small class="text-muted">@(ViewBag.Step2Description ?? "Clique para converter")</small>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="step-indicator">
<span class="badge bg-gradient-green rounded-circle p-3 fs-5 mb-2 text-white">3</span>
<h6 class="fw-bold text-green-light">@(ViewBag.Step3Title ?? "Copiar")</h6>
<small class="text-muted">@(ViewBag.Step3Description ?? "Copie o resultado")</small>
</div>
</div>
</div>
<!-- Área do Conversor -->
<div id="converter-container">
@await Html.PartialAsync("_ConverterWidget")
</div>
<!-- Informações Adicionais -->
<div class="row mt-4">
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-shield-alt text-success me-2"></i>
<small class="text-green-light fw-bold">@(ViewBag.SecurityText ?? "Seus dados estão seguros")</small>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-bolt text-success me-2"></i>
<small class="text-green-light fw-bold">@(ViewBag.FileInfoText ?? "Processamento rápido e seguro")</small>
</div>
</div>
</div>
</div>
</div>
<!-- Anúncio Retangular Após o Conversor -->
@{
ViewBag.AdPosition = "rectangle-post-converter";
ViewBag.AdSlotId = ViewBag.AdSlots?.RectanglePost ?? "4567890123";
ViewBag.AdFormat = "rectangle";
ViewBag.AdSize = "; width: 300px; height: 250px;";
ViewBag.AdCssClass = "ad-container ad-rectangle mt-4";
ViewBag.IsSticky = false;
ViewBag.ShowOnMobile = true;
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
</div>
</div>
</div> </div>
</section>
<!-- Anúncio In-Feed Entre Seções --> <div class="text-center mt-3">
@{ <button class="btn btn-outline-primary btn-sm" onclick="refreshModulesList()">
ViewBag.AdPosition = "in-feed"; <i class="fas fa-sync me-1"></i> Atualizar
ViewBag.AdSlotId = ViewBag.AdSlots?.InFeed ?? "5678901234"; </button>
ViewBag.AdFormat = "fluid";
ViewBag.AdCssClass = "ad-container";
ViewBag.IsSticky = false;
ViewBag.ShowOnMobile = true;
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
<!-- Seção de Benefícios -->
<section class="benefits-section py-5">
<div class="container">
<div class="row">
<div class="col-lg-10 mx-auto text-center mb-5">
<h2 class="h3 fw-bold mb-3 text-gradient">@(ViewBag.BenefitsTitle ?? "Por Que Usar Nossa Ferramenta?")</h2>
<p class="text-green-light">@(ViewBag.BenefitsSubtitle ?? "Descubra os benefícios de nossa solução")</p>
</div>
</div>
<div class="row g-4 justify-content-center">
<!-- Features (código existente) -->
<div class="col-md-6 col-lg-3">
<div class="text-center p-3">
<div class="feature-icon mb-3">
<i class="fas fa-rocket fa-2x text-success"></i>
</div>
<h6 class="fw-bold text-green-light">@(ViewBag.Feature1Title ?? "Rápido e Fácil")</h6>
<small class="text-muted">@(ViewBag.Feature1Description ?? "Conversão instantânea")</small>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="text-center p-3">
<div class="feature-icon mb-3">
<i class="fas fa-shield-alt fa-2x text-success"></i>
</div>
<h6 class="fw-bold text-green-light">@(ViewBag.Feature2Title ?? "Seguro")</h6>
<small class="text-muted">@(ViewBag.Feature2Description ?? "Dados protegidos")</small>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="text-center p-3">
<div class="feature-icon mb-3">
<i class="fas fa-users fa-2x text-success"></i>
</div>
<h6 class="fw-bold text-green-light">@(ViewBag.Feature3Title ?? "Confiável")</h6>
<small class="text-muted">@(ViewBag.Feature3Description ?? "Resultados precisos")</small>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="text-center p-3">
<div class="feature-icon mb-3">
<i class="fas fa-clock fa-2x text-success"></i>
</div>
<h6 class="fw-bold text-green-light">Rápido</h6>
<small class="text-muted">Conversão em segundos</small>
</div>
</div>
</div>
</div> </div>
</section> </div>
</div>
<!-- Anúncio Multiplex --> <!-- Área Principal do Conversor -->
@{ <div class="col-xl-5 col-lg-5 col-md-8">
ViewBag.AdPosition = "multiplex"; <!-- Anúncio Retangular Pré-Conversor -->
ViewBag.AdSlotId = ViewBag.AdSlots?.Multiplex ?? "6789012345"; <div class="ad-placeholder rectangle">
ViewBag.AdFormat = "autorelaxed"; <i class="fas fa-rectangle-ad"></i>
ViewBag.AdCssClass = "ad-container"; <div>
ViewBag.IsSticky = false; <strong>Anúncio Retangular</strong><br>
ViewBag.ShowOnMobile = true; <small>300x250</small>
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
<!-- Seção CTA Final -->
@*
<section class="final-cta py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto text-center">
<h2 class="h3 fw-bold mb-3 text-gradient">@(ViewBag.FinalCtaTitle ?? "Pronto para Converter?")</h2>
<p class="text-green-light mb-4">@(ViewBag.FinalCtaSubtitle ?? "Use nossa ferramenta gratuita agora mesmo")</p>
<a href="#converter-container" class="btn btn-primary btn-lg hover-lift">
@(ViewBag.FinalCtaButtonText ?? "Começar Conversão")
</a>
</div>
</div>
</div> </div>
</section>
*@
</div> </div>
<!-- Sidebar Direita com Anúncios --> <!-- Card do Conversor -->
<div class="col-xl-2 col-lg-2 d-none d-lg-block"> <div class="converter-area fade-in">
@{ <div class="converter-header">
ViewBag.AdPosition = "sidebar-right"; <h2 class="h3 mb-3">
ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarRight ?? "7890123456"; <i class="fas fa-exchange-alt me-2"></i>
ViewBag.AdFormat = "vertical"; <span id="converter-title">Selecione um Conversor</span>
ViewBag.AdSize = "; width: 300px; height: 600px;"; </h2>
ViewBag.AdCssClass = "ad-container ad-sidebar"; <p class="mb-0" id="converter-description">
ViewBag.IsSticky = true; Escolha uma ferramenta de conversão no menu ao lado
ViewBag.ShowOnMobile = false; </p>
ViewBag.ShowOnDesktop = true; </div>
}
@await Html.PartialAsync("_AdUnit")
<!-- Anúncio Quadrado Adicional na Sidebar --> <!-- Steps do Processo -->
<div class="mt-4"> <div class="step-indicators">
@{ <div class="step-indicator">
ViewBag.AdPosition = "sidebar-square"; <div class="step-number">1</div>
ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarSquare ?? "8901234567"; <h6 class="step-title">Entrada</h6>
ViewBag.AdFormat = "square"; <p class="step-description">Selecione ou cole seu conteúdo</p>
ViewBag.AdSize = "; width: 250px; height: 250px;"; </div>
ViewBag.AdCssClass = "ad-container"; <div class="step-indicator">
ViewBag.IsSticky = false; <div class="step-number">2</div>
ViewBag.ShowOnMobile = false; <h6 class="step-title">Processar</h6>
ViewBag.ShowOnDesktop = true; <p class="step-description">Aguarde o processamento</p>
} </div>
@await Html.PartialAsync("_AdUnit") <div class="step-indicator">
<div class="step-number">3</div>
<h6 class="step-title">Resultado</h6>
<p class="step-description">Baixe ou copie o resultado</p>
</div>
</div>
<!-- Área do Módulo -->
<div id="converter-container" class="p-4">
<div class="text-center py-5">
<i class="fas fa-arrow-left fa-2x text-muted mb-3"></i>
<h5 class="text-muted">Selecione um conversor</h5>
<p class="text-muted">Escolha uma ferramenta no menu ao lado para começar</p>
</div>
</div>
</div>
<!-- Anúncio Retangular Pós-Conversor -->
<div class="ad-placeholder rectangle">
<i class="fas fa-rectangle-ad"></i>
<div>
<strong>Anúncio Pós-Conversão</strong><br>
<small>300x250</small>
</div>
</div>
</div>
<!-- Sidebar Direita - Anúncios -->
<div class="col-xl-2 col-lg-2 d-none d-lg-block">
<div class="ad-placeholder sidebar">
<i class="fas fa-rectangle-ad"></i>
<div>
<strong>Anúncio Vertical</strong><br>
<small>160x600 ou 300x600</small>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Banner Inferior Mobile --> <!-- Anúncio In-Feed -->
@{ <div class="container my-4">
ViewBag.AdPosition = "mobile-bottom"; <div class="ad-placeholder" style="min-height: 120px;">
ViewBag.AdSlotId = ViewBag.AdSlots?.MobileBottom ?? "9012345678"; <i class="fas fa-rectangle-ad"></i>
ViewBag.AdFormat = "banner"; <div>
ViewBag.AdSize = "; width: 320px; height: 50px;"; <strong>Anúncio In-Feed</strong><br>
ViewBag.AdCssClass = "fixed-bottom d-block d-md-none"; <small>Responsivo ou 728x90</small>
ViewBag.IsSticky = false; </div>
ViewBag.ShowOnMobile = true; </div>
ViewBag.ShowOnDesktop = false; </div>
}
<div class="@ViewBag.AdCssClass bg-white border-top p-2" style="z-index: 1050;" id="mobile-bottom-ad"> <!-- Seção de Benefícios -->
<div class="d-flex justify-content-between align-items-center"> <div class="container">
@await Html.PartialAsync("_AdUnit") <section class="benefits-section">
<button type="button" class="btn-close ms-2" onclick="document.getElementById('mobile-bottom-ad').style.display='none'"></button> <div class="text-center mb-5">
<h2 class="h3 fw-bold mb-3">Por Que Usar Nossas Ferramentas?</h2>
<p class="text-muted">Descubra os benefícios de nossas soluções</p>
</div>
<div class="feature-grid">
<div class="feature-item">
<div class="feature-icon">
<i class="fas fa-rocket"></i>
</div>
<h6 class="fw-bold mb-2">Rápido e Fácil</h6>
<p class="text-muted small mb-0">Conversão instantânea com poucos cliques</p>
</div>
<div class="feature-item">
<div class="feature-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h6 class="fw-bold mb-2">Seguro</h6>
<p class="text-muted small mb-0">Seus dados estão protegidos</p>
</div>
<div class="feature-item">
<div class="feature-icon">
<i class="fas fa-users"></i>
</div>
<h6 class="fw-bold mb-2">Confiável</h6>
<p class="text-muted small mb-0">Milhares de usuários satisfeitos</p>
</div>
<div class="feature-item">
<div class="feature-icon">
<i class="fas fa-clock"></i>
</div>
<h6 class="fw-bold mb-2">24/7 Disponível</h6>
<p class="text-muted small mb-0">Acesso a qualquer hora</p>
</div>
</div>
</section>
</div>
<!-- Anúncio Multiplex -->
<div class="container my-4">
<div class="ad-placeholder" style="min-height: 200px;">
<i class="fas fa-th"></i>
<div>
<strong>Anúncio Multiplex</strong><br>
<small>Formato flexível</small>
</div>
</div> </div>
</div> </div>
<!-- Scripts específicos -->
@section Scripts { @section Scripts {
<script src="~/js/converter.js"></script> <script src="~/js/module-loader.js"></script>
@{
var converterType = ViewBag.ConverterType ?? "generic";
var jsFileName = $"~/js/converters/{converterType.ToString().ToLower()}-converter.js";
}
<script src="@jsFileName"></script>
<!-- Inicialização dos Anúncios -->
<script> <script>
document.addEventListener('DOMContentLoaded', function() { let currentModuleId = null;
// Inicializar conversor
if (typeof initializeConverter === 'function') { document.addEventListener('DOMContentLoaded', function () {
initializeConverter(); loadModulesList();
}
// Auto-carregar primeiro módulo se não houver seleção
// Inicializar anúncios do Google
@if (ViewBag.GoogleAdsEnabled == true)
{
@Html.Raw("initializeGoogleAds();")
}
});
function initializeGoogleAds() {
try {
// Encontrar todos os anúncios na página
const adElements = document.querySelectorAll('.adsbygoogle');
// Inicializar cada anúncio
adElements.forEach(ad => {
if (!ad.dataset.adsbygoogleStatus) {
(adsbygoogle = window.adsbygoogle || []).push({});
}
});
console.log(`Initialized ${adElements.length} ad units`);
} catch (e) {
console.log('AdSense initialization error:', e);
}
}
// Refresh anúncios após conversão bem-sucedida
function refreshAdsAfterConversion() {
setTimeout(() => { setTimeout(() => {
try { const firstModule = document.querySelector('.module-item');
const adElements = document.querySelectorAll('.adsbygoogle[data-ad-position="rectangle-post-converter"]'); if (firstModule && !currentModuleId) {
adElements.forEach(ad => { firstModule.click();
(adsbygoogle = window.adsbygoogle || []).push({});
});
} catch (e) {
console.log('Ad refresh error:', e);
} }
}, 2000); }, 2000);
});
async function loadModulesList() {
try {
console.log('🔄 Carregando lista de módulos...');
const response = await fetch('/api/menu/converters?language=pt');
const data = await response.json();
if (data.success && data.menu) {
renderModulesList(data.menu);
} else {
showModulesError('Não foi possível carregar os módulos');
}
} catch (error) {
console.error('❌ Erro ao carregar módulos:', error);
showModulesError('Erro de conexão');
}
} }
function renderModulesList(menuData) {
const container = document.getElementById('modules-list');
let html = '';
menuData.forEach(category => {
html += `<div class="category-header">${category.category}</div>`;
category.items.forEach(item => {
const isNew = item.isNew ? '<span class="badge bg-warning badge-sm ms-1">Novo</span>' : '';
html += `
<a href="#" class="module-item" data-module-id="${item.moduleId}" onclick="loadModule('${item.moduleId}', '${item.title}', '${item.description}')">
<div class="module-icon">
<i class="${item.icon || 'fas fa-exchange-alt'}"></i>
</div>
<div class="module-title">${item.title}${isNew}</div>
<div class="module-description">${item.description || 'Ferramenta de conversão'}</div>
</a>
`;
});
});
if (html === '') {
html = `
<div class="text-center py-4 text-muted">
<i class="fas fa-puzzle-piece fa-2x mb-3"></i>
<p>Nenhum módulo disponível</p>
</div>
`;
}
container.innerHTML = html;
console.log('✅ Lista de módulos carregada');
}
function showModulesError(message) {
const container = document.getElementById('modules-list');
container.innerHTML = `
<div class="text-center py-4 text-danger">
<i class="fas fa-exclamation-triangle fa-2x mb-3"></i>
<p>${message}</p>
<button class="btn btn-outline-primary btn-sm" onclick="loadModulesList()">
Tentar Novamente
</button>
</div>
`;
}
async function loadModule(moduleId, title, description) {
if (currentModuleId === moduleId) return;
// Atualizar UI
document.querySelectorAll('.module-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-module-id="${moduleId}"]`).classList.add('active');
// Atualizar título
document.getElementById('converter-title').textContent = title;
document.getElementById('converter-description').textContent = description;
// Carregar módulo
const container = document.getElementById('converter-container');
container.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Carregando...</span>
</div>
<p>Carregando ${title}...</p>
</div>
`;
try {
const success = await window.ModuleLoader.loadModule(moduleId, 'converter-container', `/modules/${moduleId}`);
if (success) {
currentModuleId = moduleId;
console.log(`✅ Módulo ${moduleId} carregado com sucesso`);
// Analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'module_loaded', {
'module_id': moduleId,
'module_title': title
});
}
} else {
throw new Error('Falha ao carregar módulo');
}
} catch (error) {
console.error('❌ Erro ao carregar módulo:', error);
container.innerHTML = `
<div class="text-center py-4 text-danger">
<i class="fas fa-exclamation-triangle fa-2x mb-3"></i>
<h6>Erro ao carregar ${title}</h6>
<p class="text-muted">${error.message}</p>
<button class="btn btn-outline-primary" onclick="loadModule('${moduleId}', '${title}', '${description}')">
Tentar Novamente
</button>
</div>
`;
}
}
function refreshModulesList() {
const container = document.getElementById('modules-list');
container.innerHTML = `
<div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
<div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
<div class="loading-shimmer rounded p-3 mb-2" style="height: 80px;"></div>
`;
loadModulesList();
}
// Event listener para módulos carregados
document.addEventListener('moduleLoaded', function (event) {
console.log('🎯 Evento moduleLoaded:', event.detail);
});
</script> </script>
} }

View File

@ -1,70 +1,91 @@
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary sticky-top">
<div class="container"> <div class="container">
<a class="navbar-brand d-flex align-items-center" href="@ViewBag.HomeUrl"> <a class="navbar-brand d-flex align-items-center" href="/@ViewBag.Language">
<img src="@ViewBag.LogoUrl" alt="?" height="40" class="me-2"> <!-- Usando seu logo existente -->
<span class="fw-bold text-gradient">@ViewBag.SiteName</span> <img src="~/img/logo-white.png" alt="Convert-it" height="32" class="me-2">
<strong>Convert-it</strong>
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav me-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link @(ViewBag.CurrentPage == "home" ? "active" : "")" <a class="nav-link" href="/@ViewBag.Language">
href="@ViewBag.HomeUrl"> <i class="fas fa-home me-1"></i>
@ViewBag.MenuHome @(ViewBag.Language == "pt" ? "Início" : ViewBag.Language == "es" ? "Inicio" : "Home")
</a> </a>
</li> </li>
@*
<li class="nav-item">
<a class="nav-link @(ViewBag.CurrentPage == "about" ? "active" : "")"
href="@ViewBag.AboutUrl">
@ViewBag.MenuAbout
</a>
</li>
<li class="nav-item">
<a class="nav-link @(ViewBag.CurrentPage == "contact" ? "active" : "")"
href="@ViewBag.ContactUrl">
@ViewBag.MenuContact
</a>
</li>
*@
</ul> </ul>
<!-- Status dos Módulos -->
<div class="navbar-nav me-3">
<span class="navbar-text d-flex align-items-center">
<span class="status-indicator me-2" id="modules-status"></span>
<small id="modules-count">Carregando...</small>
</span>
</div>
<!-- Language Switcher --> <!-- Language Switcher -->
<div class="dropdown me-3"> <div class="dropdown">
<button class="btn btn-outline-success btn-sm dropdown-toggle" type="button" <button class="btn btn-outline-light dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown">
id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-globe me-1"></i> <i class="fas fa-globe me-1"></i>
@ViewBag.CurrentLanguageDisplay @ViewBag.Language?.ToUpper()
</button> </button>
<ul class="dropdown-menu" aria-labelledby="languageDropdown"> <ul class="dropdown-menu dropdown-menu-end">
<li> <li>
<a class="dropdown-item @(ViewBag.Language == "pt" ? "active" : "")" <a class="dropdown-item" href="/pt@(ViewContext.RouteData.Values["converter"] != null ? "/" + ViewContext.RouteData.Values["converter"] : "")">
href="@ViewBag.PtUrl">🇧🇷 Português</a> <i class="flag-icon flag-icon-br me-2"></i>Português
</a>
</li> </li>
<li> <li>
<a class="dropdown-item @(ViewBag.Language == "en" ? "active" : "")" <a class="dropdown-item" href="/en@(ViewContext.RouteData.Values["converter"] != null ? "/" + ViewContext.RouteData.Values["converter"] : "")">
href="@ViewBag.EnUrl">🇺🇸 English</a> <i class="flag-icon flag-icon-us me-2"></i>English
</a>
</li> </li>
<li> <li>
<a class="dropdown-item @(ViewBag.Language == "es" ? "active" : "")" <a class="dropdown-item" href="/es@(ViewContext.RouteData.Values["converter"] != null ? "/" + ViewContext.RouteData.Values["converter"] : "")">
href="@ViewBag.EsUrl">🇪🇸 Español</a> <i class="flag-icon flag-icon-es me-2"></i>Español
</a>
</li> </li>
</ul> </ul>
</div> </div>
</div>
@*
<!-- CTA Button -->
<a href="#conversion-form" class="btn btn-primary btn-sm scroll-to-form hover-lift">
@ViewBag.CtaButtonText
</a>
*@
</div>
</div> </div>
</header> </nav>
<script>
// Atualizar status dos módulos no header
async function updateModulesStatus() {
try {
const response = await fetch('/api/menu/converters?language=@ViewBag.Language');
const data = await response.json();
const statusIndicator = document.getElementById('modules-status');
const countElement = document.getElementById('modules-count');
if (data.success && data.menu) {
const totalModules = data.menu.reduce((total, category) => total + category.items.length, 0);
const activeModules = data.menu.reduce((total, category) =>
total + category.items.filter(item => item.isHealthy !== false).length, 0);
statusIndicator.className = `status-indicator me-2 ${activeModules === totalModules ? 'bg-success' : 'bg-warning'}`;
statusIndicator.style.width = '8px';
statusIndicator.style.height = '8px';
statusIndicator.style.borderRadius = '50%';
statusIndicator.style.display = 'inline-block';
countElement.textContent = `${activeModules}/${totalModules} módulos`;
} else {
statusIndicator.className = 'status-indicator me-2 bg-danger';
countElement.textContent = 'Erro ao carregar';
}
} catch (error) {
console.error('Erro ao atualizar status:', error);
}
}
document.addEventListener('DOMContentLoaded', updateModulesStatus);
</script>

File diff suppressed because it is too large Load Diff