feat/plano-rodape #13

Merged
ricardo merged 4 commits from feat/plano-rodape into main 2025-08-21 20:36:09 +00:00
16 changed files with 497 additions and 130 deletions
Showing only changes of commit 58057bd033 - Show all commits

View File

@ -21,6 +21,7 @@ public class AdminController : Controller
private readonly IEmailService _emailService; private readonly IEmailService _emailService;
private readonly ILivePageService _livePageService; private readonly ILivePageService _livePageService;
private readonly IImageStorageService _imageStorage; private readonly IImageStorageService _imageStorage;
private readonly IPaymentService _paymentService;
private readonly ILogger<AdminController> _logger; private readonly ILogger<AdminController> _logger;
public AdminController( public AdminController(
@ -32,6 +33,7 @@ public class AdminController : Controller
IEmailService emailService, IEmailService emailService,
ILivePageService livePageService, ILivePageService livePageService,
IImageStorageService imageStorage, IImageStorageService imageStorage,
IPaymentService paymentService,
ILogger<AdminController> logger) ILogger<AdminController> logger)
{ {
_authService = authService; _authService = authService;
@ -42,6 +44,7 @@ public class AdminController : Controller
_emailService = emailService; _emailService = emailService;
_livePageService = livePageService; _livePageService = livePageService;
_imageStorage = imageStorage; _imageStorage = imageStorage;
_paymentService = paymentService;
_logger = logger; _logger = logger;
} }
@ -151,12 +154,14 @@ public class AdminController : Controller
} }
// CRIAR NOVA PÁGINA // CRIAR NOVA PÁGINA
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
var model = new ManagePageViewModel var model = new ManagePageViewModel
{ {
IsNewPage = true, IsNewPage = true,
AvailableCategories = categories, AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage() MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
AllowProductLinks = planLimitations.AllowProductLinks
}; };
return View(model); return View(model);
} }
@ -167,7 +172,7 @@ public class AdminController : Controller
if (page == null || page.UserId != user.Id) if (page == null || page.UserId != user.Id)
return NotFound(); return NotFound();
var model = MapToManageViewModel(page, categories, themes, userPlanType); var model = await MapToManageViewModel(page, categories, themes, userPlanType);
return View(model); return View(model);
} }
} }
@ -610,7 +615,7 @@ public class AdminController : Controller
return RedirectToAction("Dashboard"); return RedirectToAction("Dashboard");
} }
private ManagePageViewModel MapToManageViewModel(UserPage page, List<Category> categories, List<PageTheme> themes, PlanType userPlanType) private async Task<ManagePageViewModel> MapToManageViewModel(UserPage page, List<Category> categories, List<PageTheme> themes, PlanType userPlanType)
{ {
return new ManagePageViewModel return new ManagePageViewModel
{ {
@ -641,7 +646,8 @@ public class AdminController : Controller
}).ToList() ?? new List<ManageLinkViewModel>(), }).ToList() ?? new List<ManageLinkViewModel>(),
AvailableCategories = categories, AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage() MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
AllowProductLinks = (await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString())).AllowProductLinks
}; };
} }
@ -761,8 +767,23 @@ public class AdminController : Controller
// Add regular links // Add regular links
if (model.Links?.Any() == true) if (model.Links?.Any() == true)
{ {
page.Links.AddRange(model.Links.Where(l => !string.IsNullOrEmpty(l.Title) && !string.IsNullOrEmpty(l.Url)) // Validar links de produto baseado no plano do usuário
.Select((l, index) => new LinkItem var user = await _authService.GetCurrentUserAsync(User);
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
var filteredLinks = model.Links.Where(l => !string.IsNullOrEmpty(l.Title) && !string.IsNullOrEmpty(l.Url));
foreach (var link in filteredLinks)
{
// Verificar se usuário pode criar links de produto
if (link.Type == LinkType.Product && !planLimitations.AllowProductLinks)
{
throw new InvalidOperationException("Links de produto disponíveis apenas no plano Premium + Afiliados");
}
}
page.Links.AddRange(filteredLinks.Select((l, index) => new LinkItem
{ {
Title = l.Title, Title = l.Title,
Url = l.Url, Url = l.Url,

View File

@ -30,10 +30,10 @@ public class ModerationController : Controller
} }
[HttpGet("Dashboard")] [HttpGet("Dashboard")]
public async Task<IActionResult> Dashboard(int page = 1, int size = 20) public async Task<IActionResult> Dashboard(int page = 1, int size = 20, string? filter = null)
{ {
var skip = (page - 1) * size; var skip = (page - 1) * size;
var pendingPages = await _moderationService.GetPendingModerationAsync(skip, size); var pendingPages = await _moderationService.GetPendingModerationAsync(skip, size, filter);
var stats = await _moderationService.GetModerationStatsAsync(); var stats = await _moderationService.GetModerationStatsAsync();
var viewModel = new ModerationDashboardViewModel var viewModel = new ModerationDashboardViewModel
@ -47,6 +47,7 @@ public class ModerationController : Controller
CreatedAt = p.CreatedAt, CreatedAt = p.CreatedAt,
ModerationAttempts = p.ModerationAttempts, ModerationAttempts = p.ModerationAttempts,
PlanType = p.PlanLimitations.PlanType.ToString(), PlanType = p.PlanLimitations.PlanType.ToString(),
IsSpecialModeration = p.PlanLimitations.SpecialModeration ?? false,
PreviewUrl = !string.IsNullOrEmpty(p.PreviewToken) PreviewUrl = !string.IsNullOrEmpty(p.PreviewToken)
? $"/page/{p.Category}/{p.Slug}?preview={p.PreviewToken}" ? $"/page/{p.Category}/{p.Slug}?preview={p.PreviewToken}"
: null : null
@ -54,7 +55,8 @@ public class ModerationController : Controller
Stats = stats, Stats = stats,
CurrentPage = page, CurrentPage = page,
PageSize = size, PageSize = size,
HasNextPage = pendingPages.Count == size HasNextPage = pendingPages.Count == size,
CurrentFilter = filter ?? "all"
}; };
return View(viewModel); return View(viewModel);

View File

@ -16,13 +16,15 @@ public class PaymentController : Controller
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IUserRepository _userService; private readonly IUserRepository _userService;
private readonly ISubscriptionRepository _subscriptionRepository; private readonly ISubscriptionRepository _subscriptionRepository;
private readonly IConfiguration _configuration;
public PaymentController(IPaymentService paymentService, IAuthService authService, IUserRepository userService, ISubscriptionRepository subscriptionRepository) public PaymentController(IPaymentService paymentService, IAuthService authService, IUserRepository userService, ISubscriptionRepository subscriptionRepository, IConfiguration configuration)
{ {
_paymentService = paymentService; _paymentService = paymentService;
_authService = authService; _authService = authService;
_userService = userService; _userService = userService;
_subscriptionRepository = subscriptionRepository; _subscriptionRepository = subscriptionRepository;
_configuration = configuration;
} }
[HttpPost] [HttpPost]
@ -220,47 +222,33 @@ public class PaymentController : Controller
private List<AvailablePlanViewModel> GetAvailablePlans(string currentPlan) private List<AvailablePlanViewModel> GetAvailablePlans(string currentPlan)
{ {
var plans = new List<AvailablePlanViewModel> var plansConfig = _configuration.GetSection("Plans");
var plans = new List<AvailablePlanViewModel>();
// Adicionar planos mensais apenas (excluir Trial e planos anuais)
var monthlyPlans = new[] { "Basic", "Professional", "Premium", "PremiumAffiliate" };
foreach (var planKey in monthlyPlans)
{ {
// Plano Trial não é incluído aqui pois é gerenciado internamente var planSection = plansConfig.GetSection(planKey);
// O "downgrade" para Trial acontece via cancelamento da assinatura if (planSection.Exists())
new()
{ {
PlanType = "basic", plans.Add(new AvailablePlanViewModel
DisplayName = "Básico", {
Price = 9.90m, PlanType = planKey.ToLower(),
PriceId = "price_basic", // Substitua pelos IDs reais do Stripe DisplayName = planSection["Name"] ?? planKey,
MaxLinks = 5, Price = decimal.Parse(planSection["Price"] ?? "0"),
AllowAnalytics = true, PriceId = planSection["PriceId"] ?? "",
Features = new List<string> { "5 links", "Temas básicos", "Análises básicas" }, MaxLinks = int.Parse(planSection["MaxLinks"] ?? "0"),
IsCurrentPlan = currentPlan == "basic" AllowAnalytics = bool.Parse(planSection["AllowAnalytics"] ?? "false"),
}, AllowCustomDomain = true, // URL personalizada em todos os planos pagos
new() AllowCustomThemes = bool.Parse(planSection["AllowPremiumThemes"] ?? "false"),
{ AllowProductLinks = bool.Parse(planSection["AllowProductLinks"] ?? "false"),
PlanType = "professional", Features = planSection.GetSection("Features").Get<List<string>>() ?? new List<string>(),
DisplayName = "Profissional", IsCurrentPlan = currentPlan.Equals(planKey, StringComparison.OrdinalIgnoreCase)
Price = 24.90m, });
PriceId = "price_professional", // Substitua pelos IDs reais do Stripe
MaxLinks = 15,
AllowAnalytics = true,
AllowCustomDomain = true,
Features = new List<string> { "15 links", "Todos os temas", "Página rápida", "Análises avançadas" },
IsCurrentPlan = currentPlan == "professional"
},
new()
{
PlanType = "premium",
DisplayName = "Premium",
Price = 29.90m,
PriceId = "price_premium", // Substitua pelos IDs reais do Stripe
MaxLinks = -1, // Ilimitado
AllowCustomThemes = true,
AllowAnalytics = true,
AllowCustomDomain = true,
Features = new List<string> { "Links ilimitados", "Temas personalizados", "Página rápida", "Suporte prioritário" },
IsCurrentPlan = currentPlan == "premium"
} }
}; }
// Marcar upgrades e filtrar downgrades // Marcar upgrades e filtrar downgrades
var currentPlanIndex = plans.FindIndex(p => p.IsCurrentPlan); var currentPlanIndex = plans.FindIndex(p => p.IsCurrentPlan);

View File

@ -35,6 +35,9 @@ public class PlanLimitations
[BsonElement("allowProductLinks")] [BsonElement("allowProductLinks")]
public bool AllowProductLinks { get; set; } = false; public bool AllowProductLinks { get; set; } = false;
[BsonElement("specialModeration")]
public bool? SpecialModeration { get; set; } = false;
[BsonElement("ogExtractionsUsedToday")] [BsonElement("ogExtractionsUsedToday")]
public int OGExtractionsUsedToday { get; set; } = 0; public int OGExtractionsUsedToday { get; set; } = 0;

View File

@ -6,7 +6,7 @@ public interface IModerationService
{ {
Task<string> GeneratePreviewTokenAsync(string pageId); Task<string> GeneratePreviewTokenAsync(string pageId);
Task<bool> ValidatePreviewTokenAsync(string pageId, string token); Task<bool> ValidatePreviewTokenAsync(string pageId, string token);
Task<List<UserPage>> GetPendingModerationAsync(int skip = 0, int take = 20); Task<List<UserPage>> GetPendingModerationAsync(int skip = 0, int take = 20, string? filter = null);
Task<UserPage?> GetPageForModerationAsync(string pageId); Task<UserPage?> GetPageForModerationAsync(string pageId);
Task ApprovePageAsync(string pageId, string moderatorId, string? notes = null); Task ApprovePageAsync(string pageId, string moderatorId, string? notes = null);
Task RejectPageAsync(string pageId, string moderatorId, string reason, List<string> issues); Task RejectPageAsync(string pageId, string moderatorId, string reason, List<string> issues);

View File

@ -52,16 +52,44 @@ public class ModerationService : IModerationService
return isValid; return isValid;
} }
public async Task<List<UserPage>> GetPendingModerationAsync(int skip = 0, int take = 20)
{
var filter = Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.PendingModeration);
// Ordenar por prioridade do plano e depois por data public async Task<List<UserPage>> GetPendingModerationAsync(int skip = 0, int take = 20, string? filter = null)
{
var filterBuilder = Builders<UserPage>.Filter;
var baseFilter = filterBuilder.Eq(p => p.Status, PageStatus.PendingModeration);
FilterDefinition<UserPage> finalFilter = baseFilter;
// Aplicar filtro de moderação especial
if (!string.IsNullOrEmpty(filter))
{
switch (filter.ToLower())
{
case "special":
// Filtrar apenas páginas com moderação especial (Premium + Afiliados)
var specialFilter = filterBuilder.Eq("planLimitations.specialModeration", true);
finalFilter = filterBuilder.And(baseFilter, specialFilter);
break;
case "normal":
// Filtrar apenas páginas sem moderação especial
var normalFilter = filterBuilder.Or(
filterBuilder.Eq("planLimitations.specialModeration", false),
filterBuilder.Exists("planLimitations.specialModeration", false)
);
finalFilter = filterBuilder.And(baseFilter, normalFilter);
break;
default:
// "all" ou qualquer outro valor: sem filtro adicional
break;
}
}
// Ordenar por moderação especial primeiro (SLA reduzido), depois por data
var sort = Builders<UserPage>.Sort var sort = Builders<UserPage>.Sort
.Ascending("planLimitations.planType") .Descending("planLimitations.specialModeration")
.Ascending(p => p.CreatedAt); .Ascending(p => p.CreatedAt);
var pages = await _userPageRepository.GetManyAsync(filter, sort, skip, take); var pages = await _userPageRepository.GetManyAsync(finalFilter, sort, skip, take);
return pages.ToList(); return pages.ToList();
} }

View File

@ -205,22 +205,26 @@ public class PaymentService : IPaymentService
{ {
"basic" => new PlanLimitations "basic" => new PlanLimitations
{ {
MaxLinks = 5, MaxLinks = 8,
AllowCustomThemes = false, AllowCustomThemes = false,
AllowAnalytics = true, AllowAnalytics = true,
AllowCustomDomain = false, AllowCustomDomain = true, // URL personalizada em todos os planos pagos
AllowMultipleDomains = false, AllowMultipleDomains = false,
PrioritySupport = false, PrioritySupport = false,
AllowProductLinks = false,
MaxProductLinks = 0,
PlanType = "basic" PlanType = "basic"
}, },
"professional" => new PlanLimitations "professional" => new PlanLimitations
{ {
MaxLinks = 15, MaxLinks = 20,
AllowCustomThemes = false, AllowCustomThemes = false,
AllowAnalytics = true, AllowAnalytics = true,
AllowCustomDomain = true, AllowCustomDomain = true,
AllowMultipleDomains = false, AllowMultipleDomains = false,
PrioritySupport = false, PrioritySupport = false,
AllowProductLinks = false,
MaxProductLinks = 0,
PlanType = "professional" PlanType = "professional"
}, },
"premium" => new PlanLimitations "premium" => new PlanLimitations
@ -231,17 +235,33 @@ public class PaymentService : IPaymentService
AllowCustomDomain = true, AllowCustomDomain = true,
AllowMultipleDomains = true, AllowMultipleDomains = true,
PrioritySupport = true, PrioritySupport = true,
AllowProductLinks = false,
MaxProductLinks = 0,
PlanType = "premium" PlanType = "premium"
}, },
"premiumaffiliate" => new PlanLimitations
{
MaxLinks = -1, // Unlimited
AllowCustomThemes = true,
AllowAnalytics = true,
AllowCustomDomain = true,
AllowMultipleDomains = true,
PrioritySupport = true,
AllowProductLinks = true,
MaxProductLinks = 10,
PlanType = "premiumaffiliate"
},
_ => new PlanLimitations _ => new PlanLimitations
{ {
MaxLinks = 5, MaxLinks = 3,
AllowCustomThemes = false, AllowCustomThemes = false,
AllowAnalytics = false, AllowAnalytics = false,
AllowCustomDomain = false, AllowCustomDomain = false,
AllowMultipleDomains = false, AllowMultipleDomains = false,
PrioritySupport = false, PrioritySupport = false,
PlanType = "free" AllowProductLinks = false,
MaxProductLinks = 0,
PlanType = "trial"
} }
}; };

View File

@ -46,6 +46,7 @@ public class ManagePageViewModel
// Plan limitations // Plan limitations
public int MaxLinksAllowed { get; set; } = 3; public int MaxLinksAllowed { get; set; } = 3;
public bool AllowProductLinks { get; set; } = false;
public bool CanUseTheme(string themeName) => AvailableThemes.Any(t => t.Name.ToLower() == themeName.ToLower()); public bool CanUseTheme(string themeName) => AvailableThemes.Any(t => t.Name.ToLower() == themeName.ToLower());
/// <summary> /// <summary>

View File

@ -57,6 +57,7 @@ public class AvailablePlanViewModel
public bool AllowCustomThemes { get; set; } public bool AllowCustomThemes { get; set; }
public bool AllowAnalytics { get; set; } public bool AllowAnalytics { get; set; }
public bool AllowCustomDomain { get; set; } public bool AllowCustomDomain { get; set; }
public bool AllowProductLinks { get; set; }
public bool IsCurrentPlan { get; set; } public bool IsCurrentPlan { get; set; }
public bool IsUpgrade { get; set; } public bool IsUpgrade { get; set; }
public bool IsDowngrade { get; set; } public bool IsDowngrade { get; set; }

View File

@ -10,6 +10,7 @@ public class ModerationDashboardViewModel
public int PageSize { get; set; } = 20; public int PageSize { get; set; } = 20;
public bool HasNextPage { get; set; } = false; public bool HasNextPage { get; set; } = false;
public bool HasPreviousPage => CurrentPage > 1; public bool HasPreviousPage => CurrentPage > 1;
public string CurrentFilter { get; set; } = "all";
} }
public class ModerationPageViewModel public class ModerationPageViewModel
@ -78,4 +79,5 @@ public class PendingPageViewModel
public string PreviewUrl { get; set; } = ""; public string PreviewUrl { get; set; } = "";
public string PriorityLabel { get; set; } = ""; public string PriorityLabel { get; set; } = "";
public string PriorityColor { get; set; } = ""; public string PriorityColor { get; set; } = "";
public bool IsSpecialModeration { get; set; } = false;
} }

View File

@ -288,7 +288,7 @@
<div class="link-input-group product-link-preview" data-link="@i"> <div class="link-input-group product-link-preview" data-link="@i">
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-shopping-bag me-2 text-success"></i>Link de Produto @(i + 1) <i class="fas fa-shopping-bag me-2 text-success"></i>Link de Afiliado @(i + 1)
</h6> </h6>
<button type="button" class="btn btn-sm btn-outline-danger remove-link-btn"> <button type="button" class="btn btn-sm btn-outline-danger remove-link-btn">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
@ -518,8 +518,8 @@
<!-- Tipo de Link --> <!-- Tipo de Link -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Tipo de Link</label> <label class="form-label">Tipo de Link</label>
<div class="d-flex gap-2"> <div class="d-flex gap-2 flex-wrap">
<div class="form-check flex-fill"> <div class="form-check @(Model.AllowProductLinks ? "flex-fill" : "w-100")">
<input class="form-check-input" type="radio" name="linkType" id="linkTypeNormal" value="Normal" checked> <input class="form-check-input" type="radio" name="linkType" id="linkTypeNormal" value="Normal" checked>
<label class="form-check-label w-100 p-2 border rounded" for="linkTypeNormal"> <label class="form-check-label w-100 p-2 border rounded" for="linkTypeNormal">
<i class="fas fa-link me-2"></i> <i class="fas fa-link me-2"></i>
@ -527,14 +527,40 @@
<div class="small text-muted">Link simples para sites, redes sociais, etc.</div> <div class="small text-muted">Link simples para sites, redes sociais, etc.</div>
</label> </label>
</div> </div>
<div class="form-check flex-fill"> <div class="form-check @(Model.AllowProductLinks ? "flex-fill" : "w-100 mt-2")" id="productLinkOption">
<input class="form-check-input" type="radio" name="linkType" id="linkTypeProduct" value="Product"> <input class="form-check-input" type="radio" name="linkType" id="linkTypeProduct" value="Product" @(!Model.AllowProductLinks ? "disabled" : "")>
<label class="form-check-label w-100 p-2 border rounded" for="linkTypeProduct"> <label class="form-check-label w-100 p-2 border rounded @(!Model.AllowProductLinks ? "position-relative" : "")" for="linkTypeProduct">
<i class="fas fa-shopping-bag me-2"></i> @if (!Model.AllowProductLinks)
<strong>Link de Produto</strong> {
<div class="small text-muted">Para produtos de e-commerce com preview</div> <div class="position-absolute top-0 end-0 m-1" style="z-index: 2;">
<div class="bg-warning text-dark px-2 py-1 rounded shadow-sm d-flex align-items-center">
<i class="fas fa-crown me-1"></i>
<small class="fw-bold">Premium + Afiliados</small>
</div>
</div>
<div class="position-absolute top-0 start-0 w-100 h-100 bg-warning bg-opacity-5 rounded" style="z-index: 1;"></div>
}
<div class="@(!Model.AllowProductLinks ? "opacity-50" : "")">
<i class="fas fa-shopping-bag me-2"></i>
<strong>Link de Afiliado</strong>
<div class="small text-muted">Para produtos de e-commerce com preview</div>
</div>
</label> </label>
</div> </div>
@if (!Model.AllowProductLinks)
{
<div class="col-12 mt-2">
<div class="alert alert-warning small mb-0 d-flex align-items-center">
<i class="fas fa-crown text-warning me-2"></i>
<span class="flex-grow-1">
<strong>Links de afiliado</strong> disponíveis apenas no plano <strong>Premium + Afiliados</strong>.
</span>
<a asp-controller="Home" asp-action="Pricing" class="btn btn-warning btn-sm ms-2">
<i class="fas fa-arrow-up me-1"></i>Fazer Upgrade
</a>
</div>
</div>
}
</div> </div>
</div> </div>
@ -578,7 +604,7 @@
</div> </div>
</div> </div>
<!-- Seção para Link de Produto --> <!-- Seção para Link de Afiliado -->
<div id="productLinkSection" style="display: none;"> <div id="productLinkSection" style="display: none;">
<div class="mb-3"> <div class="mb-3">
<label for="productUrl" class="form-label">URL do Produto</label> <label for="productUrl" class="form-label">URL do Produto</label>
@ -1235,7 +1261,7 @@
<div class="link-input-group product-link-preview" data-link="${nextIndex}"> <div class="link-input-group product-link-preview" data-link="${nextIndex}">
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-shopping-bag me-2 text-success"></i>Link de Produto ${displayCount} <i class="fas fa-shopping-bag me-2 text-success"></i>Link de Afiliado ${displayCount}
</h6> </h6>
<button type="button" class="btn btn-sm btn-outline-danger remove-link-btn"> <button type="button" class="btn btn-sm btn-outline-danger remove-link-btn">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>

View File

@ -19,7 +19,7 @@
Criada para profissionais e empresas no Brasil. Criada para profissionais e empresas no Brasil.
Organize todos os seus links em uma página única e profissional. Organize todos os seus links em uma página única e profissional.
</p> </p>
<div class="d-flex gap-3 flex-wrap"> <div class="d-flex gap-3 flex-wrap mb-4 mb-lg-0">
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
{ {
<a asp-controller="Admin" asp-action="Dashboard" class="btn btn-light btn-lg px-4"> <a asp-controller="Admin" asp-action="Dashboard" class="btn btn-light btn-lg px-4">

View File

@ -25,14 +25,14 @@
</div> </div>
</div> </div>
<div class="row g-4 justify-content-center"> <div class="row g-3 justify-content-center pricing-cards">
<!-- Plano Trial --> <!-- Plano Trial -->
<div class="col-lg-3 col-md-6"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-success bg-opacity-10 text-center py-4"> <div class="card-header bg-success bg-opacity-10 text-center py-4">
<h4 class="mb-0">Trial Gratuito</h4> <h5 class="mb-0">Trial Gratuito</h5>
<div class="mt-3"> <div class="mt-3">
<span class="display-4 fw-bold text-success">R$ 0</span> <span class="display-5 fw-bold text-success">R$ 0</span>
<span class="text-muted">/7 dias</span> <span class="text-muted">/7 dias</span>
</div> </div>
</div> </div>
@ -74,20 +74,20 @@
</div> </div>
<!-- Plano Básico --> <!-- Plano Básico -->
<div class="col-lg-3 col-md-6"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-light text-center py-4"> <div class="card-header bg-light text-center py-4">
<h4 class="mb-0">Básico</h4> <h5 class="mb-0">Básico</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-4 fw-bold text-primary">R$ 9,90</span> <span class="display-5 fw-bold text-primary">R$ 5,90</span>
<span class="text-muted">/mês</span> <span class="text-muted">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-4 fw-bold text-primary">R$ 99,00</span> <span class="display-5 fw-bold text-primary">R$ 59,00</span>
<span class="text-muted">/ano</span> <span class="text-muted">/ano</span>
<br> <br>
<small class="text-success">Economize R$ 19,80 (2 meses grátis)</small> <small class="text-success">Economize R$ 11,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
</div> </div>
@ -95,11 +95,15 @@
<ul class="list-unstyled"> <ul class="list-unstyled">
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
<strong>3 páginas</strong>, 8 links cada <strong>3 páginas</strong>
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Temas básicos 8 links por página
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
20 temas básicos
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
@ -111,11 +115,11 @@
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-muted me-2">✗</i> <i class="text-muted me-2">✗</i>
<span class="text-muted">Página rápida</span> <span class="text-muted">Temas premium</span>
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-muted me-2">✗</i> <i class="text-muted me-2">✗</i>
<span class="text-muted">Temas premium</span> <span class="text-muted">Links de produto</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -144,20 +148,20 @@
</div> </div>
<!-- Plano Profissional (Decoy) --> <!-- Plano Profissional (Decoy) -->
<div class="col-lg-3 col-md-6"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-warning bg-opacity-10 text-center py-4"> <div class="card-header bg-warning bg-opacity-10 text-center py-4">
<h4 class="mb-0">Profissional</h4> <h5 class="mb-0">Profissional</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-4 fw-bold text-warning">R$ 24,90</span> <span class="display-5 fw-bold text-warning">R$ 12,90</span>
<span class="text-muted">/mês</span> <span class="text-muted">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-4 fw-bold text-warning">R$ 249,00</span> <span class="display-5 fw-bold text-warning">R$ 129,00</span>
<span class="text-muted">/ano</span> <span class="text-muted">/ano</span>
<br> <br>
<small class="text-success">Economize R$ 49,80 (2 meses grátis)</small> <small class="text-success">Economize R$ 25,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
</div> </div>
@ -165,11 +169,15 @@
<ul class="list-unstyled"> <ul class="list-unstyled">
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
<strong>5 páginas</strong>, 20 links cada <strong>5 páginas</strong>
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Todos os temas 20 links por página
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
20 temas básicos
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
@ -177,15 +185,15 @@
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Suporte por email URL personalizada
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-muted me-2">✗</i> <i class="text-muted me-2">✗</i>
<span class="text-muted">Links ilimitados</span> <span class="text-muted">Temas premium</span>
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-muted me-2">✗</i> <i class="text-muted me-2">✗</i>
<span class="text-muted">Suporte prioritário</span> <span class="text-muted">Links de produto</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -214,23 +222,23 @@
</div> </div>
<!-- Plano Premium (Mais Popular) --> <!-- Plano Premium (Mais Popular) -->
<div class="col-lg-3 col-md-6"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-primary shadow position-relative"> <div class="card h-100 border-primary shadow position-relative">
<div class="position-absolute top-0 start-50 translate-middle"> <div class="position-absolute top-0 start-50 translate-middle">
<span class="badge bg-primary px-3 py-2">Mais Popular</span> <span class="badge bg-primary px-3 py-2">Mais Popular</span>
</div> </div>
<div class="card-header bg-primary text-white text-center py-4"> <div class="card-header bg-primary text-white text-center py-4">
<h4 class="mb-0">Premium</h4> <h5 class="mb-0">Premium</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-4 fw-bold">R$ 29,90</span> <span class="display-5 fw-bold">R$ 19,90</span>
<span class="opacity-75">/mês</span> <span class="opacity-75">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-4 fw-bold">R$ 299,00</span> <span class="display-5 fw-bold">R$ 199,00</span>
<span class="opacity-75">/ano</span> <span class="opacity-75">/ano</span>
<br> <br>
<small class="text-light">Economize R$ 59,80 (2 meses grátis)</small> <small class="text-light">Economize R$ 39,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
<small class="opacity-75">Melhor custo-benefício!</small> <small class="opacity-75">Melhor custo-benefício!</small>
@ -239,11 +247,15 @@
<ul class="list-unstyled"> <ul class="list-unstyled">
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
<strong>15 páginas</strong>, links ilimitados <strong>15 páginas</strong>
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Temas premium Links ilimitados
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
40 temas*
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
@ -251,17 +263,20 @@
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Backup automático URL personalizada
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Suporte prioritário Suporte prioritário
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-muted me-2">✗</i>
Recursos exclusivos <span class="text-muted">Links de produto</span>
</li> </li>
</ul> </ul>
<div class="mt-3">
<small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small>
</div>
</div> </div>
<div class="card-footer bg-transparent p-4"> <div class="card-footer bg-transparent p-4">
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
@ -286,6 +301,87 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Plano Premium + Afiliados -->
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-success shadow">
<div class="position-absolute top-0 start-50 translate-middle pricing-premium-badge">
<span class="badge bg-success px-3 py-2">Novo!</span>
</div>
<div class="card-header bg-success text-white text-center py-4">
<h5 class="mb-0">Premium + Afiliados</h5>
<div class="mt-3">
<div class="pricing-monthly">
<span class="display-5 fw-bold">R$ 29,90</span>
<span class="opacity-75">/mês</span>
</div>
<div class="pricing-yearly d-none">
<span class="display-5 fw-bold">R$ 299,00</span>
<span class="opacity-75">/ano</span>
<br>
<small class="text-light">Economize R$ 59,80 (2 meses grátis)</small>
</div>
</div>
<small class="opacity-75">Para monetização!</small>
</div>
<div class="card-body p-4">
<ul class="list-unstyled">
<li class="mb-3">
<i class="text-success me-2">✓</i>
<strong>15 páginas</strong>
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
Links ilimitados
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
40 temas*
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
<strong>Links de produto</strong>
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
Moderação plus
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
Suporte prioritário
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
10 links afiliados
</li>
</ul>
<div class="mt-3">
<small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small>
</div>
</div>
<div class="card-footer bg-transparent p-4">
@if (User.Identity?.IsAuthenticated == true)
{
<div class="pricing-monthly">
<form asp-controller="Payment" asp-action="CreateCheckoutSession" method="post">
<input type="hidden" name="planType" value="PremiumAffiliate" />
<button type="submit" class="btn btn-success w-100 fw-bold">Escolher Premium + Afiliados</button>
</form>
</div>
<div class="pricing-yearly d-none">
<form asp-controller="Payment" asp-action="CreateCheckoutSession" method="post">
<input type="hidden" name="planType" value="PremiumAffiliateYearly" />
<button type="submit" class="btn btn-success w-100 fw-bold">Escolher Premium + Afiliados Anual</button>
</form>
</div>
}
else
{
<a asp-controller="Auth" asp-action="Login" class="btn btn-success w-100 fw-bold">Escolher Premium + Afiliados</a>
}
</div>
</div>
</div>
</div> </div>
<!-- Comparação de recursos --> <!-- Comparação de recursos -->
@ -300,6 +396,7 @@
<th class="text-center">Básico</th> <th class="text-center">Básico</th>
<th class="text-center">Profissional</th> <th class="text-center">Profissional</th>
<th class="text-center">Premium</th> <th class="text-center">Premium</th>
<th class="text-center">Premium + Afiliados</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -309,6 +406,7 @@
<td class="text-center">3</td> <td class="text-center">3</td>
<td class="text-center">5</td> <td class="text-center">5</td>
<td class="text-center"><strong>15</strong></td> <td class="text-center"><strong>15</strong></td>
<td class="text-center"><strong>15</strong></td>
</tr> </tr>
<tr> <tr>
<td>Links por página</td> <td>Links por página</td>
@ -316,13 +414,15 @@
<td class="text-center">8</td> <td class="text-center">8</td>
<td class="text-center">20</td> <td class="text-center">20</td>
<td class="text-center"><strong>Ilimitado</strong></td> <td class="text-center"><strong>Ilimitado</strong></td>
<td class="text-center"><strong>Ilimitado</strong></td>
</tr> </tr>
<tr> <tr>
<td>Temas disponíveis</td> <td>Temas disponíveis</td>
<td class="text-center">1 básico</td> <td class="text-center">1 básico</td>
<td class="text-center">Básicos</td> <td class="text-center">20 básicos</td>
<td class="text-center">Todos</td> <td class="text-center">20 básicos</td>
<td class="text-center"><strong>Customizáveis</strong></td> <td class="text-center"><strong>40 (básicos + premium)</strong></td>
<td class="text-center"><strong>40 (básicos + premium)</strong></td>
</tr> </tr>
<tr> <tr>
<td>Analytics</td> <td>Analytics</td>
@ -330,23 +430,43 @@
<td class="text-center">Simples</td> <td class="text-center">Simples</td>
<td class="text-center">Avançado</td> <td class="text-center">Avançado</td>
<td class="text-center"><strong>Completo</strong></td> <td class="text-center"><strong>Completo</strong></td>
<td class="text-center"><strong>Completo</strong></td>
</tr> </tr>
<tr> <tr>
<td>Suporte por email</td> <td>Suporte por email</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td>
<td class="text-center">✅</td> <td class="text-center">✅</td>
<td class="text-center">✅</td> <td class="text-center">✅</td>
</tr> </tr>
<tr> <tr>
<td>Backup automático</td> <td>URL personalizada</td>
<td class="text-center">❌</td>
<td class="text-center">✅</td>
<td class="text-center">✅</td>
<td class="text-center">✅</td>
<td class="text-center">✅</td>
</tr>
<tr>
<td>Suporte prioritário</td>
<td class="text-center">❌</td>
<td class="text-center">❌</td>
<td class="text-center">❌</td>
<td class="text-center"><strong>✅</strong></td>
<td class="text-center"><strong>✅</strong></td>
</tr>
<tr>
<td>Links de produto</td>
<td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center"><strong>✅</strong></td> <td class="text-center"><strong>✅</strong></td>
</tr> </tr>
<tr> <tr>
<td>Suporte prioritário</td> <td>Moderação especial</td>
<td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>
<td class="text-center">❌</td> <td class="text-center">❌</td>

View File

@ -16,8 +16,16 @@
@if (Model.PendingPages.Any()) @if (Model.PendingPages.Any())
{ {
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header d-flex justify-content-between align-items-center">
<h5>Páginas Pendentes</h5> <h5 class="mb-0">Páginas Pendentes</h5>
<div class="d-flex align-items-center">
<label for="moderationFilter" class="form-label me-2 mb-0">Filtrar:</label>
<select id="moderationFilter" class="form-select form-select-sm" style="width: auto;" onchange="filterModeration()">
<option value="all" selected="@(Model.CurrentFilter == "all")">Todas as páginas</option>
<option value="special" selected="@(Model.CurrentFilter == "special")">Moderação especial (SLA reduzido)</option>
<option value="normal" selected="@(Model.CurrentFilter == "normal")">Moderação normal</option>
</select>
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
@ -33,8 +41,16 @@
<tbody> <tbody>
@foreach (var pageItem in Model.PendingPages) @foreach (var pageItem in Model.PendingPages)
{ {
<tr> <tr @(pageItem.IsSpecialModeration ? "class=table-warning" : "")>
<td>@pageItem.DisplayName</td> <td>
@pageItem.DisplayName
@if (pageItem.IsSpecialModeration)
{
<span class="badge bg-warning text-dark ms-2">
<i class="fas fa-star"></i> SLA Reduzido
</span>
}
</td>
<td>@pageItem.Category</td> <td>@pageItem.Category</td>
<td>@pageItem.CreatedAt.ToString("dd/MM/yyyy")</td> <td>@pageItem.CreatedAt.ToString("dd/MM/yyyy")</td>
<td> <td>
@ -58,3 +74,21 @@
</div> </div>
} }
</div> </div>
<script>
function filterModeration() {
const filter = document.getElementById('moderationFilter').value;
const currentUrl = new URL(window.location);
if (filter === 'all') {
currentUrl.searchParams.delete('filter');
} else {
currentUrl.searchParams.set('filter', filter);
}
// Reset page to 1 when filtering
currentUrl.searchParams.set('page', '1');
window.location.href = currentUrl.toString();
}
</script>

View File

@ -27,42 +27,103 @@
}, },
"Plans": { "Plans": {
"Basic": { "Basic": {
"Name": "Básico",
"PriceId": "price_1RjUskBMIadsOxJVgLwlVo1y", "PriceId": "price_1RjUskBMIadsOxJVgLwlVo1y",
"Price": 9.90, "Price": 5.90,
"MaxLinks": 5, "MaxPages": 3,
"Features": [ "basic_themes", "simple_analytics" ] "MaxLinks": 8,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ],
"Interval": "month"
}, },
"Professional": { "Professional": {
"Name": "Profissional",
"PriceId": "price_1RjUv9BMIadsOxJVORqlM4E9", "PriceId": "price_1RjUv9BMIadsOxJVORqlM4E9",
"Price": 24.90, "Price": 12.90,
"MaxLinks": 15, "MaxPages": 5,
"Features": [ "all_themes", "advanced_analytics" ] "MaxLinks": 20,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ],
"Interval": "month"
}, },
"Premium": { "Premium": {
"Name": "Premium",
"PriceId": "price_1RjUw0BMIadsOxJVmdouNV1g", "PriceId": "price_1RjUw0BMIadsOxJVmdouNV1g",
"Price": 29.90, "Price": 19.90,
"MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"Features": [ "custom_themes", "full_analytics", "priority_support" ] "AllowPremiumThemes": true,
"AllowProductLinks": false,
"AllowAnalytics": true,
"SpecialModeration": false,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ],
"Interval": "month"
},
"PremiumAffiliate": {
"Name": "Premium + Afiliados",
"PriceId": "price_premium_affiliate_monthly",
"Price": 29.90,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": true,
"AllowAnalytics": true,
"SpecialModeration": true,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ],
"Interval": "month"
}, },
"BasicYearly": { "BasicYearly": {
"Name": "Básico Anual",
"PriceId": "price_basic_yearly_placeholder", "PriceId": "price_basic_yearly_placeholder",
"Price": 99.00, "Price": 59.00,
"MaxLinks": 5, "MaxPages": 3,
"Features": [ "basic_themes", "simple_analytics" ], "MaxLinks": 8,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
}, },
"ProfessionalYearly": { "ProfessionalYearly": {
"Name": "Profissional Anual",
"PriceId": "price_professional_yearly_placeholder", "PriceId": "price_professional_yearly_placeholder",
"Price": 249.00, "Price": 129.00,
"MaxLinks": 15, "MaxPages": 5,
"Features": [ "all_themes", "advanced_analytics" ], "MaxLinks": 20,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
}, },
"PremiumYearly": { "PremiumYearly": {
"Name": "Premium Anual",
"PriceId": "price_premium_yearly_placeholder", "PriceId": "price_premium_yearly_placeholder",
"Price": 299.00, "Price": 199.00,
"MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"Features": [ "custom_themes", "full_analytics", "priority_support" ], "AllowPremiumThemes": true,
"AllowProductLinks": false,
"AllowAnalytics": true,
"SpecialModeration": false,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ],
"Interval": "year"
},
"PremiumAffiliateYearly": {
"Name": "Premium + Afiliados Anual",
"PriceId": "price_premium_affiliate_yearly",
"Price": 299.00,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": true,
"AllowAnalytics": true,
"SpecialModeration": true,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
} }
}, },

View File

@ -352,6 +352,66 @@ main {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
} }
/* Correção para problema visual do card Premium na página de pricing */
.pricing-cards {
margin-top: 80px;
}
.pricing-premium-badge {
top: -20px !important;
}
/* Garantir que badges não sejam cortados */
.pricing-cards .position-absolute {
z-index: 10;
}
.pricing-cards .card {
overflow: visible;
}
/* Layout otimizado para 5 cards */
@media (min-width: 1200px) {
/* XL+: 5 cards em uma linha com largura igual */
.pricing-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.pricing-cards .col-xl-2 {
flex: 0 0 18%;
max-width: 18%;
margin: 0 1%;
}
}
@media (min-width: 992px) and (max-width: 1199.98px) {
/* LG: 3 na primeira linha, 2 na segunda (centralizado) */
.pricing-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.pricing-cards .col-lg-4 {
flex: 0 0 30%;
max-width: 30%;
margin: 0 1.5% 20px 1.5%;
}
/* Quebra de linha após o 3º elemento */
.pricing-cards .col-lg-4:nth-child(4) {
margin-top: 20px;
}
}
@media (max-width: 991.98px) {
.pricing-cards {
margin-top: 60px;
}
}
/* Hover effects para home menu */ /* Hover effects para home menu */
.bg-home-blue .nav-link:hover { .bg-home-blue .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);