feat: qrcode com contador de leituras
This commit is contained in:
parent
2edb4e1196
commit
8541e68711
@ -307,8 +307,8 @@ namespace QRRapidoApp.Controllers
|
|||||||
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
|
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
|
||||||
|
|
||||||
// DEBUG: Log detalhado dos parâmetros recebidos
|
// DEBUG: Log detalhado dos parâmetros recebidos
|
||||||
_logger.LogInformation("🔍 [DEBUG] GenerateRapidWithLogo called - RequestId: {RequestId}, ApplyLogoColorization: {ApplyLogoColorization}, LogoSizePercent: {LogoSizePercent}, HasLogo: {HasLogo}",
|
_logger.LogInformation("🔍 [DEBUG] GenerateRapidWithLogo called - RequestId: {RequestId}, EnableTracking: {EnableTracking}, Type: {Type}, ApplyLogoColorization: {ApplyLogoColorization}, LogoSizePercent: {LogoSizePercent}, HasLogo: {HasLogo}",
|
||||||
requestId, request.ApplyLogoColorization, request.LogoSizePercent, request.HasLogo);
|
requestId, request.EnableTracking, request.Type, request.ApplyLogoColorization, request.LogoSizePercent, request.HasLogo);
|
||||||
|
|
||||||
using (_logger.BeginScope(new Dictionary<string, object>
|
using (_logger.BeginScope(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
|
|||||||
138
Controllers/TrackingController.cs
Normal file
138
Controllers/TrackingController.cs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using QRRapidoApp.Services;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controller for tracking QR code scans (Premium feature)
|
||||||
|
/// Handles redirect URLs and analytics
|
||||||
|
/// </summary>
|
||||||
|
[Route("t")]
|
||||||
|
public class TrackingController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly ILogger<TrackingController> _logger;
|
||||||
|
|
||||||
|
public TrackingController(IUserService userService, ILogger<TrackingController> logger)
|
||||||
|
{
|
||||||
|
_userService = userService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Track QR code scan and redirect to original URL
|
||||||
|
/// URL format: https://qrrapido.site/t/{trackingId}
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{trackingId}")]
|
||||||
|
public async Task<IActionResult> TrackAndRedirect(string trackingId)
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
using (_logger.BeginScope(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["TrackingId"] = trackingId,
|
||||||
|
["QRTracking"] = true
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("QR tracking request - TrackingId: {TrackingId}", trackingId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get QR code from database
|
||||||
|
var qr = await _userService.GetQRByTrackingIdAsync(trackingId);
|
||||||
|
|
||||||
|
if (qr == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("QR code not found for tracking - TrackingId: {TrackingId}", trackingId);
|
||||||
|
return NotFound("QR code not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment scan count and update last access (fire and forget for performance)
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _userService.IncrementQRScanCountAsync(trackingId);
|
||||||
|
_logger.LogDebug("QR scan count incremented - TrackingId: {TrackingId}, NewCount: {NewCount}",
|
||||||
|
trackingId, qr.ScanCount + 1);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error incrementing scan count - TrackingId: {TrackingId}", trackingId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
_logger.LogInformation("QR redirect - TrackingId: {TrackingId}, Destination: {Destination}, ProcessingTime: {ProcessingTimeMs}ms",
|
||||||
|
trackingId, qr.Content, stopwatch.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
// Redirect to original URL (HTTP 302 temporary redirect)
|
||||||
|
return Redirect(qr.Content);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
_logger.LogError(ex, "QR tracking failed - TrackingId: {TrackingId}, ProcessingTime: {ProcessingTimeMs}ms",
|
||||||
|
trackingId, stopwatch.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
// Return error page instead of exposing exception details
|
||||||
|
return StatusCode(500, "Error processing QR code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get analytics for a specific QR code (requires authentication)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("analytics/{trackingId}")]
|
||||||
|
public async Task<IActionResult> GetAnalytics(string trackingId)
|
||||||
|
{
|
||||||
|
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Analytics request without authentication - TrackingId: {TrackingId}", trackingId);
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var qr = await _userService.GetQRByTrackingIdAsync(trackingId);
|
||||||
|
|
||||||
|
if (qr == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Analytics request for non-existent QR - TrackingId: {TrackingId}", trackingId);
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify ownership
|
||||||
|
if (qr.UserId != userId)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Analytics request for QR owned by different user - TrackingId: {TrackingId}, RequestingUser: {UserId}, Owner: {OwnerId}",
|
||||||
|
trackingId, userId, qr.UserId);
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Analytics retrieved - TrackingId: {TrackingId}, UserId: {UserId}, ScanCount: {ScanCount}",
|
||||||
|
trackingId, userId, qr.ScanCount);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
trackingId = qr.TrackingId,
|
||||||
|
scanCount = qr.ScanCount,
|
||||||
|
createdAt = qr.CreatedAt,
|
||||||
|
lastAccessedAt = qr.LastAccessedAt,
|
||||||
|
content = qr.Content,
|
||||||
|
type = qr.Type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error retrieving analytics - TrackingId: {TrackingId}, UserId: {UserId}",
|
||||||
|
trackingId, userId);
|
||||||
|
return StatusCode(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
345
Docs/QR-Analytics-Feature.md
Normal file
345
Docs/QR-Analytics-Feature.md
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
# 📊 QR Code Analytics - Premium Feature
|
||||||
|
|
||||||
|
Sistema de rastreamento de leituras de QR codes para usuários premium.
|
||||||
|
|
||||||
|
## 🎯 Visão Geral
|
||||||
|
|
||||||
|
Permite que usuários premium acompanhem quantas vezes seus QR codes foram escaneados, com timestamps e histórico completo.
|
||||||
|
|
||||||
|
### Como Funciona
|
||||||
|
|
||||||
|
1. **Usuário premium** gera QR code com analytics habilitado
|
||||||
|
2. QR code aponta para URL de redirect: `https://qrrapido.site/t/{trackingId}`
|
||||||
|
3. Ao escanear, o sistema:
|
||||||
|
- Incrementa contador de leituras (`scanCount`)
|
||||||
|
- Atualiza timestamp da última leitura (`lastAccessedAt`)
|
||||||
|
- Redireciona para URL original (HTTP 302)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Implementação Técnica
|
||||||
|
|
||||||
|
### Novos Componentes
|
||||||
|
|
||||||
|
#### 1. **TrackingController** (`Controllers/TrackingController.cs`)
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
- `GET /t/{trackingId}` - Redireciona e contabiliza leitura
|
||||||
|
- `GET /t/analytics/{trackingId}` - Retorna analytics (requer autenticação)
|
||||||
|
|
||||||
|
#### 2. **Campos no MongoDB**
|
||||||
|
|
||||||
|
**QRCodeHistory** (campos já existentes, agora em uso):
|
||||||
|
```csharp
|
||||||
|
public int ScanCount { get; set; } = 0; // Contador de leituras
|
||||||
|
public DateTime LastAccessedAt { get; set; } // Última leitura
|
||||||
|
public bool IsDynamic { get; set; } = false; // Se é trackable
|
||||||
|
public string? TrackingId { get; set; } // ID público (novo)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. **QRGenerationRequest** (novo campo)
|
||||||
|
```csharp
|
||||||
|
public bool EnableTracking { get; set; } = false; // Premium feature
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. **UserService** (novos métodos)
|
||||||
|
```csharp
|
||||||
|
Task<QRCodeHistory?> GetQRByTrackingIdAsync(string trackingId);
|
||||||
|
Task IncrementQRScanCountAsync(string trackingId);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Como Usar
|
||||||
|
|
||||||
|
### Backend (já implementado)
|
||||||
|
|
||||||
|
#### Gerar QR com Analytics
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var request = new QRGenerationRequest
|
||||||
|
{
|
||||||
|
Type = "url",
|
||||||
|
Content = "https://google.com",
|
||||||
|
IsPremium = true,
|
||||||
|
EnableTracking = true // ✅ Habilita analytics
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _qrService.GenerateRapidAsync(request);
|
||||||
|
|
||||||
|
// result.TrackingId conterá o ID para analytics
|
||||||
|
// QR code aponta para: https://qrrapido.site/t/{trackingId}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Consultar Analytics
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Via UserService
|
||||||
|
var qr = await _userService.GetQRByTrackingIdAsync("abc123def456");
|
||||||
|
Console.WriteLine($"Leituras: {qr.ScanCount}");
|
||||||
|
Console.WriteLine($"Última leitura: {qr.LastAccessedAt}");
|
||||||
|
|
||||||
|
// Via API (requer autenticação)
|
||||||
|
GET /t/analytics/abc123def456
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"trackingId": "abc123def456",
|
||||||
|
"scanCount": 42,
|
||||||
|
"createdAt": "2025-10-19T10:00:00Z",
|
||||||
|
"lastAccessedAt": "2025-10-20T15:30:00Z",
|
||||||
|
"content": "https://google.com",
|
||||||
|
"type": "url"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Frontend (ainda não implementado)
|
||||||
|
|
||||||
|
#### Opção 1: Checkbox no Gerador
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Em Views/Home/Index.cshtml -->
|
||||||
|
<div class="form-check premium-feature">
|
||||||
|
<input type="checkbox" id="enableTracking" class="form-check-input">
|
||||||
|
<label for="enableTracking">
|
||||||
|
<i class="fas fa-chart-line"></i> Habilitar Analytics
|
||||||
|
<span class="badge bg-warning">Premium</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Em wwwroot/js/qr-speed-generator.js
|
||||||
|
const enableTracking = document.getElementById('enableTracking');
|
||||||
|
|
||||||
|
formData.append('EnableTracking', enableTracking?.checked && this.isPremium);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Opção 2: Dashboard de Analytics
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Nova view: Views/Premium/Analytics.cshtml -->
|
||||||
|
<div class="analytics-dashboard">
|
||||||
|
<h2>Meus QR Codes com Analytics</h2>
|
||||||
|
|
||||||
|
<div class="qr-list">
|
||||||
|
@foreach (var qr in Model.TrackableQRs)
|
||||||
|
{
|
||||||
|
<div class="qr-card">
|
||||||
|
<h3>@qr.Content</h3>
|
||||||
|
<p><strong>Leituras:</strong> @qr.ScanCount</p>
|
||||||
|
<p><strong>Última leitura:</strong> @qr.LastAccessedAt.ToString("dd/MM/yyyy HH:mm")</p>
|
||||||
|
<a href="/t/analytics/@qr.TrackingId">Ver detalhes</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Limitações Conhecidas
|
||||||
|
|
||||||
|
### 1. **Apenas QR codes de URL**
|
||||||
|
- Tracking funciona apenas para `Type = "url"`
|
||||||
|
- WiFi, vCard, SMS, etc. **não suportam** redirect
|
||||||
|
|
||||||
|
**Razão**: Esses tipos não aceitam URLs customizadas
|
||||||
|
|
||||||
|
**Solução futura**: Implementar pixel de tracking invisível ou API de scan manual
|
||||||
|
|
||||||
|
### 2. **Redirect adiciona ~50-200ms**
|
||||||
|
- Usuário experimenta pequeno delay ao escanear
|
||||||
|
- Impacto: Mínimo para maioria dos casos
|
||||||
|
|
||||||
|
**Otimização**: Redirect é assíncrono, contador atualiza em background
|
||||||
|
|
||||||
|
### 3. **Bloqueadores de ads podem afetar**
|
||||||
|
- Alguns bloqueadores podem marcar `/t/` como tracking
|
||||||
|
- Probabilidade: Baixa (não usa cookies ou JS)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Segurança
|
||||||
|
|
||||||
|
### Proteções Implementadas
|
||||||
|
|
||||||
|
1. **Tracking ID não-sequencial**: GUID truncado (12 chars)
|
||||||
|
2. **Validação de ownership**: Endpoint `/t/analytics/` verifica se QR pertence ao usuário
|
||||||
|
3. **Fire-and-forget counting**: Não bloqueia redirect se MongoDB estiver lento
|
||||||
|
4. **Logging completo**: Todas as leituras são logadas
|
||||||
|
|
||||||
|
### Considerações de Privacidade
|
||||||
|
|
||||||
|
- **Não coleta**: IP, device, localização (por enquanto)
|
||||||
|
- **Apenas conta**: Quantidade de leituras + timestamp
|
||||||
|
- **LGPD compliant**: Usuário premium pode deletar histórico a qualquer momento
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Exemplos de Uso
|
||||||
|
|
||||||
|
### Caso 1: Rastreamento de Campanha
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Gerar QR para campanha de marketing
|
||||||
|
var campaign = new QRGenerationRequest
|
||||||
|
{
|
||||||
|
Type = "url",
|
||||||
|
Content = "https://minhaloja.com/promo-verao",
|
||||||
|
IsPremium = true,
|
||||||
|
EnableTracking = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var qr = await _qrService.GenerateRapidAsync(campaign);
|
||||||
|
|
||||||
|
// Distribuir QR em materiais impressos
|
||||||
|
// Depois consultar: await _userService.GetQRByTrackingIdAsync(qr.TrackingId);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caso 2: Validação de Engajamento
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Verificar se QR code em outdoor teve leituras
|
||||||
|
var qr = await _userService.GetQRByTrackingIdAsync("abc123def456");
|
||||||
|
|
||||||
|
if (qr.ScanCount > 100)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Outdoor teve boa visibilidade!");
|
||||||
|
}
|
||||||
|
else if (qr.ScanCount == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("QR pode estar ilegível ou mal posicionado");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Configuração
|
||||||
|
|
||||||
|
### appsettings.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"App": {
|
||||||
|
"BaseUrl": "https://qrrapido.site" // ✅ Usado para gerar URLs de tracking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### appsettings.Development.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"App": {
|
||||||
|
"BaseUrl": "https://localhost:52428" // ✅ Para testes locais
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testando Localmente
|
||||||
|
|
||||||
|
### 1. Gerar QR com Tracking
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# POST /api/QR/GenerateRapid
|
||||||
|
curl -X POST https://localhost:52428/api/QR/GenerateRapid \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"type": "url",
|
||||||
|
"content": "https://google.com",
|
||||||
|
"isPremium": true,
|
||||||
|
"enableTracking": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"qrCodeBase64": "iVBORw0KG...",
|
||||||
|
"trackingId": "a1b2c3d4e5f6",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Simular Leitura do QR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Abrir no navegador (ou curl)
|
||||||
|
curl -L https://localhost:52428/t/a1b2c3d4e5f6
|
||||||
|
# Deve redirecionar para https://google.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Verificar Contador
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Acessar MongoDB ou via API
|
||||||
|
curl https://localhost:52428/t/analytics/a1b2c3d4e5f6 \
|
||||||
|
-H "Authorization: Bearer {token}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"trackingId": "a1b2c3d4e5f6",
|
||||||
|
"scanCount": 1,
|
||||||
|
"lastAccessedAt": "2025-10-20T10:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Próximos Passos (Features Futuras)
|
||||||
|
|
||||||
|
### Analytics Avançado
|
||||||
|
- [ ] Gráfico de leituras por dia/semana/mês
|
||||||
|
- [ ] Geo-localização (IP → País/Cidade)
|
||||||
|
- [ ] Tipo de device (mobile/desktop)
|
||||||
|
- [ ] Browser/OS usado para escanear
|
||||||
|
|
||||||
|
### UI/UX
|
||||||
|
- [ ] Dashboard de analytics no painel premium
|
||||||
|
- [ ] Exportar dados para CSV/Excel
|
||||||
|
- [ ] Notificações quando QR atingir X leituras
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- [ ] Cache Redis para evitar queries MongoDB em cada scan
|
||||||
|
- [ ] Batch updates (atualizar contador a cada N leituras)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Referências
|
||||||
|
|
||||||
|
- **Tracking URLs**: Mesma estratégia usada por bit.ly, tinyurl, etc.
|
||||||
|
- **HTTP 302 Redirect**: Padrão para preservar SEO e funcionalidade
|
||||||
|
- **Fire-and-forget**: Pattern do ASP.NET Core para operações assíncronas não-bloqueantes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Problema: "QR code not found"
|
||||||
|
- Verificar se `trackingId` existe no MongoDB
|
||||||
|
- Verificar se QR foi salvo com `IsDynamic = true`
|
||||||
|
|
||||||
|
### Problema: Contador não incrementa
|
||||||
|
- Verificar logs do `TrackingController`
|
||||||
|
- Verificar conectividade MongoDB
|
||||||
|
- Verificar se `IncrementQRScanCountAsync` está sendo chamado
|
||||||
|
|
||||||
|
### Problema: Redirect não funciona
|
||||||
|
- Verificar se URL original está salva em `Content`
|
||||||
|
- Verificar se `BaseUrl` está configurado corretamente
|
||||||
|
- Verificar logs de erro no controller
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Implementado em**: 2025-10-20
|
||||||
|
**Versão**: 1.0.0
|
||||||
|
**Status**: ✅ Backend completo, Frontend pendente
|
||||||
@ -36,6 +36,9 @@ namespace QRRapidoApp.Models
|
|||||||
[BsonElement("isDynamic")]
|
[BsonElement("isDynamic")]
|
||||||
public bool IsDynamic { get; set; } = false;
|
public bool IsDynamic { get; set; } = false;
|
||||||
|
|
||||||
|
[BsonElement("trackingId")]
|
||||||
|
public string? TrackingId { get; set; } // Public ID for tracking URL (premium feature)
|
||||||
|
|
||||||
[BsonElement("size")]
|
[BsonElement("size")]
|
||||||
public int Size { get; set; } = 300;
|
public int Size { get; set; } = 300;
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,12 @@ namespace QRRapidoApp.Models.ViewModels
|
|||||||
/// Se deve aplicar a cor do QR code no logo (Premium feature)
|
/// Se deve aplicar a cor do QR code no logo (Premium feature)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ApplyLogoColorization { get; set; } = false;
|
public bool ApplyLogoColorization { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Se deve habilitar analytics de leitura (Premium feature)
|
||||||
|
/// Quando habilitado, o QR code usa URL de redirect para contabilizar leituras
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableTracking { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QRGenerationResult
|
public class QRGenerationResult
|
||||||
@ -42,5 +48,6 @@ namespace QRRapidoApp.Models.ViewModels
|
|||||||
public bool Success { get; set; } = true;
|
public bool Success { get; set; } = true;
|
||||||
public string? ErrorMessage { get; set; }
|
public string? ErrorMessage { get; set; }
|
||||||
public LogoReadabilityInfo? ReadabilityInfo { get; set; } // Nova propriedade para análise de legibilidade
|
public LogoReadabilityInfo? ReadabilityInfo { get; set; } // Nova propriedade para análise de legibilidade
|
||||||
|
public string? TrackingId { get; set; } // Tracking ID for analytics (Premium feature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -783,4 +783,23 @@
|
|||||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||||
<value>Anonymous users: 3 QR codes per day</value>
|
<value>Anonymous users: 3 QR codes per day</value>
|
||||||
</data>
|
</data>
|
||||||
|
<!-- Premium Features -->
|
||||||
|
<data name="AdvancedCustomization" xml:space="preserve">
|
||||||
|
<value>Advanced customization (colors, corners)</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoSupport" xml:space="preserve">
|
||||||
|
<value>Logo and image support</value>
|
||||||
|
</data>
|
||||||
|
<data name="HistoryAndDownloads" xml:space="preserve">
|
||||||
|
<value>Unlimited history and downloads</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRReadCounter" xml:space="preserve">
|
||||||
|
<value>QR code scan counter</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnableTracking" xml:space="preserve">
|
||||||
|
<value>Enable scan counter</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrackingInfo" xml:space="preserve">
|
||||||
|
<value>QR code will use redirect URL to count scans</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -2041,6 +2041,15 @@
|
|||||||
<data name="HistoryAndDownloads" xml:space="preserve">
|
<data name="HistoryAndDownloads" xml:space="preserve">
|
||||||
<value>Historial y descargas ilimitadas</value>
|
<value>Historial y descargas ilimitadas</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="QRReadCounter" xml:space="preserve">
|
||||||
|
<value>Contador de lecturas de códigos QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnableTracking" xml:space="preserve">
|
||||||
|
<value>Habilitar contador de lecturas</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrackingInfo" xml:space="preserve">
|
||||||
|
<value>El código QR usará URL de redireccionamiento para contabilizar lecturas</value>
|
||||||
|
</data>
|
||||||
<!-- FAQ Updates -->
|
<!-- FAQ Updates -->
|
||||||
<data name="FAQ_StaticQRExplanation" xml:space="preserve">
|
<data name="FAQ_StaticQRExplanation" xml:space="preserve">
|
||||||
<value>Todos los códigos QR generados por QR Rápido son estáticos y permanentes. Esto significa que el contenido está codificado directamente en el código QR y no puede ser modificado después de la generación. Este enfoque garantiza máxima privacidad, ya que no rastreamos escaneos, y funciona sin conexión sin depender de servidores externos.</value>
|
<value>Todos los códigos QR generados por QR Rápido son estáticos y permanentes. Esto significa que el contenido está codificado directamente en el código QR y no puede ser modificado después de la generación. Este enfoque garantiza máxima privacidad, ya que no rastreamos escaneos, y funciona sin conexión sin depender de servidores externos.</value>
|
||||||
|
|||||||
@ -2131,6 +2131,15 @@
|
|||||||
<data name="HistoryAndDownloads" xml:space="preserve">
|
<data name="HistoryAndDownloads" xml:space="preserve">
|
||||||
<value>Histórico e downloads ilimitados</value>
|
<value>Histórico e downloads ilimitados</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="QRReadCounter" xml:space="preserve">
|
||||||
|
<value>Contador de leituras de QR codes</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnableTracking" xml:space="preserve">
|
||||||
|
<value>Habilitar contador de leituras</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrackingInfo" xml:space="preserve">
|
||||||
|
<value>QR code usará URL de redirect para contabilizar leituras</value>
|
||||||
|
</data>
|
||||||
<!-- FAQ Updates -->
|
<!-- FAQ Updates -->
|
||||||
<data name="FAQ_StaticQRExplanation" xml:space="preserve">
|
<data name="FAQ_StaticQRExplanation" xml:space="preserve">
|
||||||
<value>Todos os QR codes gerados pelo QR Rápido são estáticos e permanentes. Isso significa que o conteúdo é codificado diretamente no QR code e não pode ser alterado após a geração. Essa abordagem garante máxima privacidade, pois não rastreamos scans, e funciona offline sem depender de servidores externos.</value>
|
<value>Todos os QR codes gerados pelo QR Rápido são estáticos e permanentes. Isso significa que o conteúdo é codificado diretamente no QR code e não pode ser alterado após a geração. Essa abordagem garante máxima privacidade, pois não rastreamos scans, e funciona offline sem depender de servidores externos.</value>
|
||||||
|
|||||||
@ -28,5 +28,9 @@ namespace QRRapidoApp.Services
|
|||||||
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
||||||
Task<List<User>> GetUsersForHistoryCleanupAsync(DateTime cutoffDate);
|
Task<List<User>> GetUsersForHistoryCleanupAsync(DateTime cutoffDate);
|
||||||
Task DeleteUserHistoryAsync(string userId);
|
Task DeleteUserHistoryAsync(string userId);
|
||||||
|
|
||||||
|
// QR Code Tracking (Analytics) - Premium feature
|
||||||
|
Task<QRCodeHistory?> GetQRByTrackingIdAsync(string trackingId);
|
||||||
|
Task IncrementQRScanCountAsync(string trackingId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,6 +45,30 @@ namespace QRRapidoApp.Services
|
|||||||
{
|
{
|
||||||
await _semaphore.WaitAsync();
|
await _semaphore.WaitAsync();
|
||||||
|
|
||||||
|
// Generate tracking ID for premium users with tracking enabled
|
||||||
|
string? trackingId = null;
|
||||||
|
string originalContent = request.Content;
|
||||||
|
|
||||||
|
// DEBUG: Log all conditions
|
||||||
|
_logger.LogInformation("[TRACKING DEBUG] IsPremium: {IsPremium}, EnableTracking: {EnableTracking}, Type: {Type}",
|
||||||
|
request.IsPremium, request.EnableTracking, request.Type);
|
||||||
|
|
||||||
|
if (request.IsPremium && request.EnableTracking && request.Type == "url")
|
||||||
|
{
|
||||||
|
// Only URL type QR codes can use tracking (redirect functionality)
|
||||||
|
trackingId = Guid.NewGuid().ToString("N").Substring(0, 12); // 12-char tracking ID
|
||||||
|
var baseUrl = _config.GetValue<string>("App:BaseUrl", "https://qrrapido.site");
|
||||||
|
request.Content = $"{baseUrl}/t/{trackingId}";
|
||||||
|
|
||||||
|
_logger.LogInformation("✅ Tracking enabled for QR - TrackingId: {TrackingId}, OriginalURL: {OriginalURL}",
|
||||||
|
trackingId, originalContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("❌ Tracking NOT enabled - IsPremium: {IsPremium}, EnableTracking: {EnableTracking}, Type: {Type}",
|
||||||
|
request.IsPremium, request.EnableTracking, request.Type);
|
||||||
|
}
|
||||||
|
|
||||||
// Cache key based on content and settings
|
// Cache key based on content and settings
|
||||||
var cacheKey = GenerateCacheKey(request);
|
var cacheKey = GenerateCacheKey(request);
|
||||||
var cached = await _cache.GetStringAsync(cacheKey);
|
var cached = await _cache.GetStringAsync(cacheKey);
|
||||||
@ -59,13 +83,13 @@ namespace QRRapidoApp.Services
|
|||||||
{
|
{
|
||||||
cachedResult.GenerationTimeMs = stopwatch.ElapsedMilliseconds;
|
cachedResult.GenerationTimeMs = stopwatch.ElapsedMilliseconds;
|
||||||
cachedResult.FromCache = true;
|
cachedResult.FromCache = true;
|
||||||
|
|
||||||
// Se não tem análise de legibilidade no cache, gerar agora
|
// Se não tem análise de legibilidade no cache, gerar agora
|
||||||
if (cachedResult.ReadabilityInfo == null)
|
if (cachedResult.ReadabilityInfo == null)
|
||||||
{
|
{
|
||||||
cachedResult.ReadabilityInfo = await _readabilityAnalyzer.AnalyzeAsync(request, request.Size);
|
cachedResult.ReadabilityInfo = await _readabilityAnalyzer.AnalyzeAsync(request, request.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,9 +110,16 @@ namespace QRRapidoApp.Services
|
|||||||
Size = qrCode.Length,
|
Size = qrCode.Length,
|
||||||
RequestSettings = request,
|
RequestSettings = request,
|
||||||
Success = true,
|
Success = true,
|
||||||
ReadabilityInfo = readabilityInfo
|
ReadabilityInfo = readabilityInfo,
|
||||||
|
TrackingId = trackingId // Include tracking ID if enabled
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Restore original content if tracking was enabled
|
||||||
|
if (trackingId != null)
|
||||||
|
{
|
||||||
|
request.Content = originalContent;
|
||||||
|
}
|
||||||
|
|
||||||
// Cache for configurable time
|
// Cache for configurable time
|
||||||
var cacheExpiration = TimeSpan.FromMinutes(_config.GetValue<int>("Performance:CacheExpirationMinutes", 60));
|
var cacheExpiration = TimeSpan.FromMinutes(_config.GetValue<int>("Performance:CacheExpirationMinutes", 60));
|
||||||
await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(result), new DistributedCacheEntryOptions
|
await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(result), new DistributedCacheEntryOptions
|
||||||
|
|||||||
@ -231,7 +231,9 @@ namespace QRRapidoApp.Services
|
|||||||
GenerationTimeMs = qrResult.GenerationTimeMs,
|
GenerationTimeMs = qrResult.GenerationTimeMs,
|
||||||
FromCache = qrResult.FromCache,
|
FromCache = qrResult.FromCache,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
LastAccessedAt = DateTime.UtcNow
|
LastAccessedAt = DateTime.UtcNow,
|
||||||
|
TrackingId = qrResult.TrackingId, // Save tracking ID for analytics
|
||||||
|
IsDynamic = !string.IsNullOrEmpty(qrResult.TrackingId) // Mark as dynamic if tracking is enabled
|
||||||
};
|
};
|
||||||
|
|
||||||
await _context.QRCodeHistory.InsertOneAsync(qrHistory);
|
await _context.QRCodeHistory.InsertOneAsync(qrHistory);
|
||||||
@ -448,5 +450,52 @@ namespace QRRapidoApp.Services
|
|||||||
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
|
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
|
||||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get QR code by tracking ID (for analytics)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<QRCodeHistory?> GetQRByTrackingIdAsync(string trackingId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _context.QRCodeHistory
|
||||||
|
.Find(q => q.TrackingId == trackingId && q.IsActive)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting QR by tracking ID {TrackingId}: {ErrorMessage}", trackingId, ex.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increment scan count for QR code analytics
|
||||||
|
/// </summary>
|
||||||
|
public async Task IncrementQRScanCountAsync(string trackingId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filter = Builders<QRCodeHistory>.Filter.Eq(q => q.TrackingId, trackingId);
|
||||||
|
var update = Builders<QRCodeHistory>.Update
|
||||||
|
.Inc(q => q.ScanCount, 1)
|
||||||
|
.Set(q => q.LastAccessedAt, DateTime.UtcNow);
|
||||||
|
|
||||||
|
var result = await _context.QRCodeHistory.UpdateOneAsync(filter, update);
|
||||||
|
|
||||||
|
if (result.ModifiedCount > 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("QR scan count incremented - TrackingId: {TrackingId}", trackingId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Failed to increment scan count - TrackingId not found: {TrackingId}", trackingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error incrementing scan count for {TrackingId}: {ErrorMessage}", trackingId, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -682,7 +682,7 @@
|
|||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="premium-upgrade-box p-3 border rounded bg-light">
|
<div class="premium-upgrade-box p-3 border rounded bg-light">
|
||||||
<h6 class="fw-bold mb-2">
|
<h6 class="fw-bold mb-2">
|
||||||
<i class="fas fa-crown text-warning"></i>
|
<i class="fas fa-crown text-warning"></i>
|
||||||
Logo Personalizado - Premium
|
Logo Personalizado - Premium
|
||||||
</h6>
|
</h6>
|
||||||
<p class="mb-2 small">Adicione sua marca aos QR Codes! Agora com tamanho configurável (10-25%) e colorização automática.</p>
|
<p class="mb-2 small">Adicione sua marca aos QR Codes! Agora com tamanho configurável (10-25%) e colorização automática.</p>
|
||||||
@ -692,6 +692,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@* Analytics/Tracking - Premium Feature *@
|
||||||
|
@if (isPremium)
|
||||||
|
{
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="enable-tracking" class="form-check-input" value="true">
|
||||||
|
<label class="form-check-label" for="enable-tracking">
|
||||||
|
<i class="fas fa-chart-line text-primary"></i> @Localizer["EnableTracking"]
|
||||||
|
<span class="badge bg-primary">Premium</span>
|
||||||
|
</label>
|
||||||
|
<div class="form-text">
|
||||||
|
<i class="fas fa-info-circle"></i> @Localizer["TrackingInfo"]
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">@Localizer["BorderStyle"]</label>
|
<label class="form-label">@Localizer["BorderStyle"]</label>
|
||||||
@ -1195,6 +1212,7 @@
|
|||||||
<li><i class="fas fa-check text-success"></i> @Localizer["AdvancedCustomization"]</li>
|
<li><i class="fas fa-check text-success"></i> @Localizer["AdvancedCustomization"]</li>
|
||||||
<li><i class="fas fa-check text-success"></i> @Localizer["LogoSupport"]</li>
|
<li><i class="fas fa-check text-success"></i> @Localizer["LogoSupport"]</li>
|
||||||
<li><i class="fas fa-check text-success"></i> @Localizer["HistoryAndDownloads"]</li>
|
<li><i class="fas fa-check text-success"></i> @Localizer["HistoryAndDownloads"]</li>
|
||||||
|
<li><i class="fas fa-chart-line text-success"></i> @Localizer["QRReadCounter"]</li>
|
||||||
<li><i class="fas fa-check text-success"></i> @Localizer["PrioritySupport"]</li>
|
<li><i class="fas fa-check text-success"></i> @Localizer["PrioritySupport"]</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|||||||
@ -76,6 +76,7 @@
|
|||||||
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["AdvancedCustomization"]</li>
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["AdvancedCustomization"]</li>
|
||||||
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["LogoSupport"]</li>
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["LogoSupport"]</li>
|
||||||
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["HistoryAndDownloads"]</li>
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["HistoryAndDownloads"]</li>
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-chart-line text-success me-2"></i>@Localizer["QRReadCounter"]</li>
|
||||||
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["PrioritySupport"]</li>
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>@Localizer["PrioritySupport"]</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
// QR Rapido Speed Generator
|
// QR Rapido Speed Generator
|
||||||
class QRRapidoGenerator {
|
class QRRapidoGenerator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = 0;
|
this.startTime = 0;
|
||||||
this.currentQR = null;
|
this.currentQR = null;
|
||||||
this.timerInterval = null;
|
this.timerInterval = null;
|
||||||
|
|
||||||
|
// Initialize premium status from hidden input
|
||||||
|
this.isPremium = this.checkUserPremium();
|
||||||
|
console.log('[INIT] QRRapidoGenerator - isPremium:', this.isPremium);
|
||||||
|
|
||||||
this.languageStrings = {
|
this.languageStrings = {
|
||||||
'pt-BR': {
|
'pt-BR': {
|
||||||
tagline: 'Gere QR codes em segundos!',
|
tagline: 'Gere QR codes em segundos!',
|
||||||
@ -737,6 +742,9 @@ class QRRapidoGenerator {
|
|||||||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
||||||
|
|
||||||
// Common data for both endpoints
|
// Common data for both endpoints
|
||||||
|
const trackingEnabled = this.getTrackingEnabled();
|
||||||
|
console.log('[DEBUG] getTrackingEnabled() returned:', trackingEnabled, 'type:', typeof trackingEnabled);
|
||||||
|
|
||||||
const commonData = {
|
const commonData = {
|
||||||
type: actualType,
|
type: actualType,
|
||||||
content: encodedContent,
|
content: encodedContent,
|
||||||
@ -747,9 +755,12 @@ class QRRapidoGenerator {
|
|||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
margin: parseInt(document.getElementById('qr-margin').value),
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||||||
optimizeForSpeed: true,
|
optimizeForSpeed: true,
|
||||||
language: this.currentLang
|
language: this.currentLang,
|
||||||
|
enableTracking: trackingEnabled // Analytics feature for premium users
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('[DEBUG] commonData.enableTracking:', commonData.enableTracking);
|
||||||
|
|
||||||
// Add dynamic QR data if it's a URL type
|
// Add dynamic QR data if it's a URL type
|
||||||
if (type === 'url') {
|
if (type === 'url') {
|
||||||
let dynamicData;
|
let dynamicData;
|
||||||
@ -772,11 +783,15 @@ class QRRapidoGenerator {
|
|||||||
// Use FormData for requests with logo
|
// Use FormData for requests with logo
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
// Add all common data to FormData
|
// Add all common data to FormData with PascalCase for ASP.NET Core model binding
|
||||||
Object.keys(commonData).forEach(key => {
|
Object.keys(commonData).forEach(key => {
|
||||||
formData.append(key, commonData[key]);
|
// Convert camelCase to PascalCase for FormData (ASP.NET Core compatibility)
|
||||||
|
const pascalKey = key.charAt(0).toUpperCase() + key.slice(1);
|
||||||
|
const value = commonData[key];
|
||||||
|
console.log('[DEBUG] FormData.append:', pascalKey, '=', value, 'type:', typeof value);
|
||||||
|
formData.append(pascalKey, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOVOS PARÂMETROS DE LOGO APRIMORADO
|
// NOVOS PARÂMETROS DE LOGO APRIMORADO
|
||||||
const logoSettings = this.getLogoSettings();
|
const logoSettings = this.getLogoSettings();
|
||||||
formData.append('LogoSizePercent', logoSettings.logoSizePercent.toString());
|
formData.append('LogoSizePercent', logoSettings.logoSizePercent.toString());
|
||||||
@ -808,13 +823,38 @@ class QRRapidoGenerator {
|
|||||||
getLogoSettings() {
|
getLogoSettings() {
|
||||||
const logoSizeSlider = document.getElementById('logo-size-slider');
|
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||||
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logoSizePercent: parseInt(logoSizeSlider?.value || '20'),
|
logoSizePercent: parseInt(logoSizeSlider?.value || '20'),
|
||||||
applyColorization: logoColorizeToggle?.checked || false
|
applyColorization: logoColorizeToggle?.checked || false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to check if tracking is enabled (Premium feature)
|
||||||
|
getTrackingEnabled() {
|
||||||
|
const trackingCheckbox = document.getElementById('enable-tracking');
|
||||||
|
|
||||||
|
// DEBUG: Log each condition separately
|
||||||
|
console.log('[DEBUG] getTrackingEnabled() - Checkbox exists:', !!trackingCheckbox);
|
||||||
|
console.log('[DEBUG] getTrackingEnabled() - Checkbox checked:', trackingCheckbox?.checked);
|
||||||
|
console.log('[DEBUG] getTrackingEnabled() - this.isPremium:', this.isPremium);
|
||||||
|
|
||||||
|
// Only return true if checkbox exists, is checked, and user is premium
|
||||||
|
// IMPORTANT: Always return boolean (not undefined) for FormData compatibility
|
||||||
|
const result = !!(trackingCheckbox && trackingCheckbox.checked && this.isPremium);
|
||||||
|
console.log('[DEBUG] getTrackingEnabled() - Final result:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has premium status
|
||||||
|
checkUserPremium() {
|
||||||
|
const premiumStatus = document.getElementById('user-premium-status');
|
||||||
|
if (premiumStatus) {
|
||||||
|
return premiumStatus.value === 'premium';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Função auxiliar para obter conteúdo baseado no tipo
|
// Função auxiliar para obter conteúdo baseado no tipo
|
||||||
getContentForType(type) {
|
getContentForType(type) {
|
||||||
if (type === 'url') {
|
if (type === 'url') {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user