Compare commits

...

3 Commits

Author SHA1 Message Date
Ricardo Carneiro
cbda3db3f3 feat: pipeline
Some checks are pending
Deploy ASP.NET MVC to OCI / build-and-deploy (push) Waiting to run
2025-06-02 21:41:13 -03:00
Ricardo Carneiro
16001eb15b feat: nova versão simplificada 2025-06-01 21:39:47 -03:00
Ricardo Carneiro
c7ada0119d fix: ajustes gerais 2025-06-01 20:50:21 -03:00
28 changed files with 1412 additions and 1128 deletions

View File

@ -0,0 +1,3 @@
{
"enableAllProjectMcpServers": false
}

30
.dockerignore Normal file
View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

106
.gitea/workflows/deploy.yml Normal file
View File

@ -0,0 +1,106 @@
name: Deploy ASP.NET MVC to OCI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: registry.redecarneir.us
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: true
tags: |
registry.redecarneir.us/onlyoneaccesstemplate:latest
registry.redecarneir.us/onlyoneaccesstemplate:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to OCI Server
uses: appleboy/ssh-action@v1.0.3
with:
host: 129.146.116.218
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
# Login no registry Docker
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login registry.redecarneir.us -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
# Para qualquer container que esteja usando a porta 80
echo "=== Verificando containers na porta 80 ==="
CONTAINERS_PORT_80=$(docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}" | grep ':80->' | awk '{print $2}' || true)
if [ ! -z "$CONTAINERS_PORT_80" ]; then
echo "Parando containers na porta 80: $CONTAINERS_PORT_80"
echo "$CONTAINERS_PORT_80" | xargs -r docker stop
echo "$CONTAINERS_PORT_80" | xargs -r docker rm
else
echo "Nenhum container encontrado na porta 80"
fi
# Para o container anterior da aplicação (se existir)
docker stop onlyoneaccesstemplate || true
docker rm onlyoneaccesstemplate || true
# Remove imagem antiga
docker rmi registry.redecarneir.us/onlyoneaccesstemplate:latest || true
# Puxa nova imagem
docker pull registry.redecarneir.us/onlyoneaccesstemplate:latest
# Executa o novo container na porta 80
docker run -d \
--name onlyoneaccesstemplate \
--restart unless-stopped \
-p 80:8080 \
-p 443:8081 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ASPNETCORE_URLS="http://+:8080;https://+:8081" \
registry.redecarneir.us/onlyoneaccesstemplate:latest
# Limpa imagens não utilizadas
docker image prune -f
# Verifica se o container está rodando
docker ps | grep onlyoneaccesstemplate
# Testa se a aplicação está respondendo na porta 80
sleep 10
curl -f http://localhost:80 || echo "Aplicação pode estar inicializando..."
- name: Verify deployment
uses: appleboy/ssh-action@v1.0.3
with:
host: 129.146.116.218
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
echo "=== Status dos containers ==="
docker ps -a | grep onlyoneaccesstemplate
echo "=== Logs da aplicação (últimas 20 linhas) ==="
docker logs onlyoneaccesstemplate --tail 20
echo "=== Teste de conectividade ==="
curl -I http://localhost:80 || echo "Aplicação ainda não está acessível"

View File

@ -3,7 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.10.35122.118 VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnlyOneAccessTemplate", "OnlyOneAccessTemplate\OnlyOneAccessTemplate.csproj", "{2A672B8D-D16E-452E-A975-A3E19625453B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OnlyOneAccessTemplate", "OnlyOneAccessTemplate\OnlyOneAccessTemplate.csproj", "{2A672B8D-D16E-452E-A975-A3E19625453B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipeline", "pipeline", "{21A583BA-1BDC-42C4-BE42-DD13267BDA20}"
ProjectSection(SolutionItems) = preProject
.gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -6,7 +6,7 @@ using OnlyOneAccessTemplate.Models;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
{ {
public abstract class BaseController : Controller public class BaseController : Controller
{ {
protected readonly ISiteConfigurationService _siteConfig; protected readonly ISiteConfigurationService _siteConfig;
protected readonly ILanguageService _languageService; protected readonly ILanguageService _languageService;
@ -59,26 +59,29 @@ namespace OnlyOneAccessTemplate.Controllers
protected void SetupSeoViewBag(SiteConfiguration config, string language, string currentUrl) protected void SetupSeoViewBag(SiteConfiguration config, string language, string currentUrl)
{ {
ViewBag.GoogleAdsPublisher = _configuration.GetValue<string>("GoogleAds:PublisherID");
ViewBag.GoogleAdsEnabled = _configuration.GetValue<bool>("GoogleAds:Enabled", true);
// Basic SEO // Basic SEO
ViewBag.Language = language; ViewBag.Language = language;
ViewBag.Direction = _languageService.GetLanguageDirection(language); ViewBag.Direction = _languageService.GetLanguageDirection(language);
ViewBag.SiteName = config.Seo.SiteName; ViewBag.SiteName = config.SiteTitle;
ViewBag.SiteDescription = config.Seo.DefaultDescription; ViewBag.SiteDescription = config.SiteDescription;
ViewBag.CanonicalUrl = currentUrl; ViewBag.CanonicalUrl = currentUrl;
ViewBag.Author = config.Seo.Author; ViewBag.Author = "Convert-it Online Team";
ViewBag.GTMId = config.Seo.GoogleTagManagerId; ViewBag.GTMId = _configuration.GetValue<string>("SEO:GoogleTagManagerId");
// Default SEO values // Default SEO values
ViewBag.Title = ViewBag.Title ?? config.Seo.DefaultTitle; ViewBag.Title = ViewBag.Title ?? config.SiteTitle;
ViewBag.Description = ViewBag.Description ?? config.Seo.DefaultDescription; ViewBag.Description = ViewBag.Description ?? config.SiteDescription;
ViewBag.Keywords = ViewBag.Keywords ?? config.Seo.DefaultKeywords; ViewBag.Keywords = ViewBag.Keywords ?? config.SiteKeywords;
// Open Graph // Open Graph
ViewBag.OgTitle = ViewBag.Title; ViewBag.OgTitle = ViewBag.Title;
ViewBag.OgDescription = ViewBag.Description; ViewBag.OgDescription = ViewBag.Description;
ViewBag.OgImage = config.Seo.OgImage; ViewBag.OgImage = config.OgImage;
ViewBag.OgLocale = GetOgLocale(language); ViewBag.OgLocale = GetOgLocale(language);
ViewBag.TwitterHandle = config.Seo.TwitterHandle; ViewBag.TwitterHandle = "@convertit_online";
// Language alternatives // Language alternatives
SetupHreflangUrls(currentUrl, language); SetupHreflangUrls(currentUrl, language);
@ -89,6 +92,8 @@ namespace OnlyOneAccessTemplate.Controllers
protected void SetupContentViewBag(SiteConfiguration config, string language) protected void SetupContentViewBag(SiteConfiguration config, string language)
{ {
ViewBag.GoogleAdsPublisher = _configuration.GetValue<string>("GoogleAds:PublisherID");
ViewBag.GoogleAdsEnabled = _configuration.GetValue<bool>("GoogleAds:Enabled", true);
// Navigation // Navigation
ViewBag.HomeUrl = language == "pt" ? "/" : $"/{language}"; ViewBag.HomeUrl = language == "pt" ? "/" : $"/{language}";
ViewBag.AboutUrl = language == "pt" ? "/about" : $"/{language}/about"; ViewBag.AboutUrl = language == "pt" ? "/about" : $"/{language}/about";
@ -114,8 +119,9 @@ namespace OnlyOneAccessTemplate.Controllers
ViewBag.LogoUrl = "/img/logo.png"; ViewBag.LogoUrl = "/img/logo.png";
ViewBag.BodyClass = $"lang-{language}"; ViewBag.BodyClass = $"lang-{language}";
// Conversion config // AdSense config
ViewBag.ConversionConfig = config.Conversion; ViewBag.AdSenseClientId = config.AdSenseClientId;
ViewBag.AdSenseSlotId = config.AdSenseSlotId;
} }
// Nova função para configurar textos específicos do conversor // Nova função para configurar textos específicos do conversor

View File

@ -7,7 +7,7 @@ using OnlyOneAccessTemplate.Models;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
{ {
[ApiController] [ApiController]
[Route("converter")] // Adicione esta linha [Route("converter")]
public class ConverterController : BaseController public class ConverterController : BaseController
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
@ -34,7 +34,7 @@ namespace OnlyOneAccessTemplate.Controllers
["text-case"] = typeof(TextCaseConverterService), ["text-case"] = typeof(TextCaseConverterService),
["csv-json"] = typeof(CsvToJsonConverterService), ["csv-json"] = typeof(CsvToJsonConverterService),
["image-ocr"] = typeof(ImageToTextConverterService), ["image-ocr"] = typeof(ImageToTextConverterService),
["sentence-converter"] = typeof(UpperLowerConversorService) ["sentence-converter"] = typeof(ApiBasedSentenceConverterService)
// Adicionar novos conversores aqui // Adicionar novos conversores aqui
}; };
} }

View File

@ -1,13 +1,12 @@
using global::OnlyOneAccessTemplate.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using OnlyOneAccessTemplate.Models;
using OnlyOneAccessTemplate.Services; using OnlyOneAccessTemplate.Services;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
{ {
[Route("en")] [Route("en")]
public class EnController : HomeController public class EnController : BaseController
{ {
public EnController( public EnController(
ISiteConfigurationService siteConfig, ISiteConfigurationService siteConfig,
@ -22,22 +21,25 @@ namespace OnlyOneAccessTemplate.Controllers
protected override string GetCurrentLanguage() => "en"; protected override string GetCurrentLanguage() => "en";
[Route("")] [Route("")]
[Route("home")] [Route("index")]
public new async Task<IActionResult> Index() public IActionResult Index()
{ {
return await base.Index(); var siteConfig = _siteConfig.GetConfiguration("en");
ViewBag.PageTitle = siteConfig.SiteTitle;
ViewBag.PageDescription = siteConfig.SiteDescription;
return View("~/Views/Home/Index.cshtml", siteConfig);
} }
[Route("about")] [Route("convert-pdf-to-word")]
public new async Task<IActionResult> About() public IActionResult ConvertPdfToWord()
{ {
return await base.About(); var siteConfig = _siteConfig.GetConfiguration("en");
}
[Route("contact")] SetPageSeo("Convert PDF to Word Online Free",
public new async Task<IActionResult> Contact() "Convert your PDF files to Word quickly and free...");
{
return await base.Contact(); return View("~/Views/Home/Index.cshtml", siteConfig);
} }
} }
} }

View File

@ -1,13 +1,12 @@
using global::OnlyOneAccessTemplate.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using OnlyOneAccessTemplate.Models;
using OnlyOneAccessTemplate.Services; using OnlyOneAccessTemplate.Services;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
{ {
[Route("es")] [Route("es")]
public class EsController : HomeController public class EsController : BaseController
{ {
public EsController( public EsController(
ISiteConfigurationService siteConfig, ISiteConfigurationService siteConfig,
@ -23,21 +22,24 @@ namespace OnlyOneAccessTemplate.Controllers
[Route("")] [Route("")]
[Route("inicio")] [Route("inicio")]
public new async Task<IActionResult> Index() public IActionResult Index()
{ {
return await base.Index(); var siteConfig = _siteConfig.GetConfiguration("es");
ViewBag.PageTitle = siteConfig.SiteTitle;
ViewBag.PageDescription = siteConfig.SiteDescription;
return View("~/Views/Home/Index.cshtml", siteConfig);
} }
[Route("acerca")] [Route("convertir-pdf-a-word-en-linea")]
public new async Task<IActionResult> About() public IActionResult ConvertirPdfAWordEnLinea()
{ {
return await base.About(); var siteConfig = _siteConfig.GetConfiguration("es");
}
[Route("contacto")] SetPageSeo("Convertir PDF a Word en Línea Gratis",
public new async Task<IActionResult> Contact() "Convierte tus archivos PDF a Word rápidamente y gratis...");
{
return await base.Contact(); return View("~/Views/Home/Index.cshtml", siteConfig);
} }
} }
} }

View File

@ -1,12 +1,10 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using OnlyOneAccessTemplate.Services; using OnlyOneAccessTemplate.Services;
using OnlyOneAccessTemplate.Models; using OnlyOneAccessTemplate.Models;
namespace OnlyOneAccessTemplate.Controllers namespace OnlyOneAccessTemplate.Controllers
{ {
public class HomeController : BaseController public class HomeController : BaseController
{ {
public HomeController( public HomeController(
@ -19,92 +17,18 @@ namespace OnlyOneAccessTemplate.Controllers
{ {
} }
public async Task<IActionResult> Index() [Route("")]
[Route("pt")]
public IActionResult Index()
{ {
var language = GetCurrentLanguage(); var language = GetCurrentLanguage();
var siteConfig = _siteConfig.GetConfiguration(language);
// Get page content from database ViewBag.PageTitle = siteConfig.SiteTitle;
var pageContent = await _siteConfig.GetPageContentAsync(language, "index"); ViewBag.PageDescription = siteConfig.SiteDescription;
// If no content exists, create default content
if (pageContent == null)
{
await CreateDefaultContentAsync(language);
pageContent = await _siteConfig.GetPageContentAsync(language, "index");
}
// Set current page for navigation
ViewBag.CurrentPage = "home"; ViewBag.CurrentPage = "home";
return View(pageContent); return View(siteConfig);
}
public async Task<IActionResult> About()
{
var language = GetCurrentLanguage();
var pageContent = await _siteConfig.GetPageContentAsync(language, "about");
if (pageContent == null)
{
await CreateDefaultContentAsync(language);
pageContent = await _siteConfig.GetPageContentAsync(language, "about");
}
ViewBag.CurrentPage = "about";
return View(pageContent);
}
public async Task<IActionResult> Contact()
{
var language = GetCurrentLanguage();
var pageContent = await _siteConfig.GetPageContentAsync(language, "contact");
ViewBag.CurrentPage = "contact";
return View(pageContent);
}
[HttpGet("setup-data")]
public async Task<IActionResult> SetupDefaultData()
{
try
{
var languages = new[] { "pt", "en", "es" };
foreach (var language in languages)
{
await CreateDefaultContentAsync(language);
}
return Json(new { success = true, message = "Dados padrão criados com sucesso!" });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpGet("privacy")]
public IActionResult Privacy()
{
ViewBag.CurrentPage = "privacy";
SetPageSeo(
"Política de Privacidade",
"Nossa política de privacidade e proteção de dados",
"privacidade, proteção de dados, lgpd"
);
return View();
}
[HttpGet("terms")]
public IActionResult Terms()
{
ViewBag.CurrentPage = "terms";
SetPageSeo(
"Termos de Uso",
"Termos e condições de uso do nosso serviço",
"termos, condições, uso"
);
return View();
} }
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
@ -113,438 +37,5 @@ namespace OnlyOneAccessTemplate.Controllers
return View(); return View();
} }
private async Task CreateDefaultContentAsync(string language)
{
try
{
// Check if configuration already exists
var configExists = await _siteConfig.ConfigurationExistsAsync(language);
if (configExists) return;
// Create default configuration with sample content
var siteConfig = new SiteConfiguration
{
Language = language,
Seo = CreateDefaultSeoConfig(language),
Pages = CreateDefaultPages(language),
Conversion = CreateDefaultConversionConfig(language),
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
await _siteConfig.UpdateConfigurationAsync(siteConfig);
}
catch (Exception ex)
{
// Log error but don't break the page
Console.WriteLine($"Error creating default content: {ex.Message}");
}
}
private SeoConfig CreateDefaultSeoConfig(string language)
{
return language switch
{
"en" => new SeoConfig
{
SiteName = "OnlyOne Access Template",
DefaultTitle = "Transform Your Business - Conversion Template",
DefaultDescription = "Professional conversion solutions to boost your business results with proven methodology",
DefaultKeywords = "conversion, business, marketing, results, template",
TwitterCard = "summary_large_image",
GoogleTagManagerId = "GTM-XXXXXXX",
GoogleAnalyticsId = "GA-XXXXXXXX",
Author = "OnlyOne Access Team",
OgImage = "/img/og-image-en.jpg"
},
"es" => new SeoConfig
{
SiteName = "OnlyOne Access Template",
DefaultTitle = "Transforma Tu Negocio - Template de Conversión",
DefaultDescription = "Soluciones profesionales de conversión para potenciar los resultados de tu negocio",
DefaultKeywords = "conversión, negocio, marketing, resultados, plantilla",
TwitterCard = "summary_large_image",
GoogleTagManagerId = "GTM-XXXXXXX",
GoogleAnalyticsId = "GA-XXXXXXXX",
Author = "Equipo OnlyOne Access",
OgImage = "/img/og-image-es.jpg"
},
_ => new SeoConfig
{
SiteName = "OnlyOne Access Template",
DefaultTitle = "Transforme Seu Negócio - Template de Conversão",
DefaultDescription = "Soluções profissionais de conversão para alavancar os resultados do seu negócio",
DefaultKeywords = "conversão, negócio, marketing, resultados, template",
TwitterCard = "summary_large_image",
GoogleTagManagerId = "GTM-XXXXXXX",
GoogleAnalyticsId = "GA-XXXXXXXX",
Author = "Equipe OnlyOne Access",
OgImage = "/img/og-image-pt.jpg"
}
};
}
private Dictionary<string, PageContent> CreateDefaultPages(string language)
{
return language switch
{
"en" => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Transform Your Business Today",
Description = "Increase your sales by up to 300% with our proven methodology and expert support",
Keywords = "business transformation, conversion, sales growth, methodology",
H1 = "Transform Your Business Today",
MetaTitle = "Business Transformation | Conversion Solutions",
Blocks = CreateEnglishBlocks(),
IsActive = true,
PublishDate = DateTime.UtcNow
},
["about"] = new PageContent
{
Title = "About Us - Our Mission",
Description = "Learn about our mission to help businesses achieve better conversion rates",
H1 = "About Our Company",
MetaTitle = "About Us | Conversion Experts"
}
},
"es" => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Transforma Tu Negocio Hoy",
Description = "Aumenta tus ventas hasta un 300% con nuestra metodología probada y soporte especializado",
Keywords = "transformación empresarial, conversión, crecimiento de ventas, metodología",
H1 = "Transforma Tu Negocio Hoy",
MetaTitle = "Transformación Empresarial | Soluciones de Conversión",
Blocks = CreateSpanishBlocks(),
IsActive = true,
PublishDate = DateTime.UtcNow
},
["about"] = new PageContent
{
Title = "Acerca de Nosotros - Nuestra Misión",
Description = "Conoce nuestra misión de ayudar a las empresas a lograr mejores tasas de conversión",
H1 = "Acerca de Nuestra Empresa",
MetaTitle = "Acerca de Nosotros | Expertos en Conversión"
}
},
_ => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Transforme Seu Negócio Hoje",
Description = "Aumente suas vendas em até 300% com nossa metodologia comprovada e suporte especializado",
Keywords = "transformação empresarial, conversão, crescimento de vendas, metodologia",
H1 = "Transforme Seu Negócio Hoje",
MetaTitle = "Transformação Empresarial | Soluções de Conversão",
Blocks = CreatePortugueseBlocks(),
IsActive = true,
PublishDate = DateTime.UtcNow
},
["about"] = new PageContent
{
Title = "Sobre Nós - Nossa Missão",
Description = "Conheça nossa missão de ajudar empresas a alcançar melhores taxas de conversão",
H1 = "Sobre Nossa Empresa",
MetaTitle = "Sobre Nós | Especialistas em Conversão"
}
}
};
}
private List<ContentBlock> CreatePortugueseBlocks()
{
return new List<ContentBlock>
{
new ContentBlock
{
Type = "hero",
Title = "Transforme Seu Negócio com Nossa <span class='text-gradient'>Solução Inovadora</span>",
Content = "Aumente suas vendas em até 300% com nossa metodologia comprovada, suporte especializado e resultados garantidos em 30 dias.",
ImageUrl = "/img/hero-pt.jpg",
ButtonText = "Começar Transformação",
ButtonUrl = "#conversion-form",
Order = 1,
Properties = new Dictionary<string, object>
{
{ "badge", "🚀 Mais de 1000 empresas transformadas" },
{ "features", new[] { "✅ Metodologia comprovada", "✅ Suporte 24/7", "✅ Resultados em 30 dias" } },
{ "social_proof", "Mais de 500 avaliações 5 estrelas" }
}
},
new ContentBlock
{
Type = "features",
Title = "Por Que Escolher Nossa Solução?",
Content = "Descubra os benefícios que já transformaram mais de 1000 empresas em todo o Brasil",
Order = 2,
Properties = new Dictionary<string, object>
{
{ "feature_list", new[]
{
new Dictionary<string, object>
{
{ "icon", "fas fa-rocket" },
{ "title", "Resultados Rápidos" },
{ "description", "Veja os primeiros resultados em até 30 dias com nossa metodologia testada" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-shield-alt" },
{ "title", "Garantia Total" },
{ "description", "100% de garantia ou seu dinheiro de volta. Assumimos o risco por você" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-users" },
{ "title", "Suporte Especializado" },
{ "description", "Equipe dedicada 24/7 para garantir seu sucesso em cada etapa" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-chart-line" },
{ "title", "Crescimento Sustentável" },
{ "description", "Estratégias de longo prazo para crescimento consistente e duradouro" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-cog" },
{ "title", "Automatização" },
{ "description", "Processos automatizados que trabalham para você 24 horas por dia" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-trophy" },
{ "title", "Casos de Sucesso" },
{ "description", "Mais de 1000 empresas já alcançaram resultados extraordinários" }
}
}
}
}
},
new ContentBlock
{
Type = "testimonials",
Title = "O Que Nossos Clientes Dizem",
Content = "Mais de 1000 empresas já transformaram seus resultados. Veja alguns depoimentos:",
Order = 3,
Properties = new Dictionary<string, object>
{
{ "testimonials", new[]
{
new Dictionary<string, object>
{
{ "quote", "Aumentamos nossas vendas em 250% em apenas 3 meses. A metodologia realmente funciona e o suporte é excepcional!" },
{ "name", "Maria Silva" },
{ "position", "CEO, TechStart Brasil" },
{ "avatar", "/img/testimonial-1.jpg" }
},
new Dictionary<string, object>
{
{ "quote", "O ROI foi incrível. Recuperamos o investimento em 30 dias e continuamos crescendo desde então." },
{ "name", "João Santos" },
{ "position", "Diretor Comercial, InovaCorp" },
{ "avatar", "/img/testimonial-2.jpg" }
},
new Dictionary<string, object>
{
{ "quote", "Finalmente encontramos uma solução que entrega o que promete. Resultados desde o primeiro mês!" },
{ "name", "Ana Costa" },
{ "position", "Fundadora, GrowBiz" },
{ "avatar", "/img/testimonial-3.jpg" }
}
}
}
}
},
new ContentBlock
{
Type = "cta",
Title = "Pronto para <span class='text-warning'>Transformar</span> Seu Negócio?",
Content = "Junte-se a mais de 1000 empresas que já alcançaram resultados extraordinários com nossa metodologia comprovada.",
ButtonText = "Começar Minha Transformação",
ButtonUrl = "#conversion-form",
Order = 4,
Properties = new Dictionary<string, object>
{
{ "urgency_badge", "⚡ Últimas vagas da semana" },
{ "guarantee", "🛡️ Garantia de 30 dias ou seu dinheiro de volta" },
{ "secondary_button_text", "Assistir Demonstração" },
{ "secondary_button_url", "#demo-video" }
}
}
};
}
private List<ContentBlock> CreateEnglishBlocks()
{
return new List<ContentBlock>
{
new ContentBlock
{
Type = "hero",
Title = "Transform Your Business with Our <span class='text-gradient'>Innovative Solution</span>",
Content = "Increase your sales by up to 300% with our proven methodology, expert support and guaranteed results in 30 days.",
ImageUrl = "/img/hero-en.jpg",
ButtonText = "Start Transformation",
ButtonUrl = "#conversion-form",
Order = 1,
Properties = new Dictionary<string, object>
{
{ "badge", "🚀 Over 1000 companies transformed" },
{ "features", new[] { "✅ Proven methodology", "✅ 24/7 Support", "✅ Results in 30 days" } },
{ "social_proof", "Over 500 five-star reviews" }
}
},
new ContentBlock
{
Type = "features",
Title = "Why Choose Our Solution?",
Content = "Discover the benefits that have already transformed over 1000 companies worldwide",
Order = 2,
Properties = new Dictionary<string, object>
{
{ "feature_list", new[]
{
new Dictionary<string, object>
{
{ "icon", "fas fa-rocket" },
{ "title", "Fast Results" },
{ "description", "See first results within 30 days with our tested methodology" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-shield-alt" },
{ "title", "Full Guarantee" },
{ "description", "100% guarantee or your money back. We take the risk for you" }
},
new Dictionary<string, object>
{
{ "icon", "fas fa-users" },
{ "title", "Expert Support" },
{ "description", "24/7 dedicated team to ensure your success at every step" }
}
}
}
}
},
new ContentBlock
{
Type = "testimonials",
Title = "What Our Clients Say",
Content = "Over 1000 companies have already transformed their results. See some testimonials:",
Order = 3,
Properties = new Dictionary<string, object>
{
{ "testimonials", new[]
{
new Dictionary<string, object>
{
{ "quote", "We increased our sales by 250% in just 3 months. The methodology really works!" },
{ "name", "Mary Johnson" },
{ "position", "CEO, TechStart USA" },
{ "avatar", "/img/testimonial-en-1.jpg" }
}
}
}
}
},
new ContentBlock
{
Type = "cta",
Title = "Ready to <span class='text-warning'>Transform</span> Your Business?",
Content = "Join over 1000 companies that have already achieved extraordinary results.",
ButtonText = "Start My Transformation",
ButtonUrl = "#conversion-form",
Order = 4
}
};
}
private List<ContentBlock> CreateSpanishBlocks()
{
return new List<ContentBlock>
{
new ContentBlock
{
Type = "hero",
Title = "Transforma Tu Negocio con Nuestra <span class='text-gradient'>Solución Innovadora</span>",
Content = "Aumenta tus ventas hasta un 300% con nuestra metodología probada, soporte especializado y resultados garantizados en 30 días.",
ImageUrl = "/img/hero-es.jpg",
ButtonText = "Comenzar Transformación",
ButtonUrl = "#conversion-form",
Order = 1,
Properties = new Dictionary<string, object>
{
{ "badge", "🚀 Más de 1000 empresas transformadas" },
{ "features", new[] { "✅ Metodología probada", "✅ Soporte 24/7", "✅ Resultados en 30 días" } },
{ "social_proof", "Más de 500 reseñas de 5 estrellas" }
}
},
new ContentBlock
{
Type = "features",
Title = "¿Por Qué Elegir Nuestra Solución?",
Content = "Descubre los beneficios que ya han transformado más de 1000 empresas en todo el mundo",
Order = 2
},
new ContentBlock
{
Type = "testimonials",
Title = "Lo Que Dicen Nuestros Clientes",
Content = "Más de 1000 empresas ya han transformado sus resultados. Ve algunos testimonios:",
Order = 3
},
new ContentBlock
{
Type = "cta",
Title = "¿Listo para <span class='text-warning'>Transformar</span> Tu Negocio?",
Content = "Únete a más de 1000 empresas que ya han logrado resultados extraordinarios.",
ButtonText = "Comenzar Mi Transformación",
ButtonUrl = "#conversion-form",
Order = 4
}
};
}
private ConversionConfig CreateDefaultConversionConfig(string language)
{
return new ConversionConfig
{
FormAction = "/conversion/submit",
ThankYouPage = language switch
{
"en" => "/en/thank-you",
"es" => "/es/gracias",
_ => "/obrigado"
},
FormFields = language switch
{
"en" => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Full Name", Placeholder = "Enter your full name", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "Email Address", Placeholder = "Enter your email", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Phone Number", Placeholder = "Enter your phone number", Required = false, Order = 3 },
new() { Name = "company", Type = "text", Label = "Company Name", Placeholder = "Enter your company name", Required = false, Order = 4 }
},
"es" => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Nombre Completo", Placeholder = "Ingresa tu nombre completo", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "Correo Electrónico", Placeholder = "Ingresa tu correo", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Número de Teléfono", Placeholder = "Ingresa tu teléfono", Required = false, Order = 3 },
new() { Name = "company", Type = "text", Label = "Nombre de la Empresa", Placeholder = "Ingresa el nombre de tu empresa", Required = false, Order = 4 }
},
_ => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Nome Completo", Placeholder = "Digite seu nome completo", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "E-mail", Placeholder = "Digite seu e-mail", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Telefone", Placeholder = "Digite seu telefone", Required = false, Order = 3 },
new() { Name = "company", Type = "text", Label = "Nome da Empresa", Placeholder = "Digite o nome da sua empresa", Required = false, Order = 4 }
}
}
};
}
} }
} }

View File

@ -0,0 +1,181 @@
using Microsoft.AspNetCore.Mvc;
using System.Text.RegularExpressions;
using System.Text;
using Polly;
using Polly.Extensions.Http;
namespace OnlyOneAccessTemplate.Controllers
{
[ApiController]
[Route("api/text-conversion")]
public class TextConversionApiController : ControllerBase
{
private readonly ILogger<TextConversionApiController> _logger;
private readonly IAsyncPolicy<string> _retryPolicy;
public TextConversionApiController(ILogger<TextConversionApiController> logger)
{
_logger = logger;
// Configurar política de retry com Polly
_retryPolicy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
_logger.LogWarning("Tentativa {RetryCount} de conversão de texto após {Delay}ms. Erro: {Error}",
retryCount, timespan.TotalMilliseconds, outcome.Message);
})
.AsAsyncPolicy<string>();
}
[HttpPost("sentence-case")]
public async Task<IActionResult> ConvertToSentenceCase([FromBody] TextConversionRequest request)
{
try
{
if (string.IsNullOrEmpty(request.Text))
{
return BadRequest(new { success = false, message = "Texto não pode estar vazio" });
}
// Aplicar política de retry
var result = await _retryPolicy.ExecuteAsync(async () =>
{
return await Task.FromResult(ConvertToSentenceCase(request.Text));
});
_logger.LogInformation("Conversão de texto realizada com sucesso para {Length} caracteres", request.Text.Length);
return Ok(new TextConversionResponse
{
Success = true,
OriginalText = request.Text,
ConvertedText = result,
ProcessedAt = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para sentence case");
return StatusCode(500, new { success = false, message = "Erro interno do servidor" });
}
}
[HttpPost("upper-case")]
public async Task<IActionResult> ConvertToUpperCase([FromBody] TextConversionRequest request)
{
try
{
if (string.IsNullOrEmpty(request.Text))
{
return BadRequest(new { success = false, message = "Texto não pode estar vazio" });
}
var result = await _retryPolicy.ExecuteAsync(async () =>
{
return await Task.FromResult(request.Text.ToUpper());
});
return Ok(new TextConversionResponse
{
Success = true,
OriginalText = request.Text,
ConvertedText = result,
ProcessedAt = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para maiúsculas");
return StatusCode(500, new { success = false, message = "Erro interno do servidor" });
}
}
[HttpPost("lower-case")]
public async Task<IActionResult> ConvertToLowerCase([FromBody] TextConversionRequest request)
{
try
{
if (string.IsNullOrEmpty(request.Text))
{
return BadRequest(new { success = false, message = "Texto não pode estar vazio" });
}
var result = await _retryPolicy.ExecuteAsync(async () =>
{
return await Task.FromResult(request.Text.ToLower());
});
return Ok(new TextConversionResponse
{
Success = true,
OriginalText = request.Text,
ConvertedText = result,
ProcessedAt = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para minúsculas");
return StatusCode(500, new { success = false, message = "Erro interno do servidor" });
}
}
[HttpGet("health")]
public IActionResult HealthCheck()
{
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow });
}
private string ConvertToSentenceCase(string text)
{
if (string.IsNullOrEmpty(text))
return text;
// Converte todo o texto para minúsculas primeiro
string lowerText = text.ToLower();
// StringBuilder para construir o resultado
StringBuilder result = new StringBuilder(lowerText);
// Capitaliza a primeira letra do texto se for uma letra
if (result.Length > 0 && char.IsLetter(result[0]))
{
result[0] = char.ToUpper(result[0]);
}
// Regex para encontrar início de frases/parágrafos
// Procura por pontos, exclamações, interrogações ou quebras de linha
// seguidos por espaços e uma letra
string pattern = @"([.!?\n]\s*)([a-z])";
string resultText = result.ToString();
resultText = Regex.Replace(resultText, pattern, match =>
{
return match.Groups[1].Value + char.ToUpper(match.Groups[2].Value[0]);
});
return resultText;
}
}
public class TextConversionRequest
{
public string Text { get; set; } = string.Empty;
public string? Language { get; set; }
public Dictionary<string, string>? Options { get; set; }
}
public class TextConversionResponse
{
public bool Success { get; set; }
public string OriginalText { get; set; } = string.Empty;
public string ConvertedText { get; set; } = string.Empty;
public DateTime ProcessedAt { get; set; }
public string? ErrorMessage { get; set; }
public Dictionary<string, object>? Metadata { get; set; }
}
}

View File

@ -0,0 +1,30 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM --platform=linux/arm64 mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM --platform=linux/arm64 mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["OnlyOneAccessTemplate/OnlyOneAccessTemplate.csproj", "OnlyOneAccessTemplate/"]
RUN dotnet restore "./OnlyOneAccessTemplate/OnlyOneAccessTemplate.csproj"
COPY . .
WORKDIR "/src/OnlyOneAccessTemplate"
RUN dotnet build "./OnlyOneAccessTemplate.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./OnlyOneAccessTemplate.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# Healthcheck (adicional - opcional mas recomendado)
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["dotnet", "OnlyOneAccessTemplate.dll"]

View File

@ -0,0 +1,31 @@
namespace OnlyOneAccessTemplate.Models
{
public class AdSettings
{
public bool Enabled { get; set; } = true;
public string PublisherID { get; set; } = "";
public bool TestMode { get; set; } = false;
public Dictionary<string, string> Slots { get; set; } = new();
public AdDisplayRules DisplayRules { get; set; } = new();
}
public class AdDisplayRules
{
public int MaxAdsPerPage { get; set; } = 7;
public int MaxAdsAboveFold { get; set; } = 3;
public bool ShowOnMobile { get; set; } = true;
public bool AllowStickyAds { get; set; } = true;
public List<string> DisabledPositions { get; set; } = new();
}
public class AdPerformanceMetrics
{
public string Position { get; set; } = "";
public int Impressions { get; set; }
public int Clicks { get; set; }
public double CTR => Impressions > 0 ? (double)Clicks / Impressions * 100 : 0;
public decimal Revenue { get; set; }
public decimal RPM => Impressions > 0 ? Revenue / Impressions * 1000 : 0;
public DateTime LastUpdated { get; set; } = DateTime.UtcNow;
}
}

View File

@ -11,12 +11,41 @@ namespace OnlyOneAccessTemplate.Models
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
[Required] [Required]
public string Language { get; set; } = "pt"; public string Language { get; set; } = "pt"; // "pt", "en", "es"
public string Currency { get; set; } = "BRL"; // "BRL", "USD", "EUR"
public string CountryCode { get; set; } = "BR"; // "BR", "US", "ES"
// SEO Configuration
public string SiteTitle { get; set; } = string.Empty;
public string SiteDescription { get; set; } = string.Empty;
public string SiteKeywords { get; set; } = string.Empty;
public string OgImage { get; set; } = string.Empty;
// AdSense Configuration
public string AdSenseClientId { get; set; } = string.Empty;
public string AdSenseSlotId { get; set; } = string.Empty;
// Home Page Content
public HomePageContent HomePage { get; set; } = new();
public SeoConfig Seo { get; set; } = new();
public Dictionary<string, PageContent> Pages { get; set; } = new();
public ConversionConfig Conversion { get; set; } = new();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
} }
public class HomePageContent
{
public string MainTitle { get; set; } = string.Empty;
public string MainDescription { get; set; } = string.Empty;
public string CallToAction { get; set; } = string.Empty;
public List<ConverterCard> FeaturedConverters { get; set; } = new();
}
public class ConverterCard
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
}
} }

View File

@ -4,6 +4,8 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>c25bc394-1044-45e1-85e6-111f7f46b232</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -16,7 +18,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="MongoDB.Driver" Version="3.4.0" /> <PackageReference Include="MongoDB.Driver" Version="3.4.0" />
<PackageReference Include="Polly" Version="8.4.0" />
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -35,14 +35,14 @@ builder.Services.AddScoped<ILanguageService, LanguageService>();
builder.Services.AddScoped<ISeoService, SeoService>(); builder.Services.AddScoped<ISeoService, SeoService>();
builder.Services.AddScoped<IConversionService, ConversionService>(); builder.Services.AddScoped<IConversionService, ConversionService>();
// Converter Services - Registrar todos os conversores disponíveis // Converter Services - Registrar todos os conversores dispon<EFBFBD>veis
builder.Services.AddScoped<TextCaseConverterService>(); builder.Services.AddScoped<TextCaseConverterService>();
builder.Services.AddScoped<CsvToJsonConverterService>(); builder.Services.AddScoped<CsvToJsonConverterService>();
builder.Services.AddScoped<ImageToTextConverterService>(); builder.Services.AddScoped<ImageToTextConverterService>();
builder.Services.AddScoped<UpperLowerConversorService>(); // Substituindo UpperLowerConversorService por API com Polly
// Adicione aqui novos conversores conforme necessário: builder.Services.AddHttpClient<ITextConversionApiService, TextConversionApiService>();
// builder.Services.AddScoped<SeuNovoConverterService>(); builder.Services.AddScoped<ApiBasedSentenceConverterService>();
// HttpClient for external calls // HttpClient for external calls
builder.Services.AddHttpClient<ConversionService>(); builder.Services.AddHttpClient<ConversionService>();
@ -96,22 +96,15 @@ app.MapControllerRoute(
pattern: "converter/config/{converterType}", pattern: "converter/config/{converterType}",
defaults: new { controller = "Converter", action = "GetConverterConfig" }); defaults: new { controller = "Converter", action = "GetConverterConfig" });
// Rotas específicas por idioma
app.MapControllerRoute( app.MapControllerRoute(
name: "default-pt", name: "multilingual",
pattern: "{language:regex(en|es)}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"); pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "pt-explicit",
pattern: "pt/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "english",
pattern: "en/{controller=En}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "spanish",
pattern: "es/{controller=Es}/{action=Index}/{id?}");
// SEO Routes // SEO Routes
app.MapGet("/sitemap.xml", async (ISiteConfigurationService siteConfig) => app.MapGet("/sitemap.xml", async (ISiteConfigurationService siteConfig) =>
{ {
@ -137,21 +130,21 @@ if (app.Environment.IsDevelopment())
try try
{ {
// Verificar se já existem configurações // Verificar se j<EFBFBD> existem configura<72><61>es
var languages = new[] { "pt", "en", "es" }; var languages = new[] { "pt", "en", "es" };
foreach (var lang in languages) foreach (var lang in languages)
{ {
var configExists = await siteConfigService.ConfigurationExistsAsync(lang); var configExists = await siteConfigService.ConfigurationExistsAsync(lang);
if (!configExists) if (!configExists)
{ {
logger.LogInformation("Criando configuração padrão para idioma: {Language}", lang); logger.LogInformation("Criando configura<EFBFBD><EFBFBD>o padr<64>o para idioma: {Language}", lang);
_ = await siteConfigService.GetConfigurationAsync(lang); // Isso criará a configuração padrão _ = await siteConfigService.GetConfigurationAsync(lang); // Isso criar<EFBFBD> a configura<72><61>o padr<64>o
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Erro ao inicializar dados padrão"); logger.LogError(ex, "Erro ao inicializar dados padr<EFBFBD>o");
} }
} }

View File

@ -1,4 +1,40 @@
{ {
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5201"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7299;http://localhost:5201"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json", "$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": { "iisSettings": {
"windowsAuthentication": false, "windowsAuthentication": false,
@ -7,32 +43,5 @@
"applicationUrl": "http://localhost:65221", "applicationUrl": "http://localhost:65221",
"sslPort": 0 "sslPort": 0
} }
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5201",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7299;http://localhost:5201",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
} }
} }

View File

@ -0,0 +1,104 @@
using Microsoft.Extensions.Caching.Memory;
namespace OnlyOneAccessTemplate.Services
{
public class AdsService : IAdsService
{
private readonly IConfiguration _configuration;
private readonly IMemoryCache _cache;
private readonly ILogger<AdsService> _logger;
public AdsService(IConfiguration configuration, IMemoryCache cache, ILogger<AdsService> logger)
{
_configuration = configuration;
_cache = cache;
_logger = logger;
}
public bool IsAdsEnabled()
{
return _configuration.GetValue<bool>("GoogleAds:Enabled", true);
}
public string GetPublisherID()
{
return _configuration.GetValue<string>("GoogleAds:PublisherID") ?? "";
}
public AdConfiguration GetAdConfiguration(string adPosition)
{
var configurations = GetAllAdConfigurations();
return configurations.FirstOrDefault(c => c.Position.Equals(adPosition, StringComparison.OrdinalIgnoreCase))
?? new AdConfiguration(adPosition, "default-slot", AdSize.Responsive);
}
public string GenerateAdHtml(string position, string adSlot, AdSize size = AdSize.Responsive)
{
if (!IsAdsEnabled() || string.IsNullOrEmpty(GetPublisherID()))
return "";
var sizeAttributes = GetSizeAttributes(size);
var formatAttribute = GetFormatAttribute(size);
return $@"
<ins class=""adsbygoogle""
style=""display:block{sizeAttributes.Style}""
data-ad-client=""{GetPublisherID()}""
data-ad-slot=""{adSlot}""
{formatAttribute}
{(size == AdSize.Responsive ? "data-full-width-responsive=\"true\"" : "")}></ins>";
}
private List<AdConfiguration> GetAllAdConfigurations()
{
return _cache.GetOrCreate("ad_configurations", factory =>
{
factory.SetSlidingExpiration(TimeSpan.FromHours(1));
return new List<AdConfiguration>
{
new("banner-top", GetSlotId("BannerTop"), AdSize.Banner_728x90, Priority: 10),
new("sidebar-left", GetSlotId("SidebarLeft"), AdSize.Sidebar_300x600, IsSticky: true, ShowOnMobile: false, Priority: 8),
new("rectangle-pre-converter", GetSlotId("RectanglePre"), AdSize.Rectangle_300x250, Priority: 9),
new("rectangle-post-converter", GetSlotId("RectanglePost"), AdSize.Rectangle_300x250, Priority: 7),
new("sidebar-right", GetSlotId("SidebarRight"), AdSize.Sidebar_300x600, IsSticky: true, ShowOnMobile: false, Priority: 6),
new("mobile-bottom", GetSlotId("MobileBottom"), AdSize.Mobile_320x50, ShowOnDesktop: false, Priority: 5),
new("in-feed", GetSlotId("InFeed"), AdSize.Responsive, Priority: 4),
new("multiplex", GetSlotId("Multiplex"), AdSize.Responsive, Priority: 3)
};
});
}
private string GetSlotId(string configKey)
{
return _configuration.GetValue<string>($"GoogleAds:Slots:{configKey}") ?? "default-slot";
}
private (string Style, string Width, string Height) GetSizeAttributes(AdSize size)
{
return size switch
{
AdSize.Banner_728x90 => ("; width: 728px; height: 90px;", "728", "90"),
AdSize.Rectangle_300x250 => ("; width: 300px; height: 250px;", "300", "250"),
AdSize.Sidebar_160x600 => ("; width: 160px; height: 600px;", "160", "600"),
AdSize.Sidebar_300x600 => ("; width: 300px; height: 600px;", "300", "600"),
AdSize.Mobile_320x50 => ("; width: 320px; height: 50px;", "320", "50"),
AdSize.Square_250x250 => ("; width: 250px; height: 250px;", "250", "250"),
_ => ("", "auto", "auto")
};
}
private string GetFormatAttribute(AdSize size)
{
return size switch
{
AdSize.Responsive => "data-ad-format=\"auto\"",
AdSize.Banner_728x90 => "data-ad-format=\"banner\"",
AdSize.Rectangle_300x250 => "data-ad-format=\"rectangle\"",
AdSize.Sidebar_160x600 or AdSize.Sidebar_300x600 => "data-ad-format=\"vertical\"",
AdSize.Square_250x250 => "data-ad-format=\"square\"",
_ => "data-ad-format=\"auto\""
};
}
}
}

View File

@ -65,16 +65,16 @@ namespace OnlyOneAccessTemplate.Services
}); });
// Enviar para webhook se configurado // Enviar para webhook se configurado
if (!string.IsNullOrEmpty(configuration.Conversion.WebhookUrl)) //if (!string.IsNullOrEmpty(configuration..Conversion.WebhookUrl))
{ //{
_ = Task.Run(() => SendWebhookAsync(configuration.Conversion.WebhookUrl, conversionRecord)); // _ = Task.Run(() => SendWebhookAsync(configuration.Conversion.WebhookUrl, conversionRecord));
} //}
// Disparar pixel de conversão se configurado //// Disparar pixel de conversão se configurado
if (!string.IsNullOrEmpty(configuration.Conversion.ConversionPixel)) //if (!string.IsNullOrEmpty(configuration.Conversion.ConversionPixel))
{ //{
_ = Task.Run(() => FireConversionPixelAsync(configuration.Conversion.ConversionPixel, conversionRecord)); // _ = Task.Run(() => FireConversionPixelAsync(configuration.Conversion.ConversionPixel, conversionRecord));
} //}
_logger.LogInformation("Conversão processada com sucesso: {ConversionId}", conversionRecord.Id); _logger.LogInformation("Conversão processada com sucesso: {ConversionId}", conversionRecord.Id);
return true; return true;

View File

@ -6,7 +6,6 @@ namespace OnlyOneAccessTemplate.Services
{ {
Task<SiteConfiguration> GetConfigurationAsync(string language); Task<SiteConfiguration> GetConfigurationAsync(string language);
SiteConfiguration GetConfiguration(string language); // Versão síncrona para o BaseController SiteConfiguration GetConfiguration(string language); // Versão síncrona para o BaseController
Task<PageContent?> GetPageContentAsync(string language, string pageName);
Task<string> GenerateSitemapAsync(); Task<string> GenerateSitemapAsync();
Task<string> GenerateRobotsAsync(); Task<string> GenerateRobotsAsync();
Task UpdateConfigurationAsync(SiteConfiguration config); Task UpdateConfigurationAsync(SiteConfiguration config);
@ -104,6 +103,36 @@ namespace OnlyOneAccessTemplate.Services
Task<List<string>> GetImagesAsync(string folder = "uploads"); Task<List<string>> GetImagesAsync(string folder = "uploads");
bool IsValidImageFile(IFormFile file); bool IsValidImageFile(IFormFile file);
} }
public interface IAdsService
{
bool IsAdsEnabled();
string GetPublisherID();
AdConfiguration GetAdConfiguration(string adPosition);
string GenerateAdHtml(string position, string adSlot, AdSize size = AdSize.Responsive);
}
public enum AdSize
{
Responsive,
Banner_728x90,
Rectangle_300x250,
Sidebar_160x600,
Sidebar_300x600,
Mobile_320x50,
Square_250x250
}
public record AdConfiguration(
string Position,
string SlotId,
AdSize Size,
bool IsSticky = false,
bool ShowOnMobile = true,
bool ShowOnDesktop = true,
int Priority = 1
);
// DTOs e Records // DTOs e Records
public record ConversionData( public record ConversionData(
string Language, string Language,

View File

@ -42,19 +42,19 @@ namespace OnlyOneAccessTemplate.Services
try try
{ {
var configuration = await _siteConfig.GetConfigurationAsync(language); var configuration = await _siteConfig.GetConfigurationAsync(language);
content = content ?? await _siteConfig.GetPageContentAsync(language, pageName); //content = content ?? await _siteConfig.ge.GetPageContentAsync(language, pageName);
var baseUrl = _configuration.GetValue<string>("SEO:DefaultDomain") ?? "https://localhost"; var baseUrl = _configuration.GetValue<string>("SEO:DefaultDomain") ?? "https://localhost";
var canonicalUrl = BuildCanonicalUrl(baseUrl, language, pageName); var canonicalUrl = BuildCanonicalUrl(baseUrl, language, pageName);
var hreflangUrls = _languageService.GetHreflangUrls(canonicalUrl, language); var hreflangUrls = _languageService.GetHreflangUrls(canonicalUrl, language);
var metadata = new SeoMetadata( var metadata = new SeoMetadata(
Title: content?.MetaTitle ?? content?.Title ?? configuration.Seo.DefaultTitle, Title: content?.MetaTitle ?? content?.Title ?? configuration.SiteTitle,
Description: content?.Description ?? configuration.Seo.DefaultDescription, Description: content?.Description ?? configuration.SiteDescription,
Keywords: content?.Keywords ?? configuration.Seo.DefaultKeywords, Keywords: content?.Keywords ?? configuration.SiteKeywords,
OgTitle: content?.Title ?? configuration.Seo.DefaultTitle, OgTitle: content?.Title ?? configuration.SiteTitle,
OgDescription: content?.Description ?? configuration.Seo.DefaultDescription, OgDescription: content?.Description ?? configuration.SiteDescription,
OgImage: content?.OgImage ?? configuration.Seo.OgImage, OgImage: content?.OgImage ?? configuration.OgImage,
CanonicalUrl: canonicalUrl, CanonicalUrl: canonicalUrl,
HreflangUrls: hreflangUrls, HreflangUrls: hreflangUrls,
StructuredData: await GenerateStructuredDataAsync(language, "WebPage", new { content, configuration }) StructuredData: await GenerateStructuredDataAsync(language, "WebPage", new { content, configuration })
@ -138,14 +138,14 @@ namespace OnlyOneAccessTemplate.Services
{ {
Context = "https://schema.org", Context = "https://schema.org",
Type = "WebPage", Type = "WebPage",
Name = config.Seo.SiteName, Name = config.SiteTitle,
Description = config.Seo.DefaultDescription, Description = config.SiteDescription,
Url = baseUrl, Url = baseUrl,
InLanguage = language, InLanguage = language,
IsPartOf = new IsPartOf = new
{ {
Type = "WebSite", Type = "WebSite",
Name = config.Seo.SiteName, Name = config.SiteTitle,
Url = baseUrl Url = baseUrl
}, },
DatePublished = DateTime.UtcNow.ToString("yyyy-MM-dd"), DatePublished = DateTime.UtcNow.ToString("yyyy-MM-dd"),
@ -239,7 +239,7 @@ namespace OnlyOneAccessTemplate.Services
{ {
Context = "https://schema.org", Context = "https://schema.org",
Type = "Organization", Type = "Organization",
Name = config.Seo.SiteName, Name = config.SiteTitle,
Url = baseUrl, Url = baseUrl,
Logo = new Logo = new
{ {
@ -312,7 +312,7 @@ namespace OnlyOneAccessTemplate.Services
Brand = new Brand = new
{ {
Type = "Brand", Type = "Brand",
Name = config.Seo.SiteName Name = config.SiteTitle
}, },
Offers = new Offers = new
{ {

View File

@ -85,17 +85,6 @@ namespace OnlyOneAccessTemplate.Services
} }
} }
public async Task<PageContent?> GetPageContentAsync(string language, string pageName)
{
var configuration = await GetConfigurationAsync(language);
if (configuration.Pages.TryGetValue(pageName.ToLowerInvariant(), out var pageContent))
{
return pageContent;
}
return null;
}
public async Task<string> GenerateSitemapAsync() public async Task<string> GenerateSitemapAsync()
{ {
@ -215,15 +204,7 @@ namespace OnlyOneAccessTemplate.Services
private async Task<SiteConfiguration> CreateDefaultConfigurationAsync(string language) private async Task<SiteConfiguration> CreateDefaultConfigurationAsync(string language)
{ {
var defaultConfig = new SiteConfiguration var defaultConfig = CreateDefaultConfiguration(language);
{
Language = language,
Seo = CreateDefaultSeoConfig(language),
Pages = CreateDefaultPages(language),
Conversion = CreateDefaultConversionConfig(language),
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
try try
{ {
@ -240,17 +221,54 @@ namespace OnlyOneAccessTemplate.Services
private SiteConfiguration CreateDefaultConfiguration(string language) private SiteConfiguration CreateDefaultConfiguration(string language)
{ {
var defaultConfig = new SiteConfiguration return language switch
{ {
Language = language, "en" => new SiteConfiguration
Seo = CreateDefaultSeoConfig(language), {
Pages = CreateDefaultPages(language), Language = "en",
Conversion = CreateDefaultConversionConfig(language), Currency = "USD",
CreatedAt = DateTime.UtcNow, CountryCode = "US",
UpdatedAt = DateTime.UtcNow SiteTitle = "Convert-it Online - Free Converters",
SiteDescription = "Convert your files online for free. PDF to Word, images, text and much more.",
SiteKeywords = "convert, converter, pdf, word, text, online, free",
HomePage = new HomePageContent
{
MainTitle = "Free Online Converters",
MainDescription = "Convert your files quickly and securely",
CallToAction = "Choose your converter"
}
},
"es" => new SiteConfiguration
{
Language = "es",
Currency = "EUR",
CountryCode = "ES",
SiteTitle = "Convert-it Online - Convertidores Gratuitos",
SiteDescription = "Convierte tus archivos en línea gratis. PDF a Word, imágenes, texto y mucho más.",
SiteKeywords = "convertir, convertidor, pdf, word, texto, en línea, gratis",
HomePage = new HomePageContent
{
MainTitle = "Convertidores en Línea Gratuitos",
MainDescription = "Convierte tus archivos rápidamente y con seguridad",
CallToAction = "Elige tu convertidor"
}
},
_ => new SiteConfiguration
{
Language = "pt",
Currency = "BRL",
CountryCode = "BR",
SiteTitle = "Convert-it Online - Conversores Gratuitos",
SiteDescription = "Converta seus arquivos online gratuitamente. PDF para Word, imagens, textos e muito mais.",
SiteKeywords = "converter, conversor, pdf, word, texto, online, grátis",
HomePage = new HomePageContent
{
MainTitle = "Conversores Online Gratuitos",
MainDescription = "Converta seus arquivos rapidamente e com segurança",
CallToAction = "Escolha seu conversor"
}
}
}; };
return defaultConfig;
} }
public async Task<bool> ConfigurationExistsAsync(string language) public async Task<bool> ConfigurationExistsAsync(string language)
@ -288,140 +306,6 @@ namespace OnlyOneAccessTemplate.Services
} }
} }
private SeoConfig CreateDefaultSeoConfig(string language)
{
var defaultSiteName = _config.GetValue<string>("SEO:DefaultSiteName") ?? "Site de Conversão";
return language switch
{
"en" => new SeoConfig
{
SiteName = defaultSiteName,
DefaultTitle = "Conversion Site - Transform Your Business",
DefaultDescription = "Professional conversion solutions to boost your business results",
DefaultKeywords = "conversion, business, marketing, results",
TwitterCard = "summary_large_image",
GoogleTagManagerId = _config.GetValue<string>("SEO:GoogleTagManagerId") ?? "",
GoogleAnalyticsId = _config.GetValue<string>("SEO:GoogleAnalyticsId") ?? "",
Author = "Conversion Site Team"
},
"es" => new SeoConfig
{
SiteName = defaultSiteName,
DefaultTitle = "Sitio de Conversión - Transforma Tu Negocio",
DefaultDescription = "Soluciones profesionales de conversión para potenciar los resultados de tu negocio",
DefaultKeywords = "conversión, negocio, marketing, resultados",
TwitterCard = "summary_large_image",
GoogleTagManagerId = _config.GetValue<string>("SEO:GoogleTagManagerId") ?? "",
GoogleAnalyticsId = _config.GetValue<string>("SEO:GoogleAnalyticsId") ?? "",
Author = "Equipo Sitio de Conversión"
},
_ => new SeoConfig
{
SiteName = defaultSiteName,
DefaultTitle = "Site de Conversão - Transforme Seu Negócio",
DefaultDescription = "Soluções profissionais de conversão para alavancar os resultados do seu negócio",
DefaultKeywords = "conversão, negócio, marketing, resultados",
TwitterCard = "summary_large_image",
GoogleTagManagerId = _config.GetValue<string>("SEO:GoogleTagManagerId") ?? "",
GoogleAnalyticsId = _config.GetValue<string>("SEO:GoogleAnalyticsId") ?? "",
Author = "Equipe Site de Conversão"
}
};
}
private Dictionary<string, PageContent> CreateDefaultPages(string language)
{
return language switch
{
"en" => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Home - Transform Your Business",
Description = "Professional conversion solutions to boost your business results",
H1 = "Transform Your Business Today",
MetaTitle = "Business Transformation | Conversion Solutions"
},
["about"] = new PageContent
{
Title = "About Us - Our Mission",
Description = "Learn about our mission to help businesses achieve better conversion rates",
H1 = "About Our Company",
MetaTitle = "About Us | Conversion Experts"
}
},
"es" => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Inicio - Transforma Tu Negocio",
Description = "Soluciones profesionales de conversión para potenciar los resultados de tu negocio",
H1 = "Transforma Tu Negocio Hoy",
MetaTitle = "Transformación Empresarial | Soluciones de Conversión"
},
["about"] = new PageContent
{
Title = "Acerca de Nosotros - Nuestra Misión",
Description = "Conoce nuestra misión de ayudar a las empresas a lograr mejores tasas de conversión",
H1 = "Acerca de Nuestra Empresa",
MetaTitle = "Acerca de Nosotros | Expertos en Conversión"
}
},
_ => new Dictionary<string, PageContent>
{
["index"] = new PageContent
{
Title = "Início - Transforme Seu Negócio",
Description = "Soluções profissionais de conversão para alavancar os resultados do seu negócio",
H1 = "Transforme Seu Negócio Hoje",
MetaTitle = "Transformação Empresarial | Soluções de Conversão"
},
["about"] = new PageContent
{
Title = "Sobre Nós - Nossa Missão",
Description = "Conheça nossa missão de ajudar empresas a alcançar melhores taxas de conversão",
H1 = "Sobre Nossa Empresa",
MetaTitle = "Sobre Nós | Especialistas em Conversão"
}
}
};
}
private ConversionConfig CreateDefaultConversionConfig(string language)
{
return new ConversionConfig
{
FormAction = "/conversion/submit",
ThankYouPage = language switch
{
"en" => "/thank-you",
"es" => "/gracias",
_ => "/obrigado"
},
FormFields = language switch
{
"en" => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Full Name", Placeholder = "Enter your full name", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "Email", Placeholder = "Enter your email", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Phone", Placeholder = "Enter your phone number", Required = false, Order = 3 }
},
"es" => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Nombre Completo", Placeholder = "Ingresa tu nombre completo", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "Correo Electrónico", Placeholder = "Ingresa tu correo", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Teléfono", Placeholder = "Ingresa tu número de teléfono", Required = false, Order = 3 }
},
_ => new List<FormField>
{
new() { Name = "name", Type = "text", Label = "Nome Completo", Placeholder = "Digite seu nome completo", Required = true, Order = 1 },
new() { Name = "email", Type = "email", Label = "E-mail", Placeholder = "Digite seu e-mail", Required = true, Order = 2 },
new() { Name = "phone", Type = "tel", Label = "Telefone", Placeholder = "Digite seu telefone", Required = false, Order = 3 }
}
}
};
}
private string BuildUrl(string baseUrl, string language, string page) private string BuildUrl(string baseUrl, string language, string page)
{ {

View File

@ -0,0 +1,207 @@
using OnlyOneAccessTemplate.Models;
using OnlyOneAccessTemplate.Services.OnlyOneAccessTemplate.Services;
using Polly;
using Polly.Extensions.Http;
using System.Text;
using System.Text.Json;
namespace OnlyOneAccessTemplate.Services
{
public interface ITextConversionApiService
{
Task<string> ConvertToSentenceCaseAsync(string text);
Task<string> ConvertToUpperCaseAsync(string text);
Task<string> ConvertToLowerCaseAsync(string text);
Task<bool> IsHealthyAsync();
}
public class TextConversionApiService : ITextConversionApiService
{
private readonly HttpClient _httpClient;
private readonly ILogger<TextConversionApiService> _logger;
private readonly IAsyncPolicy<HttpResponseMessage> _retryPolicy;
private readonly string _baseUrl;
public TextConversionApiService(
HttpClient httpClient,
ILogger<TextConversionApiService> logger,
IConfiguration configuration)
{
_httpClient = httpClient;
_logger = logger;
_baseUrl = configuration.GetValue<string>("TextConversionApi:BaseUrl") ?? "https://localhost:7071";
// Configurar política de retry com Polly para HttpClient
_retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => !msg.IsSuccessStatusCode)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
_logger.LogWarning("Tentativa {RetryCount} de chamada à API após {Delay}ms. Status: {StatusCode}",
retryCount, timespan.TotalMilliseconds, outcome.Result?.StatusCode);
});
// Configurar timeout
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
public async Task<string> ConvertToSentenceCaseAsync(string text)
{
try
{
var request = new { Text = text };
var response = await CallApiAsync("/api/text-conversion/sentence-case", request);
return response.ConvertedText;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para sentence case via API");
throw;
}
}
public async Task<string> ConvertToUpperCaseAsync(string text)
{
try
{
var request = new { Text = text };
var response = await CallApiAsync("/api/text-conversion/upper-case", request);
return response.ConvertedText;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para maiúsculas via API");
throw;
}
}
public async Task<string> ConvertToLowerCaseAsync(string text)
{
try
{
var request = new { Text = text };
var response = await CallApiAsync("/api/text-conversion/lower-case", request);
return response.ConvertedText;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao converter texto para minúsculas via API");
throw;
}
}
public async Task<bool> IsHealthyAsync()
{
try
{
var response = await _retryPolicy.ExecuteAsync(async () =>
{
return await _httpClient.GetAsync($"{_baseUrl}/api/text-conversion/health");
});
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao verificar saúde da API de conversão de texto");
return false;
}
}
private async Task<TextConversionApiResponse> CallApiAsync(string endpoint, object request)
{
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _retryPolicy.ExecuteAsync(async () =>
{
return await _httpClient.PostAsync($"{_baseUrl}{endpoint}", content);
});
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new HttpRequestException($"API call failed with status {response.StatusCode}: {errorContent}");
}
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<TextConversionApiResponse>(responseContent, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (result == null || !result.Success)
{
throw new InvalidOperationException($"API returned unsuccessful response: {result?.ErrorMessage}");
}
return result;
}
}
public class TextConversionApiResponse
{
public bool Success { get; set; }
public string OriginalText { get; set; } = string.Empty;
public string ConvertedText { get; set; } = string.Empty;
public DateTime ProcessedAt { get; set; }
public string? ErrorMessage { get; set; }
public Dictionary<string, object>? Metadata { get; set; }
}
// Novo serviço que substitui o UpperLowerConversorService usando a API
public class ApiBasedSentenceConverterService : BaseConverterService
{
private readonly ITextConversionApiService _apiService;
public override string ConverterType => "text-case-sentence";
public override string ConverterName => "Maiúsculas para minúsculas";
public ApiBasedSentenceConverterService(
ILogger<ApiBasedSentenceConverterService> logger,
IConfiguration configuration,
ITextConversionApiService apiService)
: base(logger, configuration)
{
_apiService = apiService;
}
public override async Task<ConversionResult> ConvertAsync(ConversionRequest request)
{
try
{
var resultado = await _apiService.ConvertToSentenceCaseAsync(request.TextInput);
return new ConversionResult(true, OutputText: resultado);
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro na conversão via API");
return new ConversionResult(false, ErrorMessage: "Erro ao processar via API");
}
}
public override ConverterConfiguration GetConfiguration(string language)
{
var texts = GetLocalizedTexts(language);
texts["ConverterTitle"] = language switch
{
"en" => "Sentence Case Convert",
"es" => "Convertir a Mayúscula Inicial",
_ => "Converter para primeira maiúscula"
};
return new ConverterConfiguration
{
ConverterType = "text",
OutputType = "text",
HasAdvancedOptions = false,
AllowShare = true,
LocalizedTexts = texts
};
}
}
}

View File

@ -1,85 +0,0 @@
using OnlyOneAccessTemplate.Services.OnlyOneAccessTemplate.Services;
using System.Text.RegularExpressions;
using System.Text;
namespace OnlyOneAccessTemplate.Services
{
public class UpperLowerConversorService : BaseConverterService
{
public override string ConverterType => "text-case-sentence";
public override string ConverterName => "Maiúsculas para minúsculas";
public UpperLowerConversorService(ILogger<UpperLowerConversorService> logger, IConfiguration configuration)
: base(logger, configuration) { }
public override async Task<ConversionResult> ConvertAsync(ConversionRequest request)
{
try
{
// Sua lógica de conversão aqui
var resultado = ConvertToSentenceCase(request.TextInput);
return new ConversionResult(true, OutputText: resultado);
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro na conversão");
return new ConversionResult(false, ErrorMessage: "Erro ao processar");
}
}
private string ConvertToSentenceCase(string text)
{
if (string.IsNullOrEmpty(text))
return text;
// Converte todo o texto para minúsculas primeiro
string lowerText = text.ToLower();
// StringBuilder para construir o resultado
StringBuilder result = new StringBuilder(lowerText);
// Capitaliza a primeira letra do texto se for uma letra
if (result.Length > 0 && char.IsLetter(result[0]))
{
result[0] = char.ToUpper(result[0]);
}
// Regex para encontrar início de frases/parágrafos
// Procura por pontos, exclamações, interrogações ou quebras de linha
// seguidos por espaços e uma letra
string pattern = @"([.!?\n]\s*)([a-z])";
string resultText = result.ToString();
resultText = Regex.Replace(resultText, pattern, match =>
{
return match.Groups[1].Value + char.ToUpper(match.Groups[2].Value[0]);
});
return resultText;
}
public override ConverterConfiguration GetConfiguration(string language)
{
var texts = GetLocalizedTexts(language);
// Personalize os textos para seu conversor
texts["ConverterTitle"] = language switch
{
"en" => "Sentence Case Convert",
"es" => "TÍTULO DE TU CONVERTIDOR",
_ => "Converter para primeira maiúscula"
};
return new ConverterConfiguration
{
ConverterType = "text", // ou "file" ou "url"
OutputType = "text", // ou "download" ou "preview"
HasAdvancedOptions = false,
AllowShare = true,
LocalizedTexts = texts
};
}
}
}

View File

@ -1,242 +1,306 @@
@model PageContent; @model OnlyOneAccessTemplate.Models.SiteConfiguration
@{ @{
ViewData["Title"] = ViewBag.Title ?? "Conversor Online"; ViewData["Title"] = ViewBag.Title ?? Model.SiteTitle;
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
<!-- Seção Principal do Conversor --> <!-- Google AdSense - Script Global -->
<section class="converter-hero bg-light py-5"> @section Head {
<div class="container"> @if (ViewBag.GoogleAdsEnabled == true && !string.IsNullOrEmpty(ViewBag.GoogleAdsPublisher))
<div class="row justify-content-center"> {
<div class="col-lg-10"> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@ViewBag.GoogleAdsPublisher"
<!-- Título e Descrição --> crossorigin="anonymous"></script>
<div class="text-center mb-5"> }
<h1 class="display-4 fw-bold text-primary mb-3">
@(ViewBag.ConverterTitle ?? "CONVERSOR ONLINE")
</h1>
<p class="lead text-muted mb-4">
@(ViewBag.ConverterDescription ?? "Converta seus arquivos de forma rápida e segura")
</p>
</div>
<!-- Card do Conversor --> <style>
<div class="card shadow-lg border-0 rounded-3"> .ad-container { margin: 15px 0; text-align: center; min-height: 50px; }
<div class="card-body p-5"> .ad-banner { min-height: 90px; }
<!-- Steps do Processo --> .ad-rectangle { min-height: 250px; }
<div class="row text-center mb-4"> .ad-sidebar { min-height: 600px; }
<div class="col-md-4 mb-3"> .ad-sticky { position: sticky; top: 20px; z-index: 100; }
<div class="step-indicator"> .converter-section { background: #f8f9fa; }
<span class="badge bg-primary rounded-circle p-3 fs-5 mb-2">1</span>
<h6 class="fw-bold">@(ViewBag.Step1Title ?? "Upload")</h6>
<small class="text-muted">@(ViewBag.Step1Description ?? "Selecione seu arquivo")</small>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="step-indicator">
<span class="badge bg-primary rounded-circle p-3 fs-5 mb-2">2</span>
<h6 class="fw-bold">@(ViewBag.Step2Title ?? "Processar")</h6>
<small class="text-muted">@(ViewBag.Step2Description ?? "Aguarde o processamento")</small>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="step-indicator">
<span class="badge bg-success rounded-circle p-3 fs-5 mb-2">3</span>
<h6 class="fw-bold">@(ViewBag.Step3Title ?? "Download")</h6>
<small class="text-muted">@(ViewBag.Step3Description ?? "Baixe o resultado")</small>
</div>
</div>
</div>
<!-- Área do Conversor --> @@media (max-width: 768px) {
<div id="converter-container"> .ad-sidebar { display: none !important; }
@await Html.PartialAsync("_ConverterWidget") .ad-rectangle { min-height: 200px; }
</div> .main-content { padding: 0 10px; }
}
<!-- Informações Adicionais --> @@media (min-width: 1200px) {
<div class="row mt-4"> .ad-sidebar { min-height: 600px; }
<div class="col-md-6"> }
<div class="d-flex align-items-center mb-2"> </style>
<i class="fas fa-shield-alt text-success me-2"></i> }
<small class="text-muted">@(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-file-alt text-primary me-2"></i>
<small class="text-muted">@(ViewBag.FileInfoText ?? "Processamento rápido e seguro")</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Seção de Benefícios --> <!-- Banner Superior -->
<section class="benefits-section py-5"> @{
<div class="container"> ViewBag.AdPosition = "banner-top";
<div class="row"> ViewBag.AdSlotId = ViewBag.AdSlots?.BannerTop ?? "1234567890";
<div class="col-lg-8 mx-auto text-center mb-5"> ViewBag.AdFormat = "auto";
<h2 class="h3 fw-bold mb-3">@(ViewBag.BenefitsTitle ?? "Por Que Usar Nossa Ferramenta?")</h2> ViewBag.AdCssClass = "ad-container ad-banner";
<p class="text-muted">@(ViewBag.BenefitsSubtitle ?? "Descubra os benefícios de nossa solução")</p> ViewBag.ShowOnMobile = true;
</div> ViewBag.ShowOnDesktop = true;
</div> }
<div class="row g-4"> @await Html.PartialAsync("_AdUnit")
<div class="container-fluid">
<div class="row">
<!-- Sidebar Esquerda com Anúncios -->
<div class="col-xl-2 col-lg-2 d-none d-lg-block">
@{ @{
bool hasFeatureBlocks = false; ViewBag.AdPosition = "sidebar-left";
if (Model != null && Model.Blocks != null) ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarLeft ?? "2345678901";
{ ViewBag.AdFormat = "vertical";
var featureBlocks = Model.Blocks.Where(b => b.Type == "features").ToList(); ViewBag.AdSize = "; width: 300px; height: 600px;";
if (featureBlocks.Any()) ViewBag.AdCssClass = "ad-container ad-sidebar";
{ ViewBag.IsSticky = true;
hasFeatureBlocks = true; ViewBag.ShowOnMobile = false;
var firstFeatureBlock = featureBlocks.First(); ViewBag.ShowOnDesktop = true;
if (firstFeatureBlock.Properties != null && firstFeatureBlock.Properties.ContainsKey("feature_list")) }
{ @await Html.PartialAsync("_AdUnit")
var featureList = firstFeatureBlock.Properties["feature_list"] as System.Collections.IEnumerable; </div>
if (featureList != null)
{ <!-- Conteúdo Principal -->
foreach (var featureObj in featureList) <div class="col-xl-8 col-lg-8 col-md-12 main-content">
{ <!-- Seção Principal do Conversor -->
var featureDict = featureObj as Dictionary<string, object>; <section class="converter-section py-4">
if (featureDict != null) <div class="container">
{ <div class="row justify-content-center">
<div class="col-md-6 col-lg-3"> <div class="col-lg-11">
<div class="text-center p-3"> <!-- Título e Descrição -->
<div class="feature-icon mb-3"> <div class="text-center mb-4">
<i class="@(featureDict.GetValueOrDefault("icon", "fas fa-star")) fa-2x text-primary"></i> <h1 class="display-4 fw-bold text-gradient mb-3">
</div> @(ViewBag.ConverterTitle ?? "CONVERSOR ONLINE")
<h6 class="fw-bold">@(featureDict.GetValueOrDefault("title", "Funcionalidade"))</h6> </h1>
<small class="text-muted">@(featureDict.GetValueOrDefault("description", "Descrição da funcionalidade"))</small> <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 -->
@{
ViewBag.AdPosition = "rectangle-pre-converter";
ViewBag.AdSlotId = ViewBag.AdSlots?.RectanglePre ?? "3456789012";
ViewBag.AdFormat = "rectangle";
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> </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>
</section>
<!-- Anúncio In-Feed Entre Seções -->
@{
ViewBag.AdPosition = "in-feed";
ViewBag.AdSlotId = ViewBag.AdSlots?.InFeed ?? "5678901234";
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>
</section>
<!-- Anúncio Multiplex -->
@{
ViewBag.AdPosition = "multiplex";
ViewBag.AdSlotId = ViewBag.AdSlots?.Multiplex ?? "6789012345";
ViewBag.AdFormat = "autorelaxed";
ViewBag.AdCssClass = "ad-container";
ViewBag.IsSticky = false;
ViewBag.ShowOnMobile = true;
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>
</section>
</div>
<!-- Sidebar Direita com Anúncios -->
<div class="col-xl-2 col-lg-2 d-none d-lg-block">
@{
ViewBag.AdPosition = "sidebar-right";
ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarRight ?? "7890123456";
ViewBag.AdFormat = "vertical";
ViewBag.AdSize = "; width: 300px; height: 600px;";
ViewBag.AdCssClass = "ad-container ad-sidebar";
ViewBag.IsSticky = true;
ViewBag.ShowOnMobile = false;
ViewBag.ShowOnDesktop = true;
}
@await Html.PartialAsync("_AdUnit")
<!-- Anúncio Quadrado Adicional na Sidebar -->
<div class="mt-4">
@{
ViewBag.AdPosition = "sidebar-square";
ViewBag.AdSlotId = ViewBag.AdSlots?.SidebarSquare ?? "8901234567";
ViewBag.AdFormat = "square";
ViewBag.AdSize = "; width: 250px; height: 250px;";
ViewBag.AdCssClass = "ad-container";
ViewBag.IsSticky = false;
ViewBag.ShowOnMobile = false;
ViewBag.ShowOnDesktop = true;
} }
} @await Html.PartialAsync("_AdUnit")
</div>
@if (!hasFeatureBlocks)
{
<!-- Features padrão se não houver dados do modelo -->
<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-primary"></i>
</div>
<h6 class="fw-bold">@(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-primary"></i>
</div>
<h6 class="fw-bold">@(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-primary"></i>
</div>
<h6 class="fw-bold">@(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-primary"></i>
</div>
<h6 class="fw-bold">Rápido</h6>
<small class="text-muted">Conversão em segundos</small>
</div>
</div>
}
</div> </div>
</div> </div>
</section> </div>
<!-- Seção de Depoimentos (Opcional) --> <!-- Banner Inferior Mobile -->
@{ @{
bool hasTestimonialBlocks = false; ViewBag.AdPosition = "mobile-bottom";
if (Model != null && Model.Blocks != null) ViewBag.AdSlotId = ViewBag.AdSlots?.MobileBottom ?? "9012345678";
{ ViewBag.AdFormat = "banner";
var testimonialBlocks = Model.Blocks.Where(b => b.Type == "testimonials").ToList(); ViewBag.AdSize = "; width: 320px; height: 50px;";
if (testimonialBlocks.Any()) ViewBag.AdCssClass = "fixed-bottom d-block d-md-none";
{ ViewBag.IsSticky = false;
hasTestimonialBlocks = true; ViewBag.ShowOnMobile = true;
} ViewBag.ShowOnDesktop = false;
}
} }
<div class="@ViewBag.AdCssClass bg-white border-top p-2" style="z-index: 1050;" id="mobile-bottom-ad">
@if (hasTestimonialBlocks) <div class="d-flex justify-content-between align-items-center">
{ @await Html.PartialAsync("_AdUnit")
<section class="testimonials-section py-5 bg-light"> <button type="button" class="btn-close ms-2" onclick="document.getElementById('mobile-bottom-ad').style.display='none'"></button>
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto text-center mb-5">
<h2 class="h3 fw-bold mb-3">@(ViewBag.DefaultTestimonialsTitle ?? "O Que Nossos Clientes Dizem")</h2>
<p class="text-muted">@(ViewBag.TestimonialsCount ?? "Avaliações positivas")</p>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-4">
<div class="card border-0 h-100">
<div class="card-body text-center">
<p class="mb-3">"@(ViewBag.Testimonial1Quote ?? "Excelente ferramenta!")"</p>
<h6 class="fw-bold">@(ViewBag.Testimonial1Name ?? "Cliente Satisfeito")</h6>
<small class="text-muted">@(ViewBag.Testimonial1Position ?? "Usuário")</small>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card border-0 h-100">
<div class="card-body text-center">
<p class="mb-3">"@(ViewBag.Testimonial2Quote ?? "Muito útil e fácil de usar!")"</p>
<h6 class="fw-bold">@(ViewBag.Testimonial2Name ?? "Outro Cliente")</h6>
<small class="text-muted">@(ViewBag.Testimonial2Position ?? "Usuário")</small>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card border-0 h-100">
<div class="card-body text-center">
<p class="mb-3">"@(ViewBag.Testimonial3Quote ?? "Recomendo para todos!")"</p>
<h6 class="fw-bold">@(ViewBag.Testimonial3Name ?? "Mais um Cliente")</h6>
<small class="text-muted">@(ViewBag.Testimonial3Position ?? "Usuário")</small>
</div>
</div>
</div>
</div>
</div>
</section>
}
<!-- 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">@(ViewBag.FinalCtaTitle ?? "Pronto para Converter?")</h2>
<p class="text-muted mb-4">@(ViewBag.FinalCtaSubtitle ?? "Use nossa ferramenta gratuita agora mesmo")</p>
<a href="#converter-container" class="btn btn-primary btn-lg">
@(ViewBag.FinalCtaButtonText ?? "Começar Conversão")
</a>
</div>
</div>
</div> </div>
</section> </div>
<!-- Scripts específicos do conversor --> <!-- Scripts específicos -->
@section Scripts { @section Scripts {
<script src="~/js/converter.js"></script> <script src="~/js/converter.js"></script>
@{ @{
@ -244,12 +308,52 @@
var jsFileName = $"~/js/converters/{converterType.ToString().ToLower()}-converter.js"; var jsFileName = $"~/js/converters/{converterType.ToString().ToLower()}-converter.js";
} }
<script src="@jsFileName"></script> <script src="@jsFileName"></script>
<!-- Inicialização dos Anúncios -->
<script> <script>
// Inicializar conversor específico document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () { // Inicializar conversor
if (typeof initializeConverter === 'function') { if (typeof initializeConverter === 'function') {
initializeConverter(); initializeConverter();
} }
// 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(() => {
try {
const adElements = document.querySelectorAll('.adsbygoogle[data-ad-position="rectangle-post-converter"]');
adElements.forEach(ad => {
(adsbygoogle = window.adsbygoogle || []).push({});
});
} catch (e) {
console.log('Ad refresh error:', e);
}
}, 2000);
}
</script> </script>
} }

View File

@ -0,0 +1,43 @@
@* Views/Shared/_AdUnit.cshtml *@
@{
var position = ViewBag.AdPosition?.ToString() ?? "default";
var slotId = ViewBag.AdSlotId?.ToString() ?? "default-slot";
var adFormat = ViewBag.AdFormat?.ToString() ?? "auto";
var adSize = ViewBag.AdSize?.ToString() ?? "";
var publisherId = ViewBag.GoogleAdsPublisher?.ToString() ?? "pub-3475956393038764";
var isEnabled = ViewBag.GoogleAdsEnabled ?? true;
var cssClass = ViewBag.AdCssClass?.ToString() ?? "ad-container";
var isSticky = ViewBag.IsSticky ?? false;
var showOnMobile = ViewBag.ShowOnMobile ?? true;
var showOnDesktop = ViewBag.ShowOnDesktop ?? true;
}
@if (isEnabled && !string.IsNullOrEmpty(publisherId) && publisherId != "pub-3475956393038764")
{
<div class="@cssClass @(isSticky ? "ad-sticky" : "") @(!showOnMobile ? "d-none d-md-block" : "") @(!showOnDesktop ? "d-block d-md-none" : "")"
data-ad-position="@position">
<ins class="adsbygoogle"
style="display:block@(adSize)"
data-ad-client="@publisherId"
data-ad-slot="@slotId"
data-ad-format="@adFormat"
@if (adFormat == "auto")
{
@Html.Raw("data-full-width-responsive=\"true\"")
}></ins>
@if (ViewBag.ShowAdLabel == true)
{
<small class="text-muted d-block text-center mt-1" style="font-size: 10px;">Publicidade</small>
}
</div>
}
else if (ViewBag.ShowPlaceholder == true)
{
<!-- Placeholder para desenvolvimento -->
<div class="@cssClass bg-light border rounded d-flex align-items-center justify-content-center"
style="min-height: 90px; color: #6c757d;">
<small>Anúncio: @position</small>
</div>
}

View File

@ -1,8 +1,8 @@
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top"> <header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm 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.HomeUrl">
<img src="@ViewBag.LogoUrl" alt="@ViewBag.SiteName" height="40" class="me-2"> <img src="@ViewBag.LogoUrl" alt="?" height="40" class="me-2">
<span class="fw-bold text-primary">@ViewBag.SiteName</span> <span class="fw-bold text-gradient">@ViewBag.SiteName</span>
</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"
@ -34,7 +34,7 @@
<!-- Language Switcher --> <!-- Language Switcher -->
<div class="dropdown me-3"> <div class="dropdown me-3">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" <button class="btn btn-outline-success btn-sm dropdown-toggle" type="button"
id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false"> 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.CurrentLanguageDisplay
@ -56,7 +56,7 @@
</div> </div>
<!-- CTA Button --> <!-- CTA Button -->
<a href="#conversion-form" class="btn btn-primary btn-sm scroll-to-form"> <a href="#conversion-form" class="btn btn-primary btn-sm scroll-to-form hover-lift">
@ViewBag.CtaButtonText @ViewBag.CtaButtonText
</a> </a>
</div> </div>

View File

@ -2,7 +2,7 @@
// Exemplo 1: Conversor de Maiúsculas/Minúsculas // Exemplo 1: Conversor de Maiúsculas/Minúsculas
"Converter": { "Converter": {
"Type": "text-case-sentence", "Type": "text-case-sentence",
"Name": "Conversor de Maiúsculas e Minúsculas" "Name": "Maiúsculas para 1a. minúsculas"
}, },
// Exemplo 2: Conversor CSV para JSON // Exemplo 2: Conversor CSV para JSON
@ -25,9 +25,13 @@
"DatabaseName": "text_case_converter_db" "DatabaseName": "text_case_converter_db"
}, },
"TextConversionApi": {
"BaseUrl": "https://localhost:7071"
},
"SEO": { "SEO": {
"DefaultDomain": "https://maiusculasminusculas.com", "DefaultDomain": "https://maiusculasminusculas.com",
"DefaultSiteName": "Conversor de Maiúsculas e Minúsculas Online", "DefaultSiteName": "Maiúsculas para 1a. minúsculas online",
"GoogleTagManagerId": "GTM-XXXXXXX", "GoogleTagManagerId": "GTM-XXXXXXX",
"GoogleAnalyticsId": "GA-XXXXXXXX" "GoogleAnalyticsId": "GA-XXXXXXXX"
}, },
@ -38,6 +42,14 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"GoogleAds": {
"PublisherID": "pub-3475956393038764",
"Enabled": true,
"TestMode": false,
"Slots": {
"BannerTop": "SEU_SLOT_ID_1",
"RectanglePre": "SEU_SLOT_ID_2"
}
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }

View File

@ -1,9 +1,12 @@
/* Custom CSS for Conversion Template */ /* Custom CSS for Conversion Template */
:root { :root {
--primary-color: #667eea; --primary-color: #28a745;
--secondary-color: #764ba2; --secondary-color: #20c997;
--success-color: #28a745; --success-color: #28a745;
--accent-color: #34ce57;
--light-green: #e8f5e8;
--dark-green: #1e7e34;
--warning-color: #ffc107; --warning-color: #ffc107;
--danger-color: #dc3545; --danger-color: #dc3545;
--info-color: #17a2b8; --info-color: #17a2b8;
@ -110,6 +113,10 @@ h1, h2, h3, h4, h5, h6 {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
} }
.bg-gradient-green {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%);
}
.text-gradient { .text-gradient {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text; -webkit-background-clip: text;
@ -117,18 +124,24 @@ h1, h2, h3, h4, h5, h6 {
background-clip: text; background-clip: text;
} }
.text-green-light {
color: var(--accent-color) !important;
}
.shadow-custom { .shadow-custom {
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.15); box-shadow: 0 10px 25px rgba(40, 167, 69, 0.15);
} }
/* Header Styles */ /* Header Styles */
.navbar { .navbar {
transition: var(--transition); transition: var(--transition);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
background-color: #ffffff !important;
border-bottom: 1px solid var(--light-green);
} }
.navbar.scrolled { .navbar.scrolled {
background: rgba(255, 255, 255, 0.95) !important; background: rgba(255, 255, 255, 0.98) !important;
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
} }
@ -141,6 +154,7 @@ h1, h2, h3, h4, h5, h6 {
font-weight: 500; font-weight: 500;
transition: var(--transition); transition: var(--transition);
position: relative; position: relative;
color: #666 !important;
} }
.nav-link:hover, .nav-link:hover,
@ -165,6 +179,54 @@ h1, h2, h3, h4, h5, h6 {
width: 100%; width: 100%;
} }
/* Melhorar dropdown */
.dropdown-menu {
border: 1px solid var(--light-green);
box-shadow: 0 4px 6px rgba(40, 167, 69, 0.1);
}
.dropdown-item {
color: #666;
transition: var(--transition);
}
.dropdown-item:hover {
background-color: var(--light-green);
color: var(--primary-color);
}
.dropdown-item.active {
background-color: var(--primary-color);
color: white;
}
/* Melhorar botão do idioma */
.btn-outline-success {
border-color: var(--primary-color);
color: var(--primary-color);
}
.btn-outline-success:hover {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
/* Melhorar navbar toggler */
.navbar-toggler {
border-color: var(--primary-color);
padding: 4px 8px;
}
.navbar-toggler:focus {
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
border-color: var(--primary-color);
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2840, 167, 69, 1%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
/* Hero Section */ /* Hero Section */
.hero-section { .hero-section {
min-height: 100vh; min-height: 100vh;
@ -192,7 +254,7 @@ h1, h2, h3, h4, h5, h6 {
} }
.hero-features .fas { .hero-features .fas {
color: #28a745; color: var(--accent-color);
} }
.scroll-indicator { .scroll-indicator {
@ -230,7 +292,7 @@ h1, h2, h3, h4, h5, h6 {
} }
.conversion-section { .conversion-section {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); background: linear-gradient(135deg, var(--light-green) 0%, #f8f9fa 100%);
} }
.form-control, .form-control,
@ -276,14 +338,15 @@ h1, h2, h3, h4, h5, h6 {
} }
.btn-primary { .btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
border: none; border: none;
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); box-shadow: 0 5px 15px rgba(40, 167, 69, 0.3);
} }
.btn-primary:hover { .btn-primary:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); box-shadow: 0 8px 25px rgba(40, 167, 69, 0.4);
background: linear-gradient(135deg, var(--dark-green), var(--primary-color));
} }
.btn-lg { .btn-lg {