feat: opacidade enquanto não selecionar o tipo.
Some checks failed
Deploy QR Rapido / test (push) Successful in 3m34s
Deploy QR Rapido / build-and-push (push) Failing after 8s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Has been skipped

This commit is contained in:
Ricardo Carneiro 2025-08-04 19:45:46 -03:00
parent 8ab0b913e2
commit 70fbdaa3c2
12 changed files with 387 additions and 28 deletions

View File

@ -121,7 +121,7 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
// ADICIONE ESTAS LINHAS:
options.Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri + "&prompt=select_account");
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};

View File

@ -35,7 +35,6 @@
<ItemGroup>
<Folder Include="wwwroot\images\" />
<Folder Include="wwwroot\css\" />
<Folder Include="wwwroot\js\" />
<Folder Include="Data\" />
<Folder Include="Services\" />
<Folder Include="Models\" />

View File

@ -14,7 +14,6 @@
- 👑 **Sistema Premium**: Stripe integrado com recursos avançados
- 📱 **PWA Ready**: Funciona como aplicativo móvel
- 🎨 **UI Moderna**: Bootstrap 5 com design focado em velocidade
- 📊 **Analytics**: Google Analytics 4 integrado
- 🏗️ **Escalável**: MongoDB + Redis para alta performance
## 🎯 Funcionalidades por Usuário
@ -36,7 +35,6 @@
- ✅ **Sem anúncios permanentemente**
- ✅ Geração prioritária (0.4s)
- ✅ QR codes dinâmicos (editáveis)
- ✅ Analytics avançados
- ✅ Suporte prioritário
- ✅ API access

View File

@ -304,7 +304,7 @@ namespace QRRapidoApp.Resources {
}
/// <summary>
/// Looks up a localized string similar to Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!.
/// Looks up a localized string similar to Remove ads, access advanced customization and much more for only $12.90/month or $129.00/year. Login and subscribe now!.
/// </summary>
public static string LoginBenefits {
get {

View File

@ -250,7 +250,7 @@
<value>Premium Offer!</value>
</data>
<data name="LoginBenefits" xml:space="preserve">
<value>Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
<value>Remove ads, access advanced customization and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
</data>
<data name="Privacy" xml:space="preserve">
<value>Privacy Policy</value>

View File

@ -0,0 +1,262 @@
@model QRRapidoApp.Models.User
@{
ViewData["Title"] = "Perfil do Usuário";
var isPremium = ViewBag.IsPremium as bool? ?? false;
var monthlyQRCount = ViewBag.MonthlyQRCount as int? ?? 0;
var qrHistory = ViewBag.QRHistory as List<QRRapidoApp.Models.QRCodeHistory> ?? new List<QRRapidoApp.Models.QRCodeHistory>();
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 mx-auto">
<!-- Header do Perfil -->
<div class="card mb-4 border-0 shadow-sm">
<div class="card-header bg-primary text-white d-flex align-items-center">
<i class="fas fa-user-circle fa-2x me-3"></i>
<div>
<h4 class="mb-0">@Model.Name</h4>
<small class="opacity-75">@Model.Email</small>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<!-- Status do Plano -->
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-crown me-2 @(isPremium ? "text-warning" : "text-muted")"></i>
<strong>Status do Plano:</strong>
</div>
@if (isPremium)
{
<span class="badge bg-warning text-dark fs-6">
<i class="fas fa-star me-1"></i>Premium
</span>
@if (Model.PremiumExpiresAt.HasValue)
{
<p class="text-muted small mt-1 mb-0">
Expira em: @Model.PremiumExpiresAt.Value.ToString("dd/MM/yyyy")
</p>
}
}
else
{
<span class="badge bg-secondary fs-6">
<i class="fas fa-user me-1"></i>Gratuito
</span>
<p class="text-muted small mt-1 mb-0">
<a href="/Premium/Upgrade" class="text-decoration-none">
<i class="fas fa-arrow-up me-1"></i>Fazer upgrade
</a>
</p>
}
</div>
<!-- Data de Cadastro -->
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-calendar-alt me-2 text-info"></i>
<strong>Membro desde:</strong>
</div>
<p class="text-muted mb-0">@Model.CreatedAt.ToString("dd/MM/yyyy")</p>
</div>
<!-- Provedor de Login -->
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fab fa-@(Model.Provider.ToLower()) me-2 text-primary"></i>
<strong>Conectado via:</strong>
</div>
<p class="text-muted mb-0">@Model.Provider</p>
</div>
<!-- Último Login -->
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-sign-in-alt me-2 text-success"></i>
<strong>Último acesso:</strong>
</div>
<p class="text-muted mb-0">@Model.LastLoginAt.ToString("dd/MM/yyyy HH:mm")</p>
</div>
</div>
</div>
</div>
<!-- Estatísticas de Uso -->
<div class="card mb-4 border-0 shadow-sm">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="fas fa-chart-bar me-2"></i>Estatísticas de Uso
</h5>
</div>
<div class="card-body">
<div class="row text-center g-3">
<div class="col-md-4">
<div class="p-3 border rounded bg-light profile-stat">
<h3 class="text-primary mb-1">@Model.TotalQRGenerated</h3>
<small class="text-muted">QR Codes Criados</small>
</div>
</div>
<div class="col-md-4">
<div class="p-3 border rounded bg-light profile-stat">
<h3 class="text-success mb-1">@monthlyQRCount</h3>
<small class="text-muted">Este Mês</small>
</div>
</div>
<div class="col-md-4">
<div class="p-3 border rounded bg-light profile-stat">
<h3 class="text-info mb-1">@Model.DailyQRCount</h3>
<small class="text-muted">Hoje</small>
</div>
</div>
</div>
</div>
</div>
<!-- Histórico Recente -->
@if (qrHistory.Any())
{
<div class="card mb-4 border-0 shadow-sm">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-history me-2"></i>Histórico Recente
</h5>
<a href="/Account/History" class="btn btn-light btn-sm">
<i class="fas fa-list me-1"></i>Ver Todos
</a>
</div>
<div class="card-body">
<div class="list-group list-group-flush">
@foreach (var qr in qrHistory.Take(5))
{
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
<div>
<div class="d-flex align-items-center">
<i class="fas fa-qrcode text-primary me-2"></i>
<div>
<h6 class="mb-1">@qr.Type.ToUpper()</h6>
<p class="mb-0 text-muted small">
@(qr.Content.Length > 50 ? qr.Content.Substring(0, 50) + "..." : qr.Content)
</p>
</div>
</div>
</div>
<small class="text-muted">@qr.CreatedAt.ToString("dd/MM HH:mm")</small>
</div>
}
</div>
</div>
</div>
}
<!-- Ações do Perfil -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">
<i class="fas fa-cogs me-2"></i>Ações
</h5>
</div>
<div class="card-body">
<div class="row g-3">
@if (!isPremium)
{
<div class="col-md-6">
<a href="/Premium/Upgrade" class="btn btn-warning w-100">
<i class="fas fa-crown me-2"></i>Upgrade para Premium
</a>
</div>
}
<div class="col-md-6">
<a href="/Account/History" class="btn btn-info w-100">
<i class="fas fa-history me-2"></i>Ver Histórico Completo
</a>
</div>
<div class="col-md-6">
<a href="/" class="btn btn-primary w-100">
<i class="fas fa-qrcode me-2"></i>Criar Novo QR Code
</a>
</div>
<div class="col-md-6">
<a href="/Account/Logout" class="btn btn-outline-danger w-100">
<i class="fas fa-sign-out-alt me-2"></i>Sair
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
}
.badge {
font-size: 0.85em;
padding: 8px 12px;
font-weight: 500;
}
.bg-light {
background-color: #f8f9fa !important;
border: 1px solid #e9ecef;
}
.list-group-item {
border: none;
border-bottom: 1px solid #dee2e6;
padding: 15px 0;
transition: background-color 0.2s ease;
}
.list-group-item:hover {
background-color: rgba(0,123,255,0.05);
}
.list-group-item:last-child {
border-bottom: none;
}
.profile-stat {
transition: all 0.3s ease;
}
.profile-stat:hover {
background-color: #e3f2fd !important;
border-color: #2196f3 !important;
}
.btn {
transition: all 0.2s ease;
}
.card-header {
font-weight: 600;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
@@media (max-width: 768px) {
.container {
padding: 0 15px;
}
.col-md-6 {
margin-bottom: 1rem;
}
.card-body .row .col-md-4 {
margin-bottom: 1rem;
}
}
</style>

View File

@ -105,7 +105,7 @@
<span data-type-guide-text>@Localizer["TypeGuideText"]</span>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="col-md-6 mb-3 opacity-controlled disabled-state" id="quick-style-group">
<label class="form-label fw-semibold">
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
</label>
@ -122,7 +122,7 @@
</div>
</div>
<div class="mb-3" id="content-group">
<div class="mb-3 opacity-controlled disabled-state" id="content-group">
<label class="form-label fw-semibold">
<i class="fas fa-edit"></i> @Localizer["Content"]
</label>
@ -138,7 +138,7 @@
</div>
<!-- Dynamic QR Section (Premium) -->
<div id="dynamic-qr-section" style="display: none;">
<div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state">
<div class="premium-feature-box">
<div class="d-flex align-items-center justify-content-between">
<div>
@ -146,30 +146,30 @@
<i class="fas fa-chart-line text-warning"></i> QR Dinâmico - Premium
</h6>
<small class="text-muted">
Analytics em tempo real: cliques, localização, dispositivos, etc.
QR Code com seu logotipo!
</small>
</div>
<div class="form-check form-switch">
@if (ViewBag.IsPremium == true)
{
<input class="form-check-input" type="checkbox" id="qr-dynamic-toggle">
<input class="form-check-input" type="checkbox" id="qr-dynamic-toggle" style="display: none;">
<label class="form-check-label" for="qr-dynamic-toggle">
Ativar Analytics
Adicione o logotivo na Personalização avançada
</label>
}
else
{
<div class="premium-upgrade-prompt">
<input class="form-check-input" type="checkbox" disabled>
<label class="form-check-label text-muted">
Ativar Analytics
<input class="form-check-input" type="checkbox" disabled style="display: none;">
<label class="form-check-label" for="qr-dynamic-toggle">
Gerar QR Code com logotipo? Assine o plano premium!
</label>
<br>
<small>
<a href="/Pagamento/SelecaoPlano" class="text-warning">
Upgrade Premium
</a> para analytics
</a> QR Code com logotipo
</small>
</div>
}
@ -179,7 +179,7 @@
</div>
<!-- URL Preview -->
<div id="url-preview" class="mt-3" style="display: none;">
<div id="url-preview" class="mt-3 opacity-controlled disabled-state" style="display: none;">
<h6><i class="fas fa-link"></i> Preview</h6>
<div id="url-preview-content" class="bg-light p-3 rounded">
<strong>URL Final:</strong> <span id="final-url-display"></span><br>
@ -188,7 +188,7 @@
</div>
<!-- VCard Interface (dynamic) -->
<div id="vcard-interface" class="mb-3" style="display: none;">
<div id="vcard-interface" class="mb-3 opacity-controlled disabled-state" style="display: none;">
<div class="alert alert-info">
<i class="fas fa-address-card"></i>
<strong>Cartão de Visita Digital</strong> - Este QR Code criará um cartão de visita digital.
@ -330,7 +330,7 @@
</div>
<!-- WiFi Interface (dynamic) -->
<div id="wifi-interface" class="mb-3" style="display: none;">
<div id="wifi-interface" class="mb-3 opacity-controlled disabled-state" style="display: none;">
<div class="alert alert-info">
<i class="fas fa-wifi"></i>
<strong>@Localizer["WiFiQRTitle"]</strong> - @Localizer["WiFiQRDescription"]
@ -469,7 +469,7 @@
</div>
<!-- Email Interface (dynamic) -->
<div id="email-interface" class="mb-3" style="display: none;">
<div id="email-interface" class="mb-3 opacity-controlled disabled-state" style="display: none;">
<div class="alert alert-info">
<strong>QR Code Email:</strong> Permite enviar email automático com destinatário, assunto e mensagem pré-definidos.
Compatível com Android, iOS e dispositivos modernos.
@ -500,7 +500,7 @@
</div>
<!-- Advanced customization (collapsible) -->
<div class="accordion mb-3" id="customization-accordion">
<div class="accordion mb-3 opacity-controlled disabled-state" id="customization-accordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
@ -702,7 +702,7 @@
</div>
</div>
<div class="d-grid">
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-div">
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn">
<i class="fas fa-bolt"></i> @Localizer["GenerateQRCodeQuickly"]
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status">
@ -961,6 +961,9 @@
<!-- Ad Space Footer (conditional) -->
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
<script src="~/js/simple-opcacity.js"></script>
<!-- Script para controles de logo aprimorados -->
<script>
// JavaScript para controles de logo aprimorados
@ -974,6 +977,21 @@ document.addEventListener('DOMContentLoaded', function() {
const previewSizeText = document.getElementById('preview-size-text');
const previewColorizeText = document.getElementById('preview-colorize-text');
new SimpleOpacityController('#qr-type', '#quick-style-group');
new SimpleOpacityController('#qr-type', '#content-group');
new SimpleOpacityController('#qr-type', '#dynamic-qr-section');
new SimpleOpacityController('#qr-type', '#url-preview');
new SimpleOpacityController('#qr-type', '#vcard-interface');
new SimpleOpacityController('#qr-type', '#wifi-interface');
new SimpleOpacityController('#qr-type', '#sms-interface');
new SimpleOpacityController('#qr-type', '#email-interface');
new SimpleOpacityController('#qr-type', '#customization-accordion');
new SimpleOpacityController('#qr-type', '#button-gerar-div');
// Controle do slider de tamanho
logoSizeSlider?.addEventListener('input', function() {
const size = this.value;

View File

@ -333,6 +333,7 @@
} else {
console.error('❌ Botão não encontrado pelo script inline!');
}
});
</script>

View File

@ -12,8 +12,8 @@
},
"Authentication": {
"Google": {
"ClientId": "your-google-client-id",
"ClientSecret": "your-google-client-secret"
"ClientId": "1080447252222-dqjsu999tvrpb69oj5iapckdh9g8rvha.apps.googleusercontent.com",
"ClientSecret": "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"
},
"Microsoft": {
"ClientId": "9bec3835-acdb-4c5a-8668-6b90955c6ad2",

View File

@ -284,3 +284,15 @@ body {
.text-success {
color: #28a745 !important;
}
/* CLASSE PARA CONTROLAR OPACIDADE */
.opacity-controlled {
transition: opacity 0.3s ease-in-out;
opacity: 1;
}
/* ESTADO OPACO (quando nada selecionado) */
.opacity-controlled.disabled-state {
opacity: 0.2 !important; /* Bem opaco */
pointer-events: none; /* Desabilita interação */
}

View File

@ -108,8 +108,13 @@ class QRRapidoGenerator {
// Content validation
const qrContent = document.getElementById('qr-content');
if (qrContent) {
let timer;
qrContent.addEventListener('input', (e) => {
clearTimeout(timer);
timer = setTimeout(() => {
this.handleContentChange(e.target.value);
this.updateGenerateButton();
}, 300);
});
}

View File

@ -0,0 +1,64 @@
class SimpleOpacityController {
constructor(controlSelector, targetSelector) {
this.controlSelector = controlSelector;
this.targetSelector = targetSelector;
this.disabledClass = 'disabled-state';
this.init();
}
init() {
const controls = document.querySelectorAll(this.controlSelector);
const targets = document.querySelectorAll(this.targetSelector);
// Listener para detectar mudanças
controls.forEach(control => {
control.addEventListener('change', () => {
this.updateOpacity();
});
});
// Estado inicial
this.updateOpacity();
console.log('✅ Sistema simples de opacidade inicializado');
console.log('Controlando:', controls.length, 'controles e', targets.length, 'alvos');
}
updateOpacity() {
const hasSelection = this.hasSelection();
const targets = document.querySelectorAll(this.targetSelector);
targets.forEach(target => {
if (hasSelection) {
// TEM seleção = div normal
target.classList.remove(this.disabledClass);
console.log('🟢 Div ativada - seleção detectada');
} else {
// NÃO tem seleção = div opaca
target.classList.add(this.disabledClass);
console.log('🔴 Div desativada - nenhuma seleção');
}
});
}
hasSelection() {
// Para SELECT: verificar se valor não está vazio
const selects = document.querySelectorAll(this.controlSelector + '[type=""], ' + this.controlSelector + ':not([type])');
for (let select of selects) {
if (select.value && select.value.trim() !== '') {
return true;
}
}
// Para RADIO BUTTONS: verificar se algum está selecionado E não é vazio
const radios = document.querySelectorAll(this.controlSelector + '[type="radio"]');
for (let radio of radios) {
if (radio.checked && radio.value && radio.value.trim() !== '') {
return true;
}
}
return false;
}
}