feat: delete e es-py
This commit is contained in:
parent
70fbdaa3c2
commit
a7af34659b
@ -3,6 +3,7 @@ using QRRapidoApp.Models;
|
|||||||
using QRRapidoApp.Services;
|
using QRRapidoApp.Services;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace QRRapidoApp.Controllers
|
namespace QRRapidoApp.Controllers
|
||||||
{
|
{
|
||||||
@ -12,13 +13,15 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly AdDisplayService _adDisplayService;
|
private readonly AdDisplayService _adDisplayService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger, AdDisplayService adDisplayService, IUserService userService, IConfiguration config)
|
public HomeController(ILogger<HomeController> logger, AdDisplayService adDisplayService, IUserService userService, IConfiguration config, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_adDisplayService = adDisplayService;
|
_adDisplayService = adDisplayService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_localizer = localizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
@ -33,7 +36,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
// SEO and Analytics data
|
// SEO and Analytics data
|
||||||
ViewBag.Title = _config["App:TaglinePT"];
|
ViewBag.Title = _config["App:TaglinePT"];
|
||||||
ViewBag.Keywords = _config["SEO:KeywordsPT"];
|
ViewBag.Keywords = _config["SEO:KeywordsPT"];
|
||||||
ViewBag.Description = "QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login.";
|
ViewBag.Description = _localizer["QRGenerateDescription"];
|
||||||
|
|
||||||
// User stats for logged in users
|
// User stats for logged in users
|
||||||
if (!string.IsNullOrEmpty(userId))
|
if (!string.IsNullOrEmpty(userId))
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using QRRapidoApp.Services;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace QRRapidoApp.Controllers
|
namespace QRRapidoApp.Controllers
|
||||||
{
|
{
|
||||||
@ -15,13 +16,15 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly AdDisplayService _adService;
|
private readonly AdDisplayService _adService;
|
||||||
private readonly ILogger<QRController> _logger;
|
private readonly ILogger<QRController> _logger;
|
||||||
|
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||||
|
|
||||||
public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger<QRController> logger)
|
public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger<QRController> logger, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer)
|
||||||
{
|
{
|
||||||
_qrService = qrService;
|
_qrService = qrService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_adService = adService;
|
_adService = adService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_localizer = localizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("GenerateRapid")]
|
[HttpPost("GenerateRapid")]
|
||||||
@ -51,13 +54,13 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (string.IsNullOrWhiteSpace(request.Content))
|
if (string.IsNullOrWhiteSpace(request.Content))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("QR generation failed - empty content provided");
|
_logger.LogWarning("QR generation failed - empty content provided");
|
||||||
return BadRequest(new { error = "Conteúdo é obrigatório", success = false });
|
return BadRequest(new { error = _localizer["RequiredContent"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Content.Length > 4000) // Limit to maintain speed
|
if (request.Content.Length > 4000) // Limit to maintain speed
|
||||||
{
|
{
|
||||||
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
|
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
|
||||||
return BadRequest(new { error = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false });
|
return BadRequest(new { error = _localizer["ContentTooLong"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user status
|
// Check user status
|
||||||
@ -70,7 +73,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
userId ?? "anonymous", request.CornerStyle);
|
userId ?? "anonymous", request.CornerStyle);
|
||||||
return BadRequest(new
|
return BadRequest(new
|
||||||
{
|
{
|
||||||
error = "Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.",
|
error = _localizer["PremiumCornerStyleRequired"],
|
||||||
requiresPremium = true,
|
requiresPremium = true,
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
@ -84,7 +87,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
userId ?? "anonymous", user?.IsPremium ?? false);
|
userId ?? "anonymous", user?.IsPremium ?? false);
|
||||||
return StatusCode(429, new
|
return StatusCode(429, new
|
||||||
{
|
{
|
||||||
error = "Limite de QR codes atingido",
|
error = _localizer["RateLimitReached"],
|
||||||
upgradeUrl = "/Premium/Upgrade",
|
upgradeUrl = "/Premium/Upgrade",
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
@ -325,13 +328,13 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (string.IsNullOrWhiteSpace(request.Content))
|
if (string.IsNullOrWhiteSpace(request.Content))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("QR generation failed - empty content provided");
|
_logger.LogWarning("QR generation failed - empty content provided");
|
||||||
return BadRequest(new { error = "Conteúdo é obrigatório", success = false });
|
return BadRequest(new { error = _localizer["RequiredContent"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Content.Length > 4000)
|
if (request.Content.Length > 4000)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
|
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
|
||||||
return BadRequest(new { error = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false });
|
return BadRequest(new { error = _localizer["ContentTooLong"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user status
|
// Check user status
|
||||||
@ -343,7 +346,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
_logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous");
|
_logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous");
|
||||||
return BadRequest(new
|
return BadRequest(new
|
||||||
{
|
{
|
||||||
error = "Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade.",
|
error = _localizer["PremiumLogoRequired"],
|
||||||
requiresPremium = true,
|
requiresPremium = true,
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
@ -363,7 +366,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (logo.Length > 2 * 1024 * 1024)
|
if (logo.Length > 2 * 1024 * 1024)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Logo upload failed - file too large: {FileSize} bytes", logo.Length);
|
_logger.LogWarning("Logo upload failed - file too large: {FileSize} bytes", logo.Length);
|
||||||
return BadRequest(new { error = "Logo muito grande. Máximo 2MB.", success = false });
|
return BadRequest(new { error = _localizer["LogoTooLarge"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate file format
|
// Validate file format
|
||||||
@ -371,7 +374,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (!allowedTypes.Contains(logo.ContentType?.ToLower()))
|
if (!allowedTypes.Contains(logo.ContentType?.ToLower()))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Logo upload failed - invalid format: {ContentType}", logo.ContentType);
|
_logger.LogWarning("Logo upload failed - invalid format: {ContentType}", logo.ContentType);
|
||||||
return BadRequest(new { error = "Formato inválido. Use PNG ou JPG.", success = false });
|
return BadRequest(new { error = _localizer["InvalidLogoFormat"], success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -388,7 +391,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error processing logo file");
|
_logger.LogError(ex, "Error processing logo file");
|
||||||
return BadRequest(new { error = "Erro ao processar logo.", success = false });
|
return BadRequest(new { error = _localizer["ErrorProcessingLogo"], success = false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +403,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
userId ?? "anonymous", user?.IsPremium ?? false);
|
userId ?? "anonymous", user?.IsPremium ?? false);
|
||||||
return StatusCode(429, new
|
return StatusCode(429, new
|
||||||
{
|
{
|
||||||
error = "Limite de QR codes atingido",
|
error = _localizer["RateLimitReached"],
|
||||||
upgradeUrl = "/Premium/Upgrade",
|
upgradeUrl = "/Premium/Upgrade",
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
@ -512,6 +515,36 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete("History/{qrId}")]
|
||||||
|
public async Task<IActionResult> DeleteFromHistory(string qrId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = await _userService.DeleteQRFromHistoryAsync(userId, qrId);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("QR code deleted from history - QRId: {QRId}, UserId: {UserId}", qrId, userId);
|
||||||
|
return Ok(new { success = true, message = _localizer["QRCodeDeleted"] });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = _localizer["ErrorDeletingQR"] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error deleting QR from history - QRId: {QRId}", qrId);
|
||||||
|
return StatusCode(500, new { success = false, message = _localizer["ErrorDeletingQR"] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> CheckRateLimitAsync(string? userId, Models.User? user)
|
private async Task<bool> CheckRateLimitAsync(string? userId, Models.User? user)
|
||||||
{
|
{
|
||||||
// Premium users have unlimited QR codes
|
// Premium users have unlimited QR codes
|
||||||
|
|||||||
@ -157,8 +157,7 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|||||||
var supportedCultures = new[]
|
var supportedCultures = new[]
|
||||||
{
|
{
|
||||||
new CultureInfo("pt-BR"),
|
new CultureInfo("pt-BR"),
|
||||||
new CultureInfo("es"),
|
new CultureInfo("es-PY")
|
||||||
new CultureInfo("en")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||||
@ -267,7 +266,7 @@ app.MapHealthChecks("/health");
|
|||||||
// Language routes (must be first)
|
// Language routes (must be first)
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "localized",
|
name: "localized",
|
||||||
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
|
pattern: "{culture:regex(^(pt-BR|es-PY)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
|
|||||||
217
Resources/SharedResource.es-PY.resx
Normal file
217
Resources/SharedResource.es-PY.resx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<!-- Content required keys from existing es.resx but adapted for Paraguay Spanish -->
|
||||||
|
<data name="Tagline" xml:space="preserve">
|
||||||
|
<value>¡Genera códigos QR en segundos!</value>
|
||||||
|
</data>
|
||||||
|
<data name="RequiredContent" xml:space="preserve">
|
||||||
|
<value>Contenido es obligatorio</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentTooLong" xml:space="preserve">
|
||||||
|
<value>Contenido muy largo. Máximo 4000 caracteres.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumCornerStyleRequired" xml:space="preserve">
|
||||||
|
<value>Estilos de borde personalizados son exclusivos del plan Premium. Actualiza para usar esta funcionalidad.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RateLimitReached" xml:space="preserve">
|
||||||
|
<value>Límite de códigos QR alcanzado</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumLogoRequired" xml:space="preserve">
|
||||||
|
<value>Logo personalizado es exclusivo del plan Premium. Actualiza para usar esta funcionalidad.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooLarge" xml:space="preserve">
|
||||||
|
<value>Logo muy grande. Máximo 2MB.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidLogoFormat" xml:space="preserve">
|
||||||
|
<value>Formato inválido. Usa PNG o JPG.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UserProfileTitle" xml:space="preserve">
|
||||||
|
<value>Perfil del Usuario</value>
|
||||||
|
</data>
|
||||||
|
<data name="HistoryTitle" xml:space="preserve">
|
||||||
|
<value>Historial de Códigos QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorSavingHistory" xml:space="preserve">
|
||||||
|
<value>Error al guardar en el historial.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FeatureNotAvailable" xml:space="preserve">
|
||||||
|
<value>Función no disponible</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeSavedHistory" xml:space="preserve">
|
||||||
|
<value>¡Código QR guardado en el historial!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShareError" xml:space="preserve">
|
||||||
|
<value>Error al compartir. Intenta otro método.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LinkCopied" xml:space="preserve">
|
||||||
|
<value>¡Enlace copiado al portapapeles!</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRContent" xml:space="preserve">
|
||||||
|
<value>Ingresa el contenido del código QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="ValidationContentMinLength" xml:space="preserve">
|
||||||
|
<value>Contenido debe tener al menos 3 caracteres</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCardValidationError" xml:space="preserve">
|
||||||
|
<value>Error en la validación del VCard: </value>
|
||||||
|
</data>
|
||||||
|
<data name="FastestGeneratorBrazil" xml:space="preserve">
|
||||||
|
<value>Código QR generado con QR Rapido - ¡el generador más rápido de Paraguay!</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRGenerateDescription" xml:space="preserve">
|
||||||
|
<value>QR Rapido: ¡Genera códigos QR en segundos! Generador ultra-rápido en español y portugués. Gratis, sin registro obligatorio. 30 días sin anuncios después del login.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoNotProvided" xml:space="preserve">
|
||||||
|
<value>Logo no proporcionado</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooSmall" xml:space="preserve">
|
||||||
|
<value>Logo muy pequeño. Mínimo 32x32 píxeles.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidImageFormat" xml:space="preserve">
|
||||||
|
<value>Formato de imagen inválido</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorProcessingLogo" xml:space="preserve">
|
||||||
|
<value>Error al procesar logo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteQRCode" xml:space="preserve">
|
||||||
|
<value>Eliminar Código QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteTitle" xml:space="preserve">
|
||||||
|
<value>Confirmar Eliminación</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteMessage" xml:space="preserve">
|
||||||
|
<value>¿Estás seguro de que quieres eliminar este código QR de tu historial?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Yes" xml:space="preserve">
|
||||||
|
<value>Sí</value>
|
||||||
|
</data>
|
||||||
|
<data name="No" xml:space="preserve">
|
||||||
|
<value>No</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeDeleted" xml:space="preserve">
|
||||||
|
<value>¡Código QR eliminado exitosamente!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorDeletingQR" xml:space="preserve">
|
||||||
|
<value>Error al eliminar código QR. Inténtalo de nuevo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Deleting" xml:space="preserve">
|
||||||
|
<value>Eliminando</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@ -783,4 +783,94 @@
|
|||||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||||
<value>Usuários anônimos: 3 QR codes por dia</value>
|
<value>Usuários anônimos: 3 QR codes por dia</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RequiredContent" xml:space="preserve">
|
||||||
|
<value>Conteúdo é obrigatório</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentTooLong" xml:space="preserve">
|
||||||
|
<value>Conteúdo muito longo. Máximo 4000 caracteres.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumCornerStyleRequired" xml:space="preserve">
|
||||||
|
<value>Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RateLimitReached" xml:space="preserve">
|
||||||
|
<value>Limite de QR codes atingido</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumLogoRequired" xml:space="preserve">
|
||||||
|
<value>Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooLarge" xml:space="preserve">
|
||||||
|
<value>Logo muito grande. Máximo 2MB.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidLogoFormat" xml:space="preserve">
|
||||||
|
<value>Formato inválido. Use PNG ou JPG.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UserProfileTitle" xml:space="preserve">
|
||||||
|
<value>Perfil do Usuário</value>
|
||||||
|
</data>
|
||||||
|
<data name="HistoryTitle" xml:space="preserve">
|
||||||
|
<value>Histórico de QR Codes</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorSavingHistory" xml:space="preserve">
|
||||||
|
<value>Erro ao salvar no histórico.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FeatureNotAvailable" xml:space="preserve">
|
||||||
|
<value>Funcionalidade não disponível</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShareError" xml:space="preserve">
|
||||||
|
<value>Erro ao compartilhar. Tente outro método.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LinkCopied" xml:space="preserve">
|
||||||
|
<value>Link copiado para a área de transferência!</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRContent" xml:space="preserve">
|
||||||
|
<value>Digite o conteúdo do QR code</value>
|
||||||
|
</data>
|
||||||
|
<data name="ValidationContentMinLength" xml:space="preserve">
|
||||||
|
<value>Conteúdo deve ter pelo menos 3 caracteres</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCardValidationError" xml:space="preserve">
|
||||||
|
<value>Erro na validação do VCard: </value>
|
||||||
|
</data>
|
||||||
|
<data name="FastestGeneratorBrazil" xml:space="preserve">
|
||||||
|
<value>QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRGenerateDescription" xml:space="preserve">
|
||||||
|
<value>QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoNotProvided" xml:space="preserve">
|
||||||
|
<value>Logo não fornecido</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooSmall" xml:space="preserve">
|
||||||
|
<value>Logo muito pequeno. Mínimo 32x32 pixels.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidImageFormat" xml:space="preserve">
|
||||||
|
<value>Formato de imagem inválido</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorProcessingLogo" xml:space="preserve">
|
||||||
|
<value>Erro ao processar logo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteQRCode" xml:space="preserve">
|
||||||
|
<value>Excluir QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteTitle" xml:space="preserve">
|
||||||
|
<value>Confirmar Exclusão</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteMessage" xml:space="preserve">
|
||||||
|
<value>Tem certeza que deseja excluir este QR code do seu histórico?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Yes" xml:space="preserve">
|
||||||
|
<value>Sim</value>
|
||||||
|
</data>
|
||||||
|
<data name="No" xml:space="preserve">
|
||||||
|
<value>Não</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeDeleted" xml:space="preserve">
|
||||||
|
<value>QR Code excluído com sucesso!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorDeletingQR" xml:space="preserve">
|
||||||
|
<value>Erro ao excluir QR code. Tente novamente.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Deleting" xml:space="preserve">
|
||||||
|
<value>Excluindo</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -336,4 +336,94 @@
|
|||||||
<data name="OpenNetwork" xml:space="preserve">
|
<data name="OpenNetwork" xml:space="preserve">
|
||||||
<value>Sem senha </value>
|
<value>Sem senha </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RequiredContent" xml:space="preserve">
|
||||||
|
<value>Content is required</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentTooLong" xml:space="preserve">
|
||||||
|
<value>Content too long. Maximum 4000 characters.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumCornerStyleRequired" xml:space="preserve">
|
||||||
|
<value>Custom corner styles are exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RateLimitReached" xml:space="preserve">
|
||||||
|
<value>QR codes limit reached</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumLogoRequired" xml:space="preserve">
|
||||||
|
<value>Custom logo is exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooLarge" xml:space="preserve">
|
||||||
|
<value>Logo too large. Maximum 2MB.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidLogoFormat" xml:space="preserve">
|
||||||
|
<value>Invalid format. Use PNG or JPG.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UserProfileTitle" xml:space="preserve">
|
||||||
|
<value>User Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="HistoryTitle" xml:space="preserve">
|
||||||
|
<value>QR Codes History</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorSavingHistory" xml:space="preserve">
|
||||||
|
<value>Error saving to history.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FeatureNotAvailable" xml:space="preserve">
|
||||||
|
<value>Feature not available</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShareError" xml:space="preserve">
|
||||||
|
<value>Error sharing. Try another method.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LinkCopied" xml:space="preserve">
|
||||||
|
<value>Link copied to clipboard!</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRContent" xml:space="preserve">
|
||||||
|
<value>Enter QR code content</value>
|
||||||
|
</data>
|
||||||
|
<data name="ValidationContentMinLength" xml:space="preserve">
|
||||||
|
<value>Content must have at least 3 characters</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCardValidationError" xml:space="preserve">
|
||||||
|
<value>VCard validation error: </value>
|
||||||
|
</data>
|
||||||
|
<data name="FastestGeneratorBrazil" xml:space="preserve">
|
||||||
|
<value>QR Code generated with QR Rapido - the fastest generator in Brazil!</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRGenerateDescription" xml:space="preserve">
|
||||||
|
<value>QR Rapido: Generate QR codes in seconds! Ultra-fast generator. Free, no registration required. 30 days ad-free after login.</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoNotProvided" xml:space="preserve">
|
||||||
|
<value>Logo not provided</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoTooSmall" xml:space="preserve">
|
||||||
|
<value>Logo too small. Minimum 32x32 pixels.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidImageFormat" xml:space="preserve">
|
||||||
|
<value>Invalid image format</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorProcessingLogo" xml:space="preserve">
|
||||||
|
<value>Error processing logo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteQRCode" xml:space="preserve">
|
||||||
|
<value>Delete QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteTitle" xml:space="preserve">
|
||||||
|
<value>Confirm Deletion</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConfirmDeleteMessage" xml:space="preserve">
|
||||||
|
<value>Are you sure you want to delete this QR code from your history?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Yes" xml:space="preserve">
|
||||||
|
<value>Yes</value>
|
||||||
|
</data>
|
||||||
|
<data name="No" xml:space="preserve">
|
||||||
|
<value>No</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeDeleted" xml:space="preserve">
|
||||||
|
<value>QR Code deleted successfully!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorDeletingQR" xml:space="preserve">
|
||||||
|
<value>Error deleting QR code. Please try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Deleting" xml:space="preserve">
|
||||||
|
<value>Deleting</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -22,6 +22,7 @@ namespace QRRapidoApp.Services
|
|||||||
Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult);
|
Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult);
|
||||||
Task<List<QRCodeHistory>> GetUserQRHistoryAsync(string userId, int limit = 50);
|
Task<List<QRCodeHistory>> GetUserQRHistoryAsync(string userId, int limit = 50);
|
||||||
Task<QRCodeHistory?> GetQRDataAsync(string qrId);
|
Task<QRCodeHistory?> GetQRDataAsync(string qrId);
|
||||||
|
Task<bool> DeleteQRFromHistoryAsync(string userId, string qrId);
|
||||||
Task<int> GetQRCountThisMonthAsync(string userId);
|
Task<int> GetQRCountThisMonthAsync(string userId);
|
||||||
Task<string> GetUserEmailAsync(string userId);
|
Task<string> GetUserEmailAsync(string userId);
|
||||||
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ using System.Numerics;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace QRRapidoApp.Services
|
namespace QRRapidoApp.Services
|
||||||
{
|
{
|
||||||
@ -21,13 +22,15 @@ namespace QRRapidoApp.Services
|
|||||||
private readonly ILogger<QRRapidoService> _logger;
|
private readonly ILogger<QRRapidoService> _logger;
|
||||||
private readonly SemaphoreSlim _semaphore;
|
private readonly SemaphoreSlim _semaphore;
|
||||||
private readonly LogoReadabilityAnalyzer _readabilityAnalyzer;
|
private readonly LogoReadabilityAnalyzer _readabilityAnalyzer;
|
||||||
|
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||||
|
|
||||||
public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger<QRRapidoService> logger, LogoReadabilityAnalyzer readabilityAnalyzer)
|
public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger<QRRapidoService> logger, LogoReadabilityAnalyzer readabilityAnalyzer, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer)
|
||||||
{
|
{
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_readabilityAnalyzer = readabilityAnalyzer;
|
_readabilityAnalyzer = readabilityAnalyzer;
|
||||||
|
_localizer = localizer;
|
||||||
|
|
||||||
// Limit simultaneous generations to maintain performance
|
// Limit simultaneous generations to maintain performance
|
||||||
var maxConcurrent = _config.GetValue<int>("Performance:MaxConcurrentGenerations", 100);
|
var maxConcurrent = _config.GetValue<int>("Performance:MaxConcurrentGenerations", 100);
|
||||||
@ -215,14 +218,14 @@ namespace QRRapidoApp.Services
|
|||||||
|
|
||||||
if (logoBytes == null || logoBytes.Length == 0)
|
if (logoBytes == null || logoBytes.Length == 0)
|
||||||
{
|
{
|
||||||
errorMessage = "Logo não fornecido";
|
errorMessage = _localizer["LogoNotProvided"];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validar tamanho máximo
|
// Validar tamanho máximo
|
||||||
if (logoBytes.Length > 2 * 1024 * 1024) // 2MB
|
if (logoBytes.Length > 2 * 1024 * 1024) // 2MB
|
||||||
{
|
{
|
||||||
errorMessage = "Logo muito grande. Máximo 2MB.";
|
errorMessage = _localizer["LogoTooLarge"];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +237,7 @@ namespace QRRapidoApp.Services
|
|||||||
// Validar dimensões mínimas
|
// Validar dimensões mínimas
|
||||||
if (image.Width < 32 || image.Height < 32)
|
if (image.Width < 32 || image.Height < 32)
|
||||||
{
|
{
|
||||||
errorMessage = "Logo muito pequeno. Mínimo 32x32 pixels.";
|
errorMessage = _localizer["LogoTooSmall"];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +248,7 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errorMessage = "Formato de imagem inválido";
|
errorMessage = _localizer["InvalidImageFormat"];
|
||||||
_logger.LogError(ex, "Error validating logo format");
|
_logger.LogError(ex, "Error validating logo format");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -276,6 +276,36 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteQRFromHistoryAsync(string userId, string qrId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// First verify that the QR code belongs to the user
|
||||||
|
var qrCode = await _context.QRCodeHistory
|
||||||
|
.Find(q => q.Id == qrId && q.UserId == userId && q.IsActive)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (qrCode == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"QR code not found or doesn't belong to user - QRId: {qrId}, UserId: {userId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft delete: mark as inactive instead of permanently deleting
|
||||||
|
var update = Builders<QRCodeHistory>.Update.Set(q => q.IsActive, false);
|
||||||
|
var result = await _context.QRCodeHistory.UpdateOneAsync(
|
||||||
|
q => q.Id == qrId && q.UserId == userId,
|
||||||
|
update);
|
||||||
|
|
||||||
|
return result.ModifiedCount > 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error deleting QR from history - QRId: {qrId}, UserId: {userId}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> GetQRCountThisMonthAsync(string userId)
|
public async Task<int> GetQRCountThisMonthAsync(string userId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@ -16,6 +16,7 @@ namespace QRRapidoApp.Tests.Services
|
|||||||
private readonly Mock<IConfiguration> _configMock;
|
private readonly Mock<IConfiguration> _configMock;
|
||||||
private readonly Mock<ILogger<QRRapidoService>> _loggerMock;
|
private readonly Mock<ILogger<QRRapidoService>> _loggerMock;
|
||||||
private readonly Mock<LogoReadabilityAnalyzer> _readabilityAnalyzerMock;
|
private readonly Mock<LogoReadabilityAnalyzer> _readabilityAnalyzerMock;
|
||||||
|
private readonly Mock<IStringLocalizer<QRRapidoApp.Resources.SharedResource>> _localizerMock;
|
||||||
private readonly QRRapidoService _service;
|
private readonly QRRapidoService _service;
|
||||||
|
|
||||||
public QRRapidoServiceTests()
|
public QRRapidoServiceTests()
|
||||||
@ -24,9 +25,10 @@ namespace QRRapidoApp.Tests.Services
|
|||||||
_configMock = new Mock<IConfiguration>();
|
_configMock = new Mock<IConfiguration>();
|
||||||
_loggerMock = new Mock<ILogger<QRRapidoService>>();
|
_loggerMock = new Mock<ILogger<QRRapidoService>>();
|
||||||
_readabilityAnalyzerMock = new Mock<LogoReadabilityAnalyzer>(Mock.Of<ILogger<LogoReadabilityAnalyzer>>(), Mock.Of<Microsoft.Extensions.Localization.IStringLocalizer<LogoReadabilityAnalyzer>>());
|
_readabilityAnalyzerMock = new Mock<LogoReadabilityAnalyzer>(Mock.Of<ILogger<LogoReadabilityAnalyzer>>(), Mock.Of<Microsoft.Extensions.Localization.IStringLocalizer<LogoReadabilityAnalyzer>>());
|
||||||
|
_localizerMock = new Mock<IStringLocalizer<QRRapidoApp.Resources.SharedResource>>();
|
||||||
|
|
||||||
SetupDefaultConfiguration();
|
SetupDefaultConfiguration();
|
||||||
_service = new QRRapidoService(_cacheMock.Object, _configMock.Object, _loggerMock.Object, _readabilityAnalyzerMock.Object);
|
_service = new QRRapidoService(_cacheMock.Object, _configMock.Object, _loggerMock.Object, _readabilityAnalyzerMock.Object, _localizerMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupDefaultConfiguration()
|
private void SetupDefaultConfiguration()
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Histórico de QR Codes";
|
ViewData["Title"] = Localizer["HistoryTitle"];
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
}
|
}
|
||||||
@ -29,7 +29,15 @@
|
|||||||
@foreach (var qr in Model)
|
@foreach (var qr in Model)
|
||||||
{
|
{
|
||||||
<div class="col-12 col-md-6 col-lg-4 mb-4">
|
<div class="col-12 col-md-6 col-lg-4 mb-4">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm position-relative">
|
||||||
|
<!-- Delete button in top-right corner -->
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-danger position-absolute"
|
||||||
|
style="top: 8px; right: 8px; z-index: 10; padding: 4px 8px;"
|
||||||
|
onclick="confirmDeleteQR('@qr.Id')"
|
||||||
|
title="@Localizer["DeleteQRCode"]">
|
||||||
|
<i class="fas fa-trash fa-sm"></i>
|
||||||
|
</button>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<img src="data:image/png;base64,@qr.QRCodeBase64"
|
<img src="data:image/png;base64,@qr.QRCodeBase64"
|
||||||
@ -119,8 +127,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de Confirmação de Exclusão -->
|
||||||
|
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="deleteConfirmModalLabel">@Localizer["ConfirmDeleteTitle"]</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>@Localizer["ConfirmDeleteMessage"]</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@Localizer["No"]</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">@Localizer["Yes"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
|
let qrToDelete = null;
|
||||||
|
|
||||||
function regenerateQR(qrId) {
|
function regenerateQR(qrId) {
|
||||||
// Get QR data from history and regenerate
|
// Get QR data from history and regenerate
|
||||||
fetch(`/api/QR/History`)
|
fetch(`/api/QR/History`)
|
||||||
@ -147,6 +176,95 @@ function regenerateQR(qrId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function confirmDeleteQR(qrId) {
|
||||||
|
qrToDelete = qrId;
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteQR(qrId) {
|
||||||
|
// Show loading state
|
||||||
|
const confirmBtn = document.getElementById('confirmDeleteBtn');
|
||||||
|
const originalText = confirmBtn.textContent;
|
||||||
|
confirmBtn.disabled = true;
|
||||||
|
confirmBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> @Localizer["Deleting"]...';
|
||||||
|
|
||||||
|
fetch(`/api/QR/History/${qrId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Show success message
|
||||||
|
showToast(data.message || '@Localizer["QRCodeDeleted"]', 'success');
|
||||||
|
|
||||||
|
// Remove the card from the UI
|
||||||
|
const card = document.querySelector(`[onclick="confirmDeleteQR('${qrId}')"]`).closest('.col-12');
|
||||||
|
if (card) {
|
||||||
|
card.style.transition = 'opacity 0.3s ease';
|
||||||
|
card.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
card.remove();
|
||||||
|
|
||||||
|
// Check if no more cards exist
|
||||||
|
const remainingCards = document.querySelectorAll('.card');
|
||||||
|
if (remainingCards.length === 0) {
|
||||||
|
location.reload(); // Reload to show "no QR codes" message
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide modal
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteConfirmModal'));
|
||||||
|
modal.hide();
|
||||||
|
} else {
|
||||||
|
showToast(data.message || '@Localizer["ErrorDeletingQR"]', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error deleting QR:', error);
|
||||||
|
showToast('@Localizer["ErrorDeletingQR"]', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Reset button state
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.textContent = originalText;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message, type = 'info') {
|
||||||
|
// Create toast element
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show position-fixed`;
|
||||||
|
toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
||||||
|
toast.innerHTML = `
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Auto remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Handle confirm delete button
|
||||||
|
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
|
||||||
|
if (qrToDelete) {
|
||||||
|
deleteQR(qrToDelete);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Auto-refresh the page periodically to show new QR codes
|
// Auto-refresh the page periodically to show new QR codes
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// Only refresh if user is still on this page and there are QR codes
|
// Only refresh if user is still on this page and there are QR codes
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
@model QRRapidoApp.Models.User
|
@model QRRapidoApp.Models.User
|
||||||
|
@inject Microsoft.Extensions.Localization.IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Perfil do Usuário";
|
ViewData["Title"] = Localizer["UserProfileTitle"];
|
||||||
var isPremium = ViewBag.IsPremium as bool? ?? false;
|
var isPremium = ViewBag.IsPremium as bool? ?? false;
|
||||||
var monthlyQRCount = ViewBag.MonthlyQRCount as int? ?? 0;
|
var monthlyQRCount = ViewBag.MonthlyQRCount as int? ?? 0;
|
||||||
var qrHistory = ViewBag.QRHistory as List<QRRapidoApp.Models.QRCodeHistory> ?? new List<QRRapidoApp.Models.QRCodeHistory>();
|
var qrHistory = ViewBag.QRHistory as List<QRRapidoApp.Models.QRCodeHistory> ?? new List<QRRapidoApp.Models.QRCodeHistory>();
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<title>@ViewData["Title"] - QR Rapido | Gerador QR Code Ultrarrápido</title>
|
<title>@ViewData["Title"] - QR Rapido | Gerador QR Code Ultrarrápido</title>
|
||||||
|
|
||||||
<!-- SEO Meta Tags -->
|
<!-- SEO Meta Tags -->
|
||||||
<meta name="description" content="QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login.">
|
<meta name="description" content="@Localizer["QRGenerateDescription"]">
|
||||||
<meta name="keywords" content="qr rapido, gerador qr rapido, qr code rapido, codigo qr rapido, qr gratis rapido, generador qr rapido, qr ultrarapido">
|
<meta name="keywords" content="qr rapido, gerador qr rapido, qr code rapido, codigo qr rapido, qr gratis rapido, generador qr rapido, qr ultrarapido">
|
||||||
<meta name="author" content="QR Rapido">
|
<meta name="author" content="QR Rapido">
|
||||||
<meta name="robots" content="index, follow">
|
<meta name="robots" content="index, follow">
|
||||||
@ -26,8 +26,8 @@
|
|||||||
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
|
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
|
||||||
|
|
||||||
<!-- Open Graph -->
|
<!-- Open Graph -->
|
||||||
<meta property="og:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
<meta property="og:title" content="QR Rapido - @Localizer["FastestQRGeneratorWeb"]">
|
||||||
<meta property="og:description" content="Gere códigos QR em segundos! Grátis, rápido e fácil. 30 dias sem anúncios após login.">
|
<meta property="og:description" content="@Localizer["QRGenerateDescription"]">
|
||||||
<meta property="og:image" content="https://qrrapido.site/images/qrrapido-og-image.png">
|
<meta property="og:image" content="https://qrrapido.site/images/qrrapido-og-image.png">
|
||||||
<meta property="og:url" content="@Context.Request.GetDisplayUrl()">
|
<meta property="og:url" content="@Context.Request.GetDisplayUrl()">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
<!-- Twitter Cards -->
|
<!-- Twitter Cards -->
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
<meta name="twitter:title" content="QR Rapido - @Localizer["FastestQRGeneratorWeb"]">
|
||||||
<meta name="twitter:description" content="Gere códigos QR em segundos! Grátis, rápido e fácil.">
|
<meta name="twitter:description" content="@Localizer["QRGenerateDescription"]">
|
||||||
<meta name="twitter:image" content="https://qrrapido.site/images/qrrapido-twitter-card.png">
|
<meta name="twitter:image" content="https://qrrapido.site/images/qrrapido-twitter-card.png">
|
||||||
|
|
||||||
<!-- Structured Data Schema.org -->
|
<!-- Structured Data Schema.org -->
|
||||||
@ -129,6 +129,26 @@
|
|||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/css/qrrapido-theme.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/qrrapido-theme.css" asp-append-version="true" />
|
||||||
|
|
||||||
|
<!-- Translation variables for JavaScript -->
|
||||||
|
<script>
|
||||||
|
window.QRRapidoTranslations = {
|
||||||
|
shareError: '@Localizer["ShareError"]',
|
||||||
|
linkCopied: '@Localizer["LinkCopied"]',
|
||||||
|
enterQRContent: '@Localizer["EnterQRContent"]',
|
||||||
|
contentTooLong: '@Localizer["ContentTooLong"]',
|
||||||
|
featureNotAvailable: '@Localizer["FeatureNotAvailable"]',
|
||||||
|
vCardValidationError: '@Localizer["VCardValidationError"]',
|
||||||
|
logoTooLarge: '@Localizer["LogoTooLarge"]',
|
||||||
|
invalidLogoFormat: '@Localizer["InvalidLogoFormat"]',
|
||||||
|
premiumCornerStyleRequired: '@Localizer["PremiumCornerStyleRequired"]',
|
||||||
|
fastestGeneratorBrazil: '@Localizer["FastestGeneratorBrazil"]',
|
||||||
|
validationContentMinLength: '@Localizer["ValidationContentMinLength"]',
|
||||||
|
errorSavingHistory: '@Localizer["ErrorSavingHistory"]',
|
||||||
|
rateLimitReached: '@Localizer["RateLimitReached"]',
|
||||||
|
premiumLogoRequired: '@Localizer["PremiumLogoRequired"]'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/qrrapido-favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/images/qrrapido-favicon.svg">
|
||||||
<link rel="icon" type="image/png" href="/images/qrrapido-favicon-32x32.png">
|
<link rel="icon" type="image/png" href="/images/qrrapido-favicon-32x32.png">
|
||||||
@ -307,13 +327,13 @@
|
|||||||
<script>
|
<script>
|
||||||
// Fallback inline para garantir funcionamento
|
// Fallback inline para garantir funcionamento
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('🔧 Script inline iniciando como fallback...');
|
// Fallback inline script starting
|
||||||
|
|
||||||
const btn = document.getElementById('theme-toggle');
|
const btn = document.getElementById('theme-toggle');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
console.log('✅ Botão encontrado pelo script inline');
|
// Theme toggle button found
|
||||||
btn.onclick = function() {
|
btn.onclick = function() {
|
||||||
console.log('🖱️ Clique detectado (inline)');
|
// Theme toggle clicked
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const current = html.getAttribute('data-theme') || 'light';
|
const current = html.getAttribute('data-theme') || 'light';
|
||||||
const newTheme = current === 'light' ? 'dark' : 'light';
|
const newTheme = current === 'light' ? 'dark' : 'light';
|
||||||
@ -329,7 +349,7 @@
|
|||||||
text.textContent = newTheme === 'dark' ? 'Escuro' : 'Claro';
|
text.textContent = newTheme === 'dark' ? 'Escuro' : 'Claro';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
console.log('✅ Theme toggle configurado inline!');
|
// Theme toggle configured inline
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ Botão não encontrado pelo script inline!');
|
console.error('❌ Botão não encontrado pelo script inline!');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -249,7 +249,7 @@ class QRRapidoGenerator {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sharing:', error);
|
console.error('Error sharing:', error);
|
||||||
if (error.name !== 'AbortError') {
|
if (error.name !== 'AbortError') {
|
||||||
this.showError('Erro ao compartilhar. Tente outro método.');
|
this.showError(window.QRRapidoTranslations?.shareError || 'Error sharing. Try another method.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ class QRRapidoGenerator {
|
|||||||
shareWhatsApp() {
|
shareWhatsApp() {
|
||||||
if (!this.currentQR) return;
|
if (!this.currentQR) return;
|
||||||
|
|
||||||
const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil! ' + window.location.origin);
|
const text = encodeURIComponent((window.QRRapidoTranslations?.fastestGeneratorBrazil || 'QR Code generated with QR Rapido!') + ' ' + window.location.origin);
|
||||||
const url = `https://wa.me/?text=${text}`;
|
const url = `https://wa.me/?text=${text}`;
|
||||||
|
|
||||||
if (this.isMobileDevice()) {
|
if (this.isMobileDevice()) {
|
||||||
@ -272,7 +272,7 @@ class QRRapidoGenerator {
|
|||||||
shareTelegram() {
|
shareTelegram() {
|
||||||
if (!this.currentQR) return;
|
if (!this.currentQR) return;
|
||||||
|
|
||||||
const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!');
|
const text = encodeURIComponent(window.QRRapidoTranslations?.fastestGeneratorBrazil || 'QR Code generated with QR Rapido!');
|
||||||
const url = encodeURIComponent(window.location.origin);
|
const url = encodeURIComponent(window.location.origin);
|
||||||
const telegramUrl = `https://t.me/share/url?url=${url}&text=${text}`;
|
const telegramUrl = `https://t.me/share/url?url=${url}&text=${text}`;
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ class QRRapidoGenerator {
|
|||||||
textArea.remove();
|
textArea.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showSuccess('Link copiado para a área de transferência!');
|
this.showSuccess(window.QRRapidoTranslations?.linkCopied || 'Link copied to clipboard!');
|
||||||
this.trackShareEvent('copy');
|
this.trackShareEvent('copy');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error copying to clipboard:', error);
|
console.error('Error copying to clipboard:', error);
|
||||||
@ -358,7 +358,7 @@ class QRRapidoGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal tracking
|
// Internal tracking
|
||||||
console.log(`QR Code shared via ${method}`);
|
// QR Code shared via ${method}
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateQRWithTimer(e) {
|
async generateQRWithTimer(e) {
|
||||||
@ -390,11 +390,7 @@ class QRRapidoGenerator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 Enviando requisição:', {
|
// Sending request to backend
|
||||||
endpoint: requestData.endpoint,
|
|
||||||
isMultipart: requestData.isMultipart,
|
|
||||||
hasLogo: requestData.isMultipart
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(requestData.endpoint, fetchOptions);
|
const response = await fetch(requestData.endpoint, fetchOptions);
|
||||||
|
|
||||||
@ -402,12 +398,12 @@ class QRRapidoGenerator {
|
|||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
|
||||||
if (response.status === 429) {
|
if (response.status === 429) {
|
||||||
this.showUpgradeModal('Limite de QR codes atingido! Upgrade para QR Rapido Premium e gere códigos ilimitados.');
|
this.showUpgradeModal(window.QRRapidoTranslations?.rateLimitReached || 'QR codes limit reached!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 400 && errorData.requiresPremium) {
|
if (response.status === 400 && errorData.requiresPremium) {
|
||||||
this.showUpgradeModal(errorData.error || 'Logo personalizado é exclusivo do plano Premium.');
|
this.showUpgradeModal(errorData.error || window.QRRapidoTranslations?.premiumLogoRequired || 'Premium logo required.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,11 +460,11 @@ class QRRapidoGenerator {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.showError('VCard generator não está disponível');
|
this.showError(window.QRRapidoTranslations?.featureNotAvailable || 'Feature not available');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showError('Erro na validação do VCard: ' + error.message);
|
this.showError((window.QRRapidoTranslations?.vCardValidationError || 'VCard validation error: ') + error.message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (qrType === 'wifi') {
|
} else if (qrType === 'wifi') {
|
||||||
@ -498,12 +494,12 @@ class QRRapidoGenerator {
|
|||||||
const qrContent = document.getElementById('qr-content').value.trim();
|
const qrContent = document.getElementById('qr-content').value.trim();
|
||||||
|
|
||||||
if (!qrContent) {
|
if (!qrContent) {
|
||||||
this.showError('Digite o conteúdo do QR code');
|
this.showError(window.QRRapidoTranslations?.enterQRContent || 'Enter QR code content');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qrContent.length > 4000) {
|
if (qrContent.length > 4000) {
|
||||||
this.showError('Conteúdo muito longo. Máximo 4000 caracteres.');
|
this.showError(window.QRRapidoTranslations?.contentTooLong || 'Content too long. Maximum 4000 characters.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,7 +934,7 @@ class QRRapidoGenerator {
|
|||||||
if (file) {
|
if (file) {
|
||||||
// Validate file size (2MB max)
|
// Validate file size (2MB max)
|
||||||
if (file.size > 2 * 1024 * 1024) {
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
this.showError('Logo muito grande. Máximo 2MB.');
|
this.showError(window.QRRapidoTranslations?.logoTooLarge || 'Logo too large. Maximum 2MB.');
|
||||||
e.target.value = ''; // Clear the input
|
e.target.value = ''; // Clear the input
|
||||||
logoPreview?.classList.add('d-none');
|
logoPreview?.classList.add('d-none');
|
||||||
return;
|
return;
|
||||||
@ -947,7 +943,7 @@ class QRRapidoGenerator {
|
|||||||
// Validate file type
|
// Validate file type
|
||||||
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
|
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
|
||||||
if (!allowedTypes.includes(file.type)) {
|
if (!allowedTypes.includes(file.type)) {
|
||||||
this.showError('Formato inválido. Use PNG ou JPG.');
|
this.showError(window.QRRapidoTranslations?.invalidLogoFormat || 'Invalid format. Use PNG or JPG.');
|
||||||
e.target.value = ''; // Clear the input
|
e.target.value = ''; // Clear the input
|
||||||
logoPreview?.classList.add('d-none');
|
logoPreview?.classList.add('d-none');
|
||||||
return;
|
return;
|
||||||
@ -1010,7 +1006,7 @@ class QRRapidoGenerator {
|
|||||||
if (option.disabled) {
|
if (option.disabled) {
|
||||||
// Reset to square
|
// Reset to square
|
||||||
e.target.value = 'square';
|
e.target.value = 'square';
|
||||||
this.showUpgradeModal('Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.');
|
this.showUpgradeModal(window.QRRapidoTranslations?.premiumCornerStyleRequired || 'Custom corner styles are exclusive to Premium plan.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1320,7 +1316,7 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Save error:', error);
|
console.error('Save error:', error);
|
||||||
this.showError('Erro ao salvar no histórico.');
|
this.showError(window.QRRapidoTranslations?.errorSavingHistory || 'Error saving to history.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1645,7 +1641,7 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
// Feedback visual para campo de conteúdo
|
// Feedback visual para campo de conteúdo
|
||||||
if (contentField) {
|
if (contentField) {
|
||||||
this.validateField(contentField, this.contentValid, 'Conteúdo deve ter pelo menos 3 caracteres');
|
this.validateField(contentField, this.contentValid, window.QRRapidoTranslations?.validationContentMinLength || 'Content must have at least 3 characters');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateGenerateButton();
|
this.updateGenerateButton();
|
||||||
|
|||||||
@ -21,8 +21,7 @@
|
|||||||
// Estado inicial
|
// Estado inicial
|
||||||
this.updateOpacity();
|
this.updateOpacity();
|
||||||
|
|
||||||
console.log('✅ Sistema simples de opacidade inicializado');
|
// Simple opacity system initialized
|
||||||
console.log('Controlando:', controls.length, 'controles e', targets.length, 'alvos');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOpacity() {
|
updateOpacity() {
|
||||||
@ -33,11 +32,11 @@
|
|||||||
if (hasSelection) {
|
if (hasSelection) {
|
||||||
// TEM seleção = div normal
|
// TEM seleção = div normal
|
||||||
target.classList.remove(this.disabledClass);
|
target.classList.remove(this.disabledClass);
|
||||||
console.log('🟢 Div ativada - seleção detectada');
|
// Div activated - selection detected
|
||||||
} else {
|
} else {
|
||||||
// NÃO tem seleção = div opaca
|
// NÃO tem seleção = div opaca
|
||||||
target.classList.add(this.disabledClass);
|
target.classList.add(this.disabledClass);
|
||||||
console.log('🔴 Div desativada - nenhuma seleção');
|
// Div deactivated - no selection
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,9 @@
|
|||||||
console.log('🎯 Theme toggle script iniciando...');
|
// Theme toggle functionality
|
||||||
|
|
||||||
// Aguardar DOM estar pronto
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('🎯 DOM carregado, procurando elementos...');
|
|
||||||
|
|
||||||
const themeToggle = document.getElementById('theme-toggle');
|
const themeToggle = document.getElementById('theme-toggle');
|
||||||
const themeIcon = document.getElementById('theme-icon');
|
const themeIcon = document.getElementById('theme-icon');
|
||||||
const themeText = document.getElementById('theme-text');
|
const themeText = document.getElementById('theme-text');
|
||||||
|
|
||||||
console.log('🎯 Elementos encontrados:', {
|
|
||||||
toggle: !!themeToggle,
|
|
||||||
icon: !!themeIcon,
|
|
||||||
text: !!themeText
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!themeToggle) {
|
if (!themeToggle) {
|
||||||
console.error('❌ Botão theme-toggle não encontrado!');
|
console.error('❌ Botão theme-toggle não encontrado!');
|
||||||
return;
|
return;
|
||||||
@ -23,8 +13,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
let currentTheme = 'light';
|
let currentTheme = 'light';
|
||||||
|
|
||||||
function applyTheme(theme) {
|
function applyTheme(theme) {
|
||||||
console.log('🎯 Aplicando tema:', theme);
|
|
||||||
|
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
|
||||||
@ -39,8 +27,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (themeText) {
|
if (themeText) {
|
||||||
themeText.textContent = 'Escuro';
|
themeText.textContent = 'Escuro';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🌙 Tema escuro aplicado');
|
|
||||||
} else {
|
} else {
|
||||||
// Modo claro
|
// Modo claro
|
||||||
html.setAttribute('data-theme', 'light');
|
html.setAttribute('data-theme', 'light');
|
||||||
@ -52,14 +38,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (themeText) {
|
if (themeText) {
|
||||||
themeText.textContent = 'Claro';
|
themeText.textContent = 'Claro';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('☀️ Tema claro aplicado');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salvar no localStorage
|
// Salvar no localStorage
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('qr-rapido-theme', theme);
|
localStorage.setItem('qr-rapido-theme', theme);
|
||||||
console.log('💾 Tema salvo no localStorage:', theme);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('⚠️ Não foi possível salvar no localStorage:', e);
|
console.warn('⚠️ Não foi possível salvar no localStorage:', e);
|
||||||
}
|
}
|
||||||
@ -69,7 +52,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Função de toggle
|
// Função de toggle
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
console.log('🔄 Toggle theme clicado. Tema atual:', currentTheme);
|
|
||||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||||
applyTheme(newTheme);
|
applyTheme(newTheme);
|
||||||
}
|
}
|
||||||
@ -77,14 +59,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Event listener
|
// Event listener
|
||||||
themeToggle.addEventListener('click', function(e) {
|
themeToggle.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('🖱️ Clique detectado no theme toggle');
|
|
||||||
toggleTheme();
|
toggleTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aplicar tema inicial (sempre claro por enquanto)
|
// Aplicar tema inicial (sempre claro por enquanto)
|
||||||
applyTheme('light');
|
applyTheme('light');
|
||||||
|
|
||||||
console.log('✅ Theme toggle configurado com sucesso!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Função global para debug
|
// Função global para debug
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user