feat: botão fechar scroll de temas

This commit is contained in:
Ricardo Carneiro 2025-08-17 19:36:30 -03:00
parent ca98001299
commit ef4d189ef1
5 changed files with 234 additions and 17 deletions

View File

@ -2,6 +2,7 @@ using BCards.Web.Models;
using BCards.Web.Repositories;
using BCards.Web.Services;
using BCards.Web.ViewModels;
using BCards.Web.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@ -110,12 +111,16 @@ public class PaymentController : Controller
try
{
// Parse do plano atual (mesmo que o Dashboard)
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var currentPlanString = userPlanType.ToString().ToLower();
var viewModel = new ManageSubscriptionViewModel
{
User = user,
StripeSubscription = await _paymentService.GetSubscriptionDetailsAsync(user.Id),
PaymentHistory = await _paymentService.GetPaymentHistoryAsync(user.Id),
AvailablePlans = GetAvailablePlans(user.CurrentPlan)
AvailablePlans = GetAvailablePlans(currentPlanString)
};
// Pegar assinatura local se existir
@ -129,11 +134,15 @@ public class PaymentController : Controller
}
catch (Exception ex)
{
// Parse do plano atual também no catch
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var currentPlanString = userPlanType.ToString().ToLower();
var errorViewModel = new ManageSubscriptionViewModel
{
User = user,
ErrorMessage = $"Erro ao carregar dados da assinatura: {ex.Message}",
AvailablePlans = GetAvailablePlans(user.CurrentPlan)
AvailablePlans = GetAvailablePlans(currentPlanString)
};
return View(errorViewModel);
@ -212,6 +221,8 @@ public class PaymentController : Controller
{
var plans = new List<AvailablePlanViewModel>
{
// Plano Trial não é incluído aqui pois é gerenciado internamente
// O "downgrade" para Trial acontece via cancelamento da assinatura
new()
{
PlanType = "basic",
@ -250,8 +261,20 @@ public class PaymentController : Controller
}
};
// Marcar upgrades e downgrades
// Marcar upgrades e filtrar downgrades
var currentPlanIndex = plans.FindIndex(p => p.IsCurrentPlan);
// Se usuário está no Trial (não encontrou plano atual), todos são upgrades
if (currentPlanIndex == -1 && (currentPlan == "trial" || currentPlan == "free"))
{
foreach (var plan in plans)
{
plan.IsUpgrade = true;
}
return plans; // Mostrar todos os planos pagos como upgrade
}
// Para planos pagos, marcar apenas upgrades superiores
for (int i = 0; i < plans.Count; i++)
{
if (i > currentPlanIndex)
@ -260,6 +283,7 @@ public class PaymentController : Controller
plans[i].IsDowngrade = true;
}
return plans;
// Retornar apenas plano atual e upgrades (Stripe não gerencia downgrades automaticamente)
return plans.Where(p => p.IsCurrentPlan || p.IsUpgrade).ToList();
}
}

View File

@ -25,10 +25,10 @@ public class User
public string StripeCustomerId { get; set; } = string.Empty;
[BsonElement("subscriptionStatus")]
public string SubscriptionStatus { get; set; } = "free";
public string SubscriptionStatus { get; set; } = "trial";
[BsonElement("currentPlan")]
public string CurrentPlan { get; set; } = "free";
public string CurrentPlan { get; set; } = "trial";
[BsonElement("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

View File

@ -322,7 +322,8 @@ public class PaymentService : IPaymentService
user.SubscriptionStatus = stripeSubscription.Status;
if (stripeSubscription.Status != "active")
{
user.CurrentPlan = "free";
// Quando assinatura não está ativa, usuário volta para o plano trial (gratuito)
user.CurrentPlan = "trial";
}
await _userRepository.UpdateAsync(user);
}

View File

@ -162,6 +162,8 @@
<div class="accordion-body">
<p class="text-muted mb-4">Escolha um tema que combine com sua personalidade ou marca:</p>
<!-- Container com scroll para os temas -->
<div class="themes-container" style="max-height: 400px; overflow-y: auto; overflow-x: hidden; padding-right: 10px;">
@{
var themeCount = 0;
}
@ -204,6 +206,7 @@
{
@:</div>
}
</div> <!-- /themes-container -->
<input asp-for="SelectedTheme" type="hidden">
@ -1605,6 +1608,70 @@
</script>
}
@section Styles {
<style>
/* Estilo customizado para o scroll dos temas */
.themes-container {
scrollbar-width: thin;
scrollbar-color: #007bff #f8f9fa;
}
.themes-container::-webkit-scrollbar {
width: 8px;
}
.themes-container::-webkit-scrollbar-track {
background: #f8f9fa;
border-radius: 4px;
}
.themes-container::-webkit-scrollbar-thumb {
background: #007bff;
border-radius: 4px;
border: 1px solid #f8f9fa;
}
.themes-container::-webkit-scrollbar-thumb:hover {
background: #0056b3;
}
/* Fade gradient no topo e bottom para indicar scroll */
.themes-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 20px;
background: linear-gradient(to bottom, rgba(248, 249, 250, 1), rgba(248, 249, 250, 0));
pointer-events: none;
z-index: 1;
}
.themes-container::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 20px;
background: linear-gradient(to top, rgba(248, 249, 250, 1), rgba(248, 249, 250, 0));
pointer-events: none;
z-index: 1;
}
/* Smooth scroll */
.themes-container {
scroll-behavior: smooth;
}
/* Container relativo para os gradients */
.accordion-body {
position: relative;
}
</style>
}
@if (TempData["Error"] != null)
{
<div class="toast-container position-fixed top-0 end-0 p-3">

View File

@ -42,6 +42,42 @@
</style>
}
@if (isPreview)
{
<style>
/* Compensar espaço da barra de preview */
body {
padding-top: 60px !important;
}
/* Responsivo para mobile */
@@media (max-width: 768px) {
.position-fixed .container-fluid .row .col:not(.col-auto) {
display: none;
}
.position-fixed .container-fluid .row .col-auto:first-child {
flex: 1;
}
body {
padding-top: 50px !important;
}
}
/* Animação suave para o toast */
.toast {
transition: all 0.3s ease;
}
/* Melhorar aparência dos botões na barra */
.btn-outline-dark:hover {
background-color: rgba(0,0,0,0.1);
border-color: rgba(0,0,0,0.3);
}
</style>
}
<div class="user-page min-vh-100 d-flex align-items-center py-4">
<div class="container">
<div class="row justify-content-center">
@ -240,8 +276,48 @@
@if (isPreview)
{
<div class="position-fixed top-0 start-0 w-100 bg-warning text-dark text-center py-2" style="z-index: 9999;">
<strong>MODO PREVIEW</strong> - Esta é uma prévia da sua página
<!-- Barra de Preview Melhorada -->
<div class="position-fixed top-0 start-0 w-100 bg-warning text-dark py-2" style="z-index: 9999; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-auto">
<i class="fas fa-eye me-2"></i>
<strong>MODO PREVIEW</strong>
</div>
<div class="col text-center">
<span class="small">Esta é uma prévia da sua página</span>
</div>
<div class="col-auto">
<button type="button" class="btn btn-sm btn-outline-dark me-2" onclick="goBackToDashboard()">
<i class="fas fa-arrow-left me-1"></i>
Voltar ao Dashboard
</button>
<button type="button" class="btn btn-sm btn-dark" onclick="closePreview()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Toast Informativo -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index: 9998;">
<div id="previewToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
<div class="toast-header bg-info text-white">
<i class="fas fa-info-circle me-2"></i>
<strong class="me-auto">Modo Preview</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
<p class="mb-2"><strong>Você está visualizando uma prévia!</strong></p>
<p class="mb-2 small">• Para fazer alterações, volte ao Dashboard</p>
<p class="mb-2 small">• Esta prévia não é a página final publicada</p>
<button type="button" class="btn btn-sm btn-primary w-100" onclick="goBackToDashboard()">
<i class="fas fa-edit me-1"></i>
Editar Página
</button>
</div>
</div>
</div>
}
@ -335,6 +411,55 @@
}
});
});
@if (isPreview)
{
@:// Mostrar toast informativo após carregar
@:setTimeout(function() {
@: var toastEl = document.getElementById('previewToast');
@: if (toastEl) {
@: var toast = new bootstrap.Toast(toastEl);
@: toast.show();
@: }
@:}, 1000); // Delay de 1 segundo
}
});
@if (isPreview)
{
@:// Funções específicas do modo preview
@:function goBackToDashboard() {
@: // Detectar se está em mobile ou desktop
@: if (window.opener) {
@: // Está numa nova aba/janela - fechar e focar na aba pai
@: window.opener.focus();
@: window.close();
@: } else {
@: // Navegação normal - ir para o dashboard
@: window.location.href = '@Url.Action("Dashboard", "Admin")';
@: }
@:}
@:
@:function closePreview() {
@: if (window.opener) {
@: // Está numa nova aba - fechar
@: window.close();
@: } else {
@: // Tentar voltar na história ou ir para dashboard
@: if (window.history.length > 1) {
@: window.history.back();
@: } else {
@: window.location.href = '@Url.Action("Dashboard", "Admin")';
@: }
@: }
@:}
@:
@:// Detectar tecla ESC para fechar preview
@:document.addEventListener('keydown', function(e) {
@: if (e.key === 'Escape') {
@: closePreview();
@: }
@:});
}
</script>
}