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 System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
@ -12,13 +13,15 @@ namespace QRRapidoApp.Controllers
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
private readonly IUserService _userService;
|
||||
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;
|
||||
_adDisplayService = adDisplayService;
|
||||
_userService = userService;
|
||||
_config = config;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
@ -33,7 +36,7 @@ namespace QRRapidoApp.Controllers
|
||||
// SEO and Analytics data
|
||||
ViewBag.Title = _config["App:TaglinePT"];
|
||||
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
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
|
||||
@ -4,6 +4,7 @@ using QRRapidoApp.Services;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
@ -15,13 +16,15 @@ namespace QRRapidoApp.Controllers
|
||||
private readonly IUserService _userService;
|
||||
private readonly AdDisplayService _adService;
|
||||
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;
|
||||
_userService = userService;
|
||||
_adService = adService;
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
[HttpPost("GenerateRapid")]
|
||||
@ -51,13 +54,13 @@ namespace QRRapidoApp.Controllers
|
||||
if (string.IsNullOrWhiteSpace(request.Content))
|
||||
{
|
||||
_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
|
||||
{
|
||||
_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
|
||||
@ -70,7 +73,7 @@ namespace QRRapidoApp.Controllers
|
||||
userId ?? "anonymous", request.CornerStyle);
|
||||
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,
|
||||
success = false
|
||||
});
|
||||
@ -84,7 +87,7 @@ namespace QRRapidoApp.Controllers
|
||||
userId ?? "anonymous", user?.IsPremium ?? false);
|
||||
return StatusCode(429, new
|
||||
{
|
||||
error = "Limite de QR codes atingido",
|
||||
error = _localizer["RateLimitReached"],
|
||||
upgradeUrl = "/Premium/Upgrade",
|
||||
success = false
|
||||
});
|
||||
@ -325,13 +328,13 @@ namespace QRRapidoApp.Controllers
|
||||
if (string.IsNullOrWhiteSpace(request.Content))
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_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
|
||||
@ -343,7 +346,7 @@ namespace QRRapidoApp.Controllers
|
||||
_logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous");
|
||||
return BadRequest(new
|
||||
{
|
||||
error = "Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade.",
|
||||
error = _localizer["PremiumLogoRequired"],
|
||||
requiresPremium = true,
|
||||
success = false
|
||||
});
|
||||
@ -363,7 +366,7 @@ namespace QRRapidoApp.Controllers
|
||||
if (logo.Length > 2 * 1024 * 1024)
|
||||
{
|
||||
_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
|
||||
@ -371,7 +374,7 @@ namespace QRRapidoApp.Controllers
|
||||
if (!allowedTypes.Contains(logo.ContentType?.ToLower()))
|
||||
{
|
||||
_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
|
||||
@ -388,7 +391,7 @@ namespace QRRapidoApp.Controllers
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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);
|
||||
return StatusCode(429, new
|
||||
{
|
||||
error = "Limite de QR codes atingido",
|
||||
error = _localizer["RateLimitReached"],
|
||||
upgradeUrl = "/Premium/Upgrade",
|
||||
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)
|
||||
{
|
||||
// Premium users have unlimited QR codes
|
||||
|
||||
@ -157,8 +157,7 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
var supportedCultures = new[]
|
||||
{
|
||||
new CultureInfo("pt-BR"),
|
||||
new CultureInfo("es"),
|
||||
new CultureInfo("en")
|
||||
new CultureInfo("es-PY")
|
||||
};
|
||||
|
||||
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||
@ -267,7 +266,7 @@ app.MapHealthChecks("/health");
|
||||
// Language routes (must be first)
|
||||
app.MapControllerRoute(
|
||||
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
|
||||
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">
|
||||
<value>Usuários anônimos: 3 QR codes por dia</value>
|
||||
</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>
|
||||
@ -336,4 +336,94 @@
|
||||
<data name="OpenNetwork" xml:space="preserve">
|
||||
<value>Sem senha </value>
|
||||
</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>
|
||||
@ -22,6 +22,7 @@ namespace QRRapidoApp.Services
|
||||
Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult);
|
||||
Task<List<QRCodeHistory>> GetUserQRHistoryAsync(string userId, int limit = 50);
|
||||
Task<QRCodeHistory?> GetQRDataAsync(string qrId);
|
||||
Task<bool> DeleteQRFromHistoryAsync(string userId, string qrId);
|
||||
Task<int> GetQRCountThisMonthAsync(string userId);
|
||||
Task<string> GetUserEmailAsync(string userId);
|
||||
Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt);
|
||||
|
||||
@ -11,6 +11,7 @@ using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace QRRapidoApp.Services
|
||||
{
|
||||
@ -21,13 +22,15 @@ namespace QRRapidoApp.Services
|
||||
private readonly ILogger<QRRapidoService> _logger;
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
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;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_readabilityAnalyzer = readabilityAnalyzer;
|
||||
_localizer = localizer;
|
||||
|
||||
// Limit simultaneous generations to maintain performance
|
||||
var maxConcurrent = _config.GetValue<int>("Performance:MaxConcurrentGenerations", 100);
|
||||
@ -215,14 +218,14 @@ namespace QRRapidoApp.Services
|
||||
|
||||
if (logoBytes == null || logoBytes.Length == 0)
|
||||
{
|
||||
errorMessage = "Logo não fornecido";
|
||||
errorMessage = _localizer["LogoNotProvided"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validar tamanho máximo
|
||||
if (logoBytes.Length > 2 * 1024 * 1024) // 2MB
|
||||
{
|
||||
errorMessage = "Logo muito grande. Máximo 2MB.";
|
||||
errorMessage = _localizer["LogoTooLarge"];
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -234,7 +237,7 @@ namespace QRRapidoApp.Services
|
||||
// Validar dimensões mínimas
|
||||
if (image.Width < 32 || image.Height < 32)
|
||||
{
|
||||
errorMessage = "Logo muito pequeno. Mínimo 32x32 pixels.";
|
||||
errorMessage = _localizer["LogoTooSmall"];
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -245,7 +248,7 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Formato de imagem inválido";
|
||||
errorMessage = _localizer["InvalidImageFormat"];
|
||||
_logger.LogError(ex, "Error validating logo format");
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
||||
@ -16,6 +16,7 @@ namespace QRRapidoApp.Tests.Services
|
||||
private readonly Mock<IConfiguration> _configMock;
|
||||
private readonly Mock<ILogger<QRRapidoService>> _loggerMock;
|
||||
private readonly Mock<LogoReadabilityAnalyzer> _readabilityAnalyzerMock;
|
||||
private readonly Mock<IStringLocalizer<QRRapidoApp.Resources.SharedResource>> _localizerMock;
|
||||
private readonly QRRapidoService _service;
|
||||
|
||||
public QRRapidoServiceTests()
|
||||
@ -24,9 +25,10 @@ namespace QRRapidoApp.Tests.Services
|
||||
_configMock = new Mock<IConfiguration>();
|
||||
_loggerMock = new Mock<ILogger<QRRapidoService>>();
|
||||
_readabilityAnalyzerMock = new Mock<LogoReadabilityAnalyzer>(Mock.Of<ILogger<LogoReadabilityAnalyzer>>(), Mock.Of<Microsoft.Extensions.Localization.IStringLocalizer<LogoReadabilityAnalyzer>>());
|
||||
_localizerMock = new Mock<IStringLocalizer<QRRapidoApp.Resources.SharedResource>>();
|
||||
|
||||
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()
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Histórico de QR Codes";
|
||||
ViewData["Title"] = Localizer["HistoryTitle"];
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
}
|
||||
@ -29,7 +29,15 @@
|
||||
@foreach (var qr in Model)
|
||||
{
|
||||
<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="text-center mb-3">
|
||||
<img src="data:image/png;base64,@qr.QRCodeBase64"
|
||||
@ -119,8 +127,29 @@
|
||||
</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 {
|
||||
<script>
|
||||
let qrToDelete = null;
|
||||
|
||||
function regenerateQR(qrId) {
|
||||
// Get QR data from history and regenerate
|
||||
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
|
||||
setInterval(() => {
|
||||
// Only refresh if user is still on this page and there are QR codes
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
@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 monthlyQRCount = ViewBag.MonthlyQRCount as int? ?? 0;
|
||||
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>
|
||||
|
||||
<!-- 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="author" content="QR Rapido">
|
||||
<meta name="robots" content="index, follow">
|
||||
@ -26,8 +26,8 @@
|
||||
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
||||
<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:title" content="QR Rapido - @Localizer["FastestQRGeneratorWeb"]">
|
||||
<meta property="og:description" content="@Localizer["QRGenerateDescription"]">
|
||||
<meta property="og:image" content="https://qrrapido.site/images/qrrapido-og-image.png">
|
||||
<meta property="og:url" content="@Context.Request.GetDisplayUrl()">
|
||||
<meta property="og:type" content="website">
|
||||
@ -36,8 +36,8 @@
|
||||
|
||||
<!-- Twitter Cards -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
||||
<meta name="twitter:description" content="Gere códigos QR em segundos! Grátis, rápido e fácil.">
|
||||
<meta name="twitter:title" content="QR Rapido - @Localizer["FastestQRGeneratorWeb"]">
|
||||
<meta name="twitter:description" content="@Localizer["QRGenerateDescription"]">
|
||||
<meta name="twitter:image" content="https://qrrapido.site/images/qrrapido-twitter-card.png">
|
||||
|
||||
<!-- Structured Data Schema.org -->
|
||||
@ -128,6 +128,26 @@
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="~/css/site.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 -->
|
||||
<link rel="icon" type="image/svg+xml" href="/images/qrrapido-favicon.svg">
|
||||
@ -307,13 +327,13 @@
|
||||
<script>
|
||||
// Fallback inline para garantir funcionamento
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🔧 Script inline iniciando como fallback...');
|
||||
// Fallback inline script starting
|
||||
|
||||
const btn = document.getElementById('theme-toggle');
|
||||
if (btn) {
|
||||
console.log('✅ Botão encontrado pelo script inline');
|
||||
// Theme toggle button found
|
||||
btn.onclick = function() {
|
||||
console.log('🖱️ Clique detectado (inline)');
|
||||
// Theme toggle clicked
|
||||
const html = document.documentElement;
|
||||
const current = html.getAttribute('data-theme') || 'light';
|
||||
const newTheme = current === 'light' ? 'dark' : 'light';
|
||||
@ -329,7 +349,7 @@
|
||||
text.textContent = newTheme === 'dark' ? 'Escuro' : 'Claro';
|
||||
}
|
||||
};
|
||||
console.log('✅ Theme toggle configurado inline!');
|
||||
// Theme toggle configured inline
|
||||
} else {
|
||||
console.error('❌ Botão não encontrado pelo script inline!');
|
||||
}
|
||||
|
||||
@ -249,7 +249,7 @@ class QRRapidoGenerator {
|
||||
} catch (error) {
|
||||
console.error('Error sharing:', error);
|
||||
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() {
|
||||
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}`;
|
||||
|
||||
if (this.isMobileDevice()) {
|
||||
@ -272,7 +272,7 @@ class QRRapidoGenerator {
|
||||
shareTelegram() {
|
||||
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 telegramUrl = `https://t.me/share/url?url=${url}&text=${text}`;
|
||||
|
||||
@ -313,7 +313,7 @@ class QRRapidoGenerator {
|
||||
textArea.remove();
|
||||
}
|
||||
|
||||
this.showSuccess('Link copiado para a área de transferência!');
|
||||
this.showSuccess(window.QRRapidoTranslations?.linkCopied || 'Link copied to clipboard!');
|
||||
this.trackShareEvent('copy');
|
||||
} catch (error) {
|
||||
console.error('Error copying to clipboard:', error);
|
||||
@ -358,7 +358,7 @@ class QRRapidoGenerator {
|
||||
}
|
||||
|
||||
// Internal tracking
|
||||
console.log(`QR Code shared via ${method}`);
|
||||
// QR Code shared via ${method}
|
||||
}
|
||||
|
||||
async generateQRWithTimer(e) {
|
||||
@ -390,11 +390,7 @@ class QRRapidoGenerator {
|
||||
};
|
||||
}
|
||||
|
||||
console.log('🚀 Enviando requisição:', {
|
||||
endpoint: requestData.endpoint,
|
||||
isMultipart: requestData.isMultipart,
|
||||
hasLogo: requestData.isMultipart
|
||||
});
|
||||
// Sending request to backend
|
||||
|
||||
const response = await fetch(requestData.endpoint, fetchOptions);
|
||||
|
||||
@ -402,12 +398,12 @@ class QRRapidoGenerator {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -464,11 +460,11 @@ class QRRapidoGenerator {
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
this.showError('VCard generator não está disponível');
|
||||
this.showError(window.QRRapidoTranslations?.featureNotAvailable || 'Feature not available');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Erro na validação do VCard: ' + error.message);
|
||||
this.showError((window.QRRapidoTranslations?.vCardValidationError || 'VCard validation error: ') + error.message);
|
||||
return false;
|
||||
}
|
||||
} else if (qrType === 'wifi') {
|
||||
@ -498,12 +494,12 @@ class QRRapidoGenerator {
|
||||
const qrContent = document.getElementById('qr-content').value.trim();
|
||||
|
||||
if (!qrContent) {
|
||||
this.showError('Digite o conteúdo do QR code');
|
||||
this.showError(window.QRRapidoTranslations?.enterQRContent || 'Enter QR code content');
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -938,7 +934,7 @@ class QRRapidoGenerator {
|
||||
if (file) {
|
||||
// Validate file size (2MB max)
|
||||
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
|
||||
logoPreview?.classList.add('d-none');
|
||||
return;
|
||||
@ -947,7 +943,7 @@ class QRRapidoGenerator {
|
||||
// Validate file type
|
||||
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
|
||||
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
|
||||
logoPreview?.classList.add('d-none');
|
||||
return;
|
||||
@ -1010,7 +1006,7 @@ class QRRapidoGenerator {
|
||||
if (option.disabled) {
|
||||
// Reset to 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;
|
||||
}
|
||||
}
|
||||
@ -1320,7 +1316,7 @@ class QRRapidoGenerator {
|
||||
|
||||
} catch (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
|
||||
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();
|
||||
|
||||
@ -21,8 +21,7 @@
|
||||
// Estado inicial
|
||||
this.updateOpacity();
|
||||
|
||||
console.log('✅ Sistema simples de opacidade inicializado');
|
||||
console.log('Controlando:', controls.length, 'controles e', targets.length, 'alvos');
|
||||
// Simple opacity system initialized
|
||||
}
|
||||
|
||||
updateOpacity() {
|
||||
@ -33,11 +32,11 @@
|
||||
if (hasSelection) {
|
||||
// TEM seleção = div normal
|
||||
target.classList.remove(this.disabledClass);
|
||||
console.log('🟢 Div ativada - seleção detectada');
|
||||
// Div activated - selection detected
|
||||
} else {
|
||||
// NÃO tem seleção = div opaca
|
||||
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...');
|
||||
|
||||
// Aguardar DOM estar pronto
|
||||
// Theme toggle functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🎯 DOM carregado, procurando elementos...');
|
||||
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const themeIcon = document.getElementById('theme-icon');
|
||||
const themeText = document.getElementById('theme-text');
|
||||
|
||||
console.log('🎯 Elementos encontrados:', {
|
||||
toggle: !!themeToggle,
|
||||
icon: !!themeIcon,
|
||||
text: !!themeText
|
||||
});
|
||||
|
||||
if (!themeToggle) {
|
||||
console.error('❌ Botão theme-toggle não encontrado!');
|
||||
return;
|
||||
@ -23,8 +13,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let currentTheme = 'light';
|
||||
|
||||
function applyTheme(theme) {
|
||||
console.log('🎯 Aplicando tema:', theme);
|
||||
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
|
||||
@ -39,8 +27,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (themeText) {
|
||||
themeText.textContent = 'Escuro';
|
||||
}
|
||||
|
||||
console.log('🌙 Tema escuro aplicado');
|
||||
} else {
|
||||
// Modo claro
|
||||
html.setAttribute('data-theme', 'light');
|
||||
@ -52,14 +38,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (themeText) {
|
||||
themeText.textContent = 'Claro';
|
||||
}
|
||||
|
||||
console.log('☀️ Tema claro aplicado');
|
||||
}
|
||||
|
||||
// Salvar no localStorage
|
||||
try {
|
||||
localStorage.setItem('qr-rapido-theme', theme);
|
||||
console.log('💾 Tema salvo no localStorage:', theme);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Não foi possível salvar no localStorage:', e);
|
||||
}
|
||||
@ -69,7 +52,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Função de toggle
|
||||
function toggleTheme() {
|
||||
console.log('🔄 Toggle theme clicado. Tema atual:', currentTheme);
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
applyTheme(newTheme);
|
||||
}
|
||||
@ -77,14 +59,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Event listener
|
||||
themeToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
console.log('🖱️ Clique detectado no theme toggle');
|
||||
toggleTheme();
|
||||
});
|
||||
|
||||
// Aplicar tema inicial (sempre claro por enquanto)
|
||||
applyTheme('light');
|
||||
|
||||
console.log('✅ Theme toggle configurado com sucesso!');
|
||||
});
|
||||
|
||||
// Função global para debug
|
||||
|
||||
Loading…
Reference in New Issue
Block a user