Compare commits
No commits in common. "eb0751cb162a78b1dcc0a533437949ce86d17175" and "2edb4e119625fa6ed9dfa3935ec7a6a9bb77d54f" have entirely different histories.
eb0751cb16
...
2edb4e1196
@ -31,9 +31,7 @@
|
||||
"Read(//mnt/c/vscode/**)",
|
||||
"Read(//mnt/c/**)",
|
||||
"Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)",
|
||||
"Bash(netstat -tln)",
|
||||
"Bash(npm install)",
|
||||
"Bash(npm install:*)"
|
||||
"Bash(netstat -tln)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@ -307,8 +307,8 @@ namespace QRRapidoApp.Controllers
|
||||
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
|
||||
|
||||
// DEBUG: Log detalhado dos parâmetros recebidos
|
||||
_logger.LogInformation("🔍 [DEBUG] GenerateRapidWithLogo called - RequestId: {RequestId}, EnableTracking: {EnableTracking}, Type: {Type}, ApplyLogoColorization: {ApplyLogoColorization}, LogoSizePercent: {LogoSizePercent}, HasLogo: {HasLogo}",
|
||||
requestId, request.EnableTracking, request.Type, request.ApplyLogoColorization, request.LogoSizePercent, request.HasLogo);
|
||||
_logger.LogInformation("🔍 [DEBUG] GenerateRapidWithLogo called - RequestId: {RequestId}, ApplyLogoColorization: {ApplyLogoColorization}, LogoSizePercent: {LogoSizePercent}, HasLogo: {HasLogo}",
|
||||
requestId, request.ApplyLogoColorization, request.LogoSizePercent, request.HasLogo);
|
||||
|
||||
using (_logger.BeginScope(new Dictionary<string, object>
|
||||
{
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,345 +0,0 @@
|
||||
# 📊 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,9 +36,6 @@ namespace QRRapidoApp.Models
|
||||
[BsonElement("isDynamic")]
|
||||
public bool IsDynamic { get; set; } = false;
|
||||
|
||||
[BsonElement("trackingId")]
|
||||
public string? TrackingId { get; set; } // Public ID for tracking URL (premium feature)
|
||||
|
||||
[BsonElement("size")]
|
||||
public int Size { get; set; } = 300;
|
||||
|
||||
|
||||
@ -28,12 +28,6 @@ namespace QRRapidoApp.Models.ViewModels
|
||||
/// Se deve aplicar a cor do QR code no logo (Premium feature)
|
||||
/// </summary>
|
||||
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
|
||||
@ -48,6 +42,5 @@ namespace QRRapidoApp.Models.ViewModels
|
||||
public bool Success { get; set; } = true;
|
||||
public string? ErrorMessage { get; set; }
|
||||
public LogoReadabilityInfo? ReadabilityInfo { get; set; } // Nova propriedade para análise de legibilidade
|
||||
public string? TrackingId { get; set; } // Tracking ID for analytics (Premium feature)
|
||||
}
|
||||
}
|
||||
@ -783,23 +783,4 @@
|
||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||
<value>Anonymous users: 3 QR codes per day</value>
|
||||
</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>
|
||||
@ -2041,15 +2041,6 @@
|
||||
<data name="HistoryAndDownloads" xml:space="preserve">
|
||||
<value>Historial y descargas ilimitadas</value>
|
||||
</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 -->
|
||||
<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>
|
||||
|
||||
@ -2131,15 +2131,6 @@
|
||||
<data name="HistoryAndDownloads" xml:space="preserve">
|
||||
<value>Histórico e downloads ilimitados</value>
|
||||
</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 -->
|
||||
<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>
|
||||
|
||||
@ -28,9 +28,5 @@ namespace QRRapidoApp.Services
|
||||
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
||||
Task<List<User>> GetUsersForHistoryCleanupAsync(DateTime cutoffDate);
|
||||
Task DeleteUserHistoryAsync(string userId);
|
||||
|
||||
// QR Code Tracking (Analytics) - Premium feature
|
||||
Task<QRCodeHistory?> GetQRByTrackingIdAsync(string trackingId);
|
||||
Task IncrementQRScanCountAsync(string trackingId);
|
||||
}
|
||||
}
|
||||
@ -45,30 +45,6 @@ namespace QRRapidoApp.Services
|
||||
{
|
||||
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
|
||||
var cacheKey = GenerateCacheKey(request);
|
||||
var cached = await _cache.GetStringAsync(cacheKey);
|
||||
@ -83,13 +59,13 @@ namespace QRRapidoApp.Services
|
||||
{
|
||||
cachedResult.GenerationTimeMs = stopwatch.ElapsedMilliseconds;
|
||||
cachedResult.FromCache = true;
|
||||
|
||||
|
||||
// Se não tem análise de legibilidade no cache, gerar agora
|
||||
if (cachedResult.ReadabilityInfo == null)
|
||||
{
|
||||
cachedResult.ReadabilityInfo = await _readabilityAnalyzer.AnalyzeAsync(request, request.Size);
|
||||
}
|
||||
|
||||
|
||||
return cachedResult;
|
||||
}
|
||||
}
|
||||
@ -110,16 +86,9 @@ namespace QRRapidoApp.Services
|
||||
Size = qrCode.Length,
|
||||
RequestSettings = request,
|
||||
Success = true,
|
||||
ReadabilityInfo = readabilityInfo,
|
||||
TrackingId = trackingId // Include tracking ID if enabled
|
||||
ReadabilityInfo = readabilityInfo
|
||||
};
|
||||
|
||||
// Restore original content if tracking was enabled
|
||||
if (trackingId != null)
|
||||
{
|
||||
request.Content = originalContent;
|
||||
}
|
||||
|
||||
// Cache for configurable time
|
||||
var cacheExpiration = TimeSpan.FromMinutes(_config.GetValue<int>("Performance:CacheExpirationMinutes", 60));
|
||||
await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(result), new DistributedCacheEntryOptions
|
||||
@ -679,230 +648,21 @@ namespace QRRapidoApp.Services
|
||||
|
||||
private byte[] ApplyCornerStyle(byte[] qrBytes, string cornerStyle, int targetSize)
|
||||
{
|
||||
if (cornerStyle == "square" || string.IsNullOrEmpty(cornerStyle))
|
||||
{
|
||||
return qrBytes; // No processing needed for square style
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Applying corner style '{CornerStyle}' to QR code", cornerStyle);
|
||||
|
||||
using var qrImage = Image.Load<Rgba32>(qrBytes);
|
||||
int width = qrImage.Width;
|
||||
int height = qrImage.Height;
|
||||
|
||||
// Detect module size by scanning the image
|
||||
int moduleSize = DetectModuleSize(qrImage);
|
||||
if (moduleSize <= 0)
|
||||
{
|
||||
_logger.LogWarning("Could not detect module size, returning original QR code");
|
||||
return qrBytes;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Detected module size: {ModuleSize}px for {Width}x{Height} QR code", moduleSize, width, height);
|
||||
|
||||
// Create a new image with styled modules
|
||||
using var styledImage = new Image<Rgba32>(width, height);
|
||||
|
||||
// Process each module
|
||||
for (int y = 0; y < height; y += moduleSize)
|
||||
{
|
||||
for (int x = 0; x < width; x += moduleSize)
|
||||
{
|
||||
// Check if this module is dark (part of the QR pattern)
|
||||
var centerPixel = qrImage[x + moduleSize / 2, y + moduleSize / 2];
|
||||
bool isDark = centerPixel.R < 128; // Dark if RGB < 128
|
||||
|
||||
if (isDark)
|
||||
{
|
||||
// Draw styled module based on cornerStyle
|
||||
DrawStyledModule(styledImage, x, y, moduleSize, cornerStyle, centerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw background (light modules)
|
||||
DrawRectangle(styledImage, x, y, moduleSize, moduleSize, centerPixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to bytes
|
||||
using var outputStream = new MemoryStream();
|
||||
styledImage.SaveAsPng(outputStream);
|
||||
_logger.LogInformation("Successfully applied corner style '{CornerStyle}'", cornerStyle);
|
||||
return outputStream.ToArray();
|
||||
// Simplified implementation for cross-platform compatibility
|
||||
// The complex corner styling can be re-implemented later using ImageSharp drawing primitives
|
||||
_logger.LogInformation("Corner style '{CornerStyle}' temporarily disabled for cross-platform compatibility. Returning original QR code.", cornerStyle);
|
||||
return qrBytes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error applying corner style {CornerStyle}, returning original QR code", cornerStyle);
|
||||
// Return original QR code if styling fails
|
||||
return qrBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private int DetectModuleSize(Image<Rgba32> qrImage)
|
||||
{
|
||||
// Scan horizontally from left to find the first transition from light to dark
|
||||
int width = qrImage.Width;
|
||||
int height = qrImage.Height;
|
||||
int midY = height / 2;
|
||||
|
||||
bool wasLight = true;
|
||||
int darkStart = -1;
|
||||
int darkEnd = -1;
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
var pixel = qrImage[x, midY];
|
||||
bool isLight = pixel.R >= 128;
|
||||
|
||||
if (wasLight && !isLight)
|
||||
{
|
||||
// Transition from light to dark
|
||||
darkStart = x;
|
||||
}
|
||||
else if (!wasLight && isLight && darkStart >= 0)
|
||||
{
|
||||
// Transition from dark to light
|
||||
darkEnd = x;
|
||||
break;
|
||||
}
|
||||
|
||||
wasLight = isLight;
|
||||
}
|
||||
|
||||
if (darkStart >= 0 && darkEnd > darkStart)
|
||||
{
|
||||
return darkEnd - darkStart;
|
||||
}
|
||||
|
||||
// Fallback: estimate based on typical QR code structure (21-177 modules)
|
||||
// Most common is 25-29 modules for QR version 1-5
|
||||
return width / 25; // Rough estimate
|
||||
}
|
||||
|
||||
private void DrawStyledModule(Image<Rgba32> image, int x, int y, int size, string style, Rgba32 color)
|
||||
{
|
||||
switch (style.ToLower())
|
||||
{
|
||||
case "rounded":
|
||||
DrawRoundedRectangle(image, x, y, size, size, size / 4, color);
|
||||
break;
|
||||
case "circle":
|
||||
DrawCircle(image, x + size / 2, y + size / 2, size / 2, color);
|
||||
break;
|
||||
default:
|
||||
DrawRectangle(image, x, y, size, size, color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRectangle(Image<Rgba32> image, int x, int y, int width, int height, Rgba32 color)
|
||||
{
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
int px = x + dx;
|
||||
int py = y + dy;
|
||||
if (px >= 0 && px < image.Width && py >= 0 && py < image.Height)
|
||||
{
|
||||
image[px, py] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRoundedRectangle(Image<Rgba32> image, int x, int y, int width, int height, int radius, Rgba32 color)
|
||||
{
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
int px = x + dx;
|
||||
int py = y + dy;
|
||||
|
||||
if (px < 0 || px >= image.Width || py < 0 || py >= image.Height)
|
||||
continue;
|
||||
|
||||
// Check if pixel is inside rounded rectangle
|
||||
bool inCorner = false;
|
||||
int distX = 0, distY = 0;
|
||||
|
||||
// Top-left corner
|
||||
if (dx < radius && dy < radius)
|
||||
{
|
||||
distX = radius - dx;
|
||||
distY = radius - dy;
|
||||
inCorner = true;
|
||||
}
|
||||
// Top-right corner
|
||||
else if (dx >= width - radius && dy < radius)
|
||||
{
|
||||
distX = dx - (width - radius - 1);
|
||||
distY = radius - dy;
|
||||
inCorner = true;
|
||||
}
|
||||
// Bottom-left corner
|
||||
else if (dx < radius && dy >= height - radius)
|
||||
{
|
||||
distX = radius - dx;
|
||||
distY = dy - (height - radius - 1);
|
||||
inCorner = true;
|
||||
}
|
||||
// Bottom-right corner
|
||||
else if (dx >= width - radius && dy >= height - radius)
|
||||
{
|
||||
distX = dx - (width - radius - 1);
|
||||
distY = dy - (height - radius - 1);
|
||||
inCorner = true;
|
||||
}
|
||||
|
||||
if (inCorner)
|
||||
{
|
||||
// Check if point is inside the rounded corner
|
||||
double distance = Math.Sqrt(distX * distX + distY * distY);
|
||||
if (distance <= radius)
|
||||
{
|
||||
image[px, py] = color;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inside the main rectangle (not in corners)
|
||||
image[px, py] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCircle(Image<Rgba32> image, int centerX, int centerY, int radius, Rgba32 color)
|
||||
{
|
||||
int x0 = centerX - radius;
|
||||
int y0 = centerY - radius;
|
||||
int diameter = radius * 2;
|
||||
|
||||
for (int dy = 0; dy <= diameter; dy++)
|
||||
{
|
||||
for (int dx = 0; dx <= diameter; dx++)
|
||||
{
|
||||
int px = x0 + dx;
|
||||
int py = y0 + dy;
|
||||
|
||||
if (px < 0 || px >= image.Width || py < 0 || py >= image.Height)
|
||||
continue;
|
||||
|
||||
// Calculate distance from center
|
||||
double distance = Math.Sqrt(Math.Pow(dx - radius, 2) + Math.Pow(dy - radius, 2));
|
||||
|
||||
if (distance <= radius)
|
||||
{
|
||||
image[px, py] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// QRModule class removed - was only used for corner styling which is temporarily simplified
|
||||
}
|
||||
}
|
||||
@ -231,9 +231,7 @@ namespace QRRapidoApp.Services
|
||||
GenerationTimeMs = qrResult.GenerationTimeMs,
|
||||
FromCache = qrResult.FromCache,
|
||||
IsActive = true,
|
||||
LastAccessedAt = DateTime.UtcNow,
|
||||
TrackingId = qrResult.TrackingId, // Save tracking ID for analytics
|
||||
IsDynamic = !string.IsNullOrEmpty(qrResult.TrackingId) // Mark as dynamic if tracking is enabled
|
||||
LastAccessedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _context.QRCodeHistory.InsertOneAsync(qrHistory);
|
||||
@ -450,52 +448,5 @@ namespace QRRapidoApp.Services
|
||||
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
|
||||
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="premium-upgrade-box p-3 border rounded bg-light">
|
||||
<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
|
||||
</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>
|
||||
@ -692,23 +692,6 @@
|
||||
</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">
|
||||
<label class="form-label">@Localizer["BorderStyle"]</label>
|
||||
@ -718,11 +701,13 @@
|
||||
{
|
||||
<option value="rounded">@Localizer["Rounded"] 👑</option>
|
||||
<option value="circle">Círculos 👑</option>
|
||||
<option value="leaf">Folha 👑</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="rounded" disabled>@Localizer["Rounded"] - Premium 👑</option>
|
||||
<option value="circle" disabled>Círculos - Premium 👑</option>
|
||||
<option value="leaf" disabled>Folha - Premium 👑</option>
|
||||
}
|
||||
</select>
|
||||
@if (!isPremium)
|
||||
@ -1210,7 +1195,6 @@
|
||||
<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["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>
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
|
||||
@ -76,7 +76,6 @@
|
||||
<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["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>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"name": "qrrapido-app",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.21"
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
@ -879,9 +879,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"version": "5.4.20",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
|
||||
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
"preview": "vite preview --config vite.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.21"
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
// QR Rapido Speed Generator
|
||||
class QRRapidoGenerator {
|
||||
class QRRapidoGenerator {
|
||||
constructor() {
|
||||
this.startTime = 0;
|
||||
this.currentQR = null;
|
||||
this.timerInterval = null;
|
||||
|
||||
// Initialize premium status from hidden input
|
||||
this.isPremium = this.checkUserPremium();
|
||||
console.log('[INIT] QRRapidoGenerator - isPremium:', this.isPremium);
|
||||
|
||||
this.languageStrings = {
|
||||
'pt-BR': {
|
||||
tagline: 'Gere QR codes em segundos!',
|
||||
@ -742,9 +737,6 @@ class QRRapidoGenerator {
|
||||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
||||
|
||||
// Common data for both endpoints
|
||||
const trackingEnabled = this.getTrackingEnabled();
|
||||
console.log('[DEBUG] getTrackingEnabled() returned:', trackingEnabled, 'type:', typeof trackingEnabled);
|
||||
|
||||
const commonData = {
|
||||
type: actualType,
|
||||
content: encodedContent,
|
||||
@ -755,12 +747,9 @@ class QRRapidoGenerator {
|
||||
margin: parseInt(document.getElementById('qr-margin').value),
|
||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||||
optimizeForSpeed: true,
|
||||
language: this.currentLang,
|
||||
enableTracking: trackingEnabled // Analytics feature for premium users
|
||||
language: this.currentLang
|
||||
};
|
||||
|
||||
console.log('[DEBUG] commonData.enableTracking:', commonData.enableTracking);
|
||||
|
||||
// Add dynamic QR data if it's a URL type
|
||||
if (type === 'url') {
|
||||
let dynamicData;
|
||||
@ -783,15 +772,11 @@ class QRRapidoGenerator {
|
||||
// Use FormData for requests with logo
|
||||
const formData = new FormData();
|
||||
|
||||
// Add all common data to FormData with PascalCase for ASP.NET Core model binding
|
||||
// Add all common data to FormData
|
||||
Object.keys(commonData).forEach(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);
|
||||
formData.append(key, commonData[key]);
|
||||
});
|
||||
|
||||
|
||||
// NOVOS PARÂMETROS DE LOGO APRIMORADO
|
||||
const logoSettings = this.getLogoSettings();
|
||||
formData.append('LogoSizePercent', logoSettings.logoSizePercent.toString());
|
||||
@ -823,38 +808,13 @@ class QRRapidoGenerator {
|
||||
getLogoSettings() {
|
||||
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
||||
|
||||
|
||||
return {
|
||||
logoSizePercent: parseInt(logoSizeSlider?.value || '20'),
|
||||
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
|
||||
getContentForType(type) {
|
||||
if (type === 'url') {
|
||||
@ -1182,8 +1142,8 @@ class QRRapidoGenerator {
|
||||
|
||||
handleCornerStyleChange(e) {
|
||||
const selectedStyle = e.target.value;
|
||||
const premiumStyles = ['rounded', 'circle'];
|
||||
|
||||
const premiumStyles = ['rounded', 'circle', 'leaf'];
|
||||
|
||||
if (premiumStyles.includes(selectedStyle)) {
|
||||
// Check if user is premium (we can detect this by checking if the option is disabled)
|
||||
const option = e.target.options[e.target.selectedIndex];
|
||||
@ -1194,12 +1154,6 @@ class QRRapidoGenerator {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a QR code already exists, regenerate with new corner style
|
||||
if (this.currentQR) {
|
||||
console.log('[CORNER STYLE] Corner style changed to:', selectedStyle, '- regenerating QR code');
|
||||
this.generateQRWithTimer(e);
|
||||
}
|
||||
}
|
||||
|
||||
applyQuickStyle(e) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user