feat: Historico e localização!

This commit is contained in:
Ricardo Carneiro 2025-07-29 19:11:47 -03:00
parent c80b73e32f
commit b189ea7275
30 changed files with 2540 additions and 565 deletions

View File

@ -4,7 +4,8 @@
"Bash(dotnet new:*)", "Bash(dotnet new:*)",
"Bash(find:*)", "Bash(find:*)",
"Bash(dotnet build:*)", "Bash(dotnet build:*)",
"Bash(timeout:*)" "Bash(timeout:*)",
"Bash(rm:*)"
], ],
"deny": [] "deny": []
} }

View File

@ -15,12 +15,15 @@ namespace QRRapidoApp.Controllers
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly AdDisplayService _adDisplayService; private readonly AdDisplayService _adDisplayService;
private readonly ILogger<AccountController> _logger; private readonly ILogger<AccountController> _logger;
private readonly IConfiguration _configuration;
public AccountController(IUserService userService, AdDisplayService adDisplayService, ILogger<AccountController> logger) public AccountController(IUserService userService, AdDisplayService adDisplayService,
ILogger<AccountController> logger, IConfiguration configuration)
{ {
_userService = userService; _userService = userService;
_adDisplayService = adDisplayService; _adDisplayService = adDisplayService;
_logger = logger; _logger = logger;
_configuration = configuration;
} }
[HttpGet] [HttpGet]
@ -33,9 +36,10 @@ namespace QRRapidoApp.Controllers
[HttpGet] [HttpGet]
public IActionResult LoginGoogle(string returnUrl = "/") public IActionResult LoginGoogle(string returnUrl = "/")
{ {
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
var properties = new AuthenticationProperties var properties = new AuthenticationProperties
{ {
RedirectUri = Url.Action("GoogleCallback"), RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
Items = { { "returnUrl", returnUrl } } Items = { { "returnUrl", returnUrl } }
}; };
return Challenge(properties, GoogleDefaults.AuthenticationScheme); return Challenge(properties, GoogleDefaults.AuthenticationScheme);
@ -44,11 +48,8 @@ namespace QRRapidoApp.Controllers
[HttpGet] [HttpGet]
public IActionResult LoginMicrosoft(string returnUrl = "/") public IActionResult LoginMicrosoft(string returnUrl = "/")
{ {
var properties = new AuthenticationProperties var redirectUrl = Url.Action("MicrosoftCallback", "Account", new { returnUrl });
{ var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
RedirectUri = Url.Action("MicrosoftCallback"),
Items = { { "returnUrl", returnUrl } }
};
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme); return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
} }

View File

@ -0,0 +1,120 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using QRRapidoApp.Services;
using System.Security.Claims;
using System.Threading.Tasks;
using QRRapidoApp.Models.ViewModels;
using System.Linq;
namespace QRRapidoApp.Controllers
{
[Authorize]
public class PagamentoController : Controller
{
private readonly IPlanService _planService;
private readonly IUserService _userService;
private readonly StripeService _stripeService;
private readonly ILogger<PagamentoController> _logger;
public PagamentoController(IPlanService planService, IUserService userService, StripeService stripeService, ILogger<PagamentoController> logger)
{
_planService = planService;
_userService = userService;
_stripeService = stripeService;
_logger = logger;
}
[HttpGet]
public async Task<IActionResult> SelecaoPlano()
{
var plans = await _planService.GetActivePlansAsync();
var countryCode = GetUserCountryCode(); // Implement this method based on your needs
var model = new SelecaoPlanoViewModel
{
Plans = plans,
CountryCode = countryCode
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> CreateCheckout(string planId)
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Json(new { success = false, error = "User not authenticated" });
}
var plan = await _planService.GetPlanByIdAsync(planId);
if (plan == null)
{
return Json(new { success = false, error = "Plan not found" });
}
var countryCode = GetUserCountryCode();
var priceId = plan.PricesByCountry.ContainsKey(countryCode)
? plan.PricesByCountry[countryCode].StripePriceId
: plan.StripePriceId;
try
{
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId);
return Json(new { success = true, url = checkoutUrl });
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error creating checkout session for user {userId} and plan {planId}");
return Json(new { success = false, error = ex.Message });
}
}
[HttpGet]
public IActionResult Sucesso()
{
ViewBag.SuccessMessage = "Pagamento concluído com sucesso! Bem-vindo ao Premium.";
return View();
}
[HttpGet]
public IActionResult Cancelar()
{
ViewBag.CancelMessage = "O pagamento foi cancelado. Você pode tentar novamente a qualquer momento.";
return View("SelecaoPlano");
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> StripeWebhook()
{
try
{
using var reader = new StreamReader(HttpContext.Request.Body);
var json = await reader.ReadToEndAsync();
var signature = Request.Headers["Stripe-Signature"].FirstOrDefault();
if (string.IsNullOrEmpty(signature))
{
return BadRequest("Missing Stripe signature");
}
await _stripeService.HandleWebhookAsync(json, signature);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing Stripe webhook");
return BadRequest(ex.Message);
}
}
private string GetUserCountryCode()
{
// Prioritize Cloudflare header, fallback to a default or other methods
return HttpContext.Request.Headers["CF-IPCountry"].FirstOrDefault() ?? "BR";
}
}
}

View File

@ -25,111 +25,9 @@ namespace QRRapidoApp.Controllers
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> Upgrade() public IActionResult Upgrade()
{ {
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; return RedirectToAction("SelecaoPlano", "Pagamento");
if (string.IsNullOrEmpty(userId))
{
return RedirectToAction("Login", "Account");
}
var user = await _userService.GetUserAsync(userId);
if (user?.IsPremium == true)
{
return RedirectToAction("Dashboard");
}
var model = new UpgradeViewModel
{
CurrentPlan = "Free",
PremiumPrice = _config.GetValue<decimal>("Premium:PremiumPrice"),
Features = _config.GetSection("Premium:Features").Get<Dictionary<string, bool>>() ?? new(),
RemainingQRs = await _userService.GetDailyQRCountAsync(userId),
IsAdFreeActive = !await _adDisplayService.ShouldShowAds(userId),
DaysUntilAdExpiry = (int)((await _adDisplayService.GetAdFreeExpiryDate(userId) - DateTime.UtcNow)?.TotalDays ?? 0)
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> CreateCheckout()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Json(new { success = false, error = "User not authenticated" });
}
var priceId = _config["Stripe:PriceId"];
if (string.IsNullOrEmpty(priceId))
{
return Json(new { success = false, error = "Stripe not configured" });
}
try
{
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId);
return Json(new { success = true, url = checkoutUrl });
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error creating checkout session for user {userId}");
return Json(new { success = false, error = ex.Message });
}
}
[HttpGet]
public async Task<IActionResult> Success(string session_id)
{
if (string.IsNullOrEmpty(session_id))
{
return RedirectToAction("Upgrade");
}
try
{
ViewBag.Success = true;
ViewBag.SessionId = session_id;
return View();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error processing successful payment for session {session_id}");
return RedirectToAction("Upgrade");
}
}
[HttpGet]
public IActionResult Cancel()
{
ViewBag.Cancelled = true;
return View("Upgrade");
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> StripeWebhook()
{
try
{
using var reader = new StreamReader(HttpContext.Request.Body);
var json = await reader.ReadToEndAsync();
var signature = Request.Headers["Stripe-Signature"].FirstOrDefault();
if (string.IsNullOrEmpty(signature))
{
return BadRequest("Missing Stripe signature");
}
await _stripeService.HandleWebhookAsync(json, signature);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing Stripe webhook");
return BadRequest(ex.Message);
}
} }
[HttpGet] [HttpGet]

View File

@ -30,8 +30,9 @@ namespace QRRapidoApp.Data
} }
} }
public IMongoCollection<User>? Users => _isConnected ? _database?.GetCollection<User>("users") : null; public IMongoCollection<User> Users => _database.GetCollection<User>("users");
public IMongoCollection<QRCodeHistory>? QRCodeHistory => _isConnected ? _database?.GetCollection<QRCodeHistory>("qr_codes") : null; public IMongoCollection<QRCodeHistory> QRCodeHistory => _database.GetCollection<QRCodeHistory>("qrCodeHistory");
public IMongoCollection<Plan> Plans => _database.GetCollection<Plan>("plans");
public IMongoCollection<AdFreeSession>? AdFreeSessions => _isConnected ? _database?.GetCollection<AdFreeSession>("ad_free_sessions") : null; public IMongoCollection<AdFreeSession>? AdFreeSessions => _isConnected ? _database?.GetCollection<AdFreeSession>("ad_free_sessions") : null;
public IMongoDatabase? Database => _isConnected ? _database : null; public IMongoDatabase? Database => _isConnected ? _database : null;

View File

@ -0,0 +1,111 @@
using System.Globalization;
namespace QRRapidoApp.Middleware
{
public class LanguageRedirectionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LanguageRedirectionMiddleware> _logger;
private readonly string[] _supportedCultures = { "pt-BR", "es", "en" };
private const string DefaultCulture = "pt-BR";
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value?.TrimStart('/') ?? "";
// Skip if already has culture in path, or if it's an API, static file, or special route
if (HasCultureInPath(path) || IsSpecialRoute(path))
{
await _next(context);
return;
}
// Detect browser language
var detectedCulture = DetectBrowserLanguage(context);
// Build redirect URL with culture
var redirectUrl = $"/{detectedCulture}";
if (!string.IsNullOrEmpty(path))
{
redirectUrl += $"/{path}";
}
// Add query string if present
if (context.Request.QueryString.HasValue)
{
redirectUrl += context.Request.QueryString.Value;
}
_logger.LogInformation("Redirecting to localized URL: {RedirectUrl} (detected culture: {Culture})",
redirectUrl, detectedCulture);
context.Response.Redirect(redirectUrl, permanent: false);
}
private bool HasCultureInPath(string path)
{
if (string.IsNullOrEmpty(path))
return false;
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0)
return false;
return _supportedCultures.Contains(segments[0]);
}
private bool IsSpecialRoute(string path)
{
var specialRoutes = new[]
{
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
"favicon.ico", "robots.txt", "sitemap.xml"
};
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
}
private string DetectBrowserLanguage(HttpContext context)
{
var acceptLanguage = context.Request.GetTypedHeaders().AcceptLanguage;
if (acceptLanguage != null && acceptLanguage.Any())
{
// Check for exact matches first
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
{
var langCode = lang.Value.Value;
// Special case: es-PY should redirect to pt-BR
if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase))
{
return DefaultCulture;
}
// Check exact match
if (_supportedCultures.Contains(langCode))
{
return langCode;
}
// Check language part only (e.g., 'es' from 'es-AR')
var languagePart = langCode.Split('-')[0];
var matchingCulture = _supportedCultures.FirstOrDefault(c => c.StartsWith(languagePart + "-") || c == languagePart);
if (matchingCulture != null)
{
return matchingCulture;
}
}
}
// Default fallback
return DefaultCulture;
}
}
}

View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
namespace QRRapidoApp.Models.Extensions
{
public static class PlanExtensions
{
public static string GetLocalizedName(this Plan plan, string languageCode)
{
if (plan.Name.TryGetValue(languageCode, out var name))
return name;
// Fallback to Portuguese if language not found
if (plan.Name.TryGetValue("pt-BR", out var ptName))
return ptName;
// Final fallback to first available language
return plan.Name.Values.FirstOrDefault() ?? "Plan";
}
public static string GetLocalizedDescription(this Plan plan, string languageCode)
{
if (plan.Description.TryGetValue(languageCode, out var description))
return description;
// Fallback to Portuguese if language not found
if (plan.Description.TryGetValue("pt-BR", out var ptDescription))
return ptDescription;
// Final fallback to first available language
return plan.Description.Values.FirstOrDefault() ?? "Premium plan description";
}
public static List<string> GetLocalizedFeatures(this Plan plan, string languageCode)
{
if (plan.Features.TryGetValue(languageCode, out var features))
return features;
// Fallback to Portuguese if language not found
if (plan.Features.TryGetValue("pt-BR", out var ptFeatures))
return ptFeatures;
// Final fallback to first available language
return plan.Features.Values.FirstOrDefault() ?? new List<string>();
}
public static string GetLanguageCode(string culture)
{
return culture switch
{
"pt-BR" or "pt" => "pt-BR",
"es" or "es-ES" => "es",
"en" or "en-US" => "en",
_ => "pt-BR" // Default to Portuguese
};
}
}
}

47
Models/Plan.cs Normal file
View File

@ -0,0 +1,47 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Collections.Generic;
namespace QRRapidoApp.Models
{
public class Plan
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = string.Empty;
[BsonElement("name")]
public Dictionary<string, string> Name { get; set; } = new(); // e.g., {"pt": "Premium Mensal", "en": "Premium Monthly", "es": "Premium Mensual"}
[BsonElement("description")]
public Dictionary<string, string> Description { get; set; } = new(); // Multilingual descriptions
[BsonElement("features")]
public Dictionary<string, List<string>> Features { get; set; } = new(); // Multilingual feature lists
[BsonElement("interval")]
public string Interval { get; set; } = string.Empty; // e.g., "month", "year"
[BsonElement("stripePriceId")]
public string StripePriceId { get; set; } = string.Empty; // Default Price ID
[BsonElement("pricesByCountry")]
public Dictionary<string, PriceInfo> PricesByCountry { get; set; } = new();
[BsonElement("isActive")]
public bool IsActive { get; set; } = true;
}
public class PriceInfo
{
[BsonElement("amount")]
public decimal Amount { get; set; }
[BsonElement("currency")]
public string Currency { get; set; } = string.Empty;
[BsonElement("stripePriceId")]
public string StripePriceId { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,12 @@
using QRRapidoApp.Models;
using System.Collections.Generic;
namespace QRRapidoApp.Models.ViewModels
{
public class SelecaoPlanoViewModel
{
public List<Plan> Plans { get; set; } = new();
public string CountryCode { get; set; } = "BR";
}
}

View File

@ -17,6 +17,7 @@ using System.Globalization;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes; using Serilog.Sinks.SystemConsole.Themes;
using Microsoft.AspNetCore.Mvc.Razor;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -28,33 +29,28 @@ Log.Logger = new LoggerConfiguration()
.Enrich.WithProcessId() .Enrich.WithProcessId()
.Enrich.WithThreadId() .Enrich.WithThreadId()
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido") .Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido")
.Enrich.WithProperty("Environment", "Dev")
.WriteTo.Async(a => a.Console( .WriteTo.Async(a => a.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}", outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
theme: AnsiConsoleTheme.Code)) theme: AnsiConsoleTheme.Code))
.WriteTo.Async(a => { .WriteTo.Async(a => a.Seq(builder.Configuration["Serilog:SeqUrl"],
var seqUrl = builder.Configuration["Serilog:SeqUrl"]; apiKey: builder.Configuration["Serilog:ApiKey"]=="" ? null : builder.Configuration["Serilog:ApiKey"]))
var apiKey = builder.Configuration["Serilog:ApiKey"];
if (!string.IsNullOrEmpty(seqUrl))
{
try
{
// Temporarily skip Seq until packages are installed
Console.WriteLine($"Seq configured for: {seqUrl}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to configure Seq sink: {ex.Message}");
// Continue without Seq - will still log to console
}
}
})
.CreateLogger(); .CreateLogger();
builder.Host.UseSerilog(); builder.Host.UseSerilog();
// Add services to the container // Add services to the container
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews()
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
builder.Services.AddDistributedMemoryCache(); // Armazena os dados da sess<73>o na mem<65>ria
builder.Services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.IdleTimeout = TimeSpan.FromMinutes(30); // Tempo de expira<72><61>o
});
// Add HttpClient for health checks // Add HttpClient for health checks
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
@ -132,7 +128,8 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"]; StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
// Localization // Localization
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "");
//builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.Configure<RequestLocalizationOptions>(options => builder.Services.Configure<RequestLocalizationOptions>(options =>
{ {
var supportedCultures = new[] var supportedCultures = new[]
@ -142,17 +139,24 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
new CultureInfo("en") new CultureInfo("en")
}; };
options.DefaultRequestCulture = new RequestCulture("pt-BR"); options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
options.SupportedCultures = supportedCultures; options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures; options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider()); options.FallBackToParentCultures = true;
options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider()); options.FallBackToParentUICultures = true;
// Clear default providers and add custom ones in priority order
options.RequestCultureProviders.Clear();
options.RequestCultureProviders.Add(new RouteDataRequestCultureProvider());
options.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
options.RequestCultureProviders.Add(new CookieRequestCultureProvider());
}); });
// Custom Services // Custom Services
builder.Services.AddScoped<IQRCodeService, QRRapidoService>(); builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IPlanService, QRRapidoApp.Services.PlanService>();
builder.Services.AddScoped<AdDisplayService>(); builder.Services.AddScoped<AdDisplayService>();
builder.Services.AddScoped<StripeService>(); builder.Services.AddScoped<StripeService>();
@ -206,6 +210,9 @@ app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseStaticFiles();
// Language redirection middleware (before routing)
app.UseMiddleware<LanguageRedirectionMiddleware>();
app.UseRouting(); app.UseRouting();
app.UseCors("AllowSpecificOrigins"); app.UseCors("AllowSpecificOrigins");
@ -216,26 +223,28 @@ app.UseRequestLocalization();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseSession();
// Custom middleware // Custom middleware
app.UseMiddleware<LastLoginUpdateMiddleware>(); app.UseMiddleware<LastLoginUpdateMiddleware>();
// Health check endpoint // Health check endpoint
app.MapHealthChecks("/health"); app.MapHealthChecks("/health");
// Controller routes // Language routes (must be first)
app.MapControllerRoute( app.MapControllerRoute(
name: "default", name: "localized",
pattern: "{controller=Home}/{action=Index}/{id?}"); pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
// API routes // API routes
app.MapControllerRoute( app.MapControllerRoute(
name: "api", name: "api",
pattern: "api/{controller}/{action=Index}/{id?}"); pattern: "api/{controller}/{action=Index}/{id?}");
// Language routes // Default fallback route (for development/testing without culture)
app.MapControllerRoute( app.MapControllerRoute(
name: "localized", name: "default",
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}"); pattern: "{controller=Home}/{action=Index}/{id?}");
try try
{ {

View File

@ -20,6 +20,7 @@
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
<PackageReference Include="Stripe.net" Version="43.15.0" /> <PackageReference Include="Stripe.net" Version="43.15.0" />
<PackageReference Include="StackExchange.Redis" Version="2.7.4" /> <PackageReference Include="StackExchange.Redis" Version="2.7.4" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
@ -34,7 +35,6 @@
<Folder Include="wwwroot\images\" /> <Folder Include="wwwroot\images\" />
<Folder Include="wwwroot\css\" /> <Folder Include="wwwroot\css\" />
<Folder Include="wwwroot\js\" /> <Folder Include="wwwroot\js\" />
<Folder Include="Resources\" />
<Folder Include="Data\" /> <Folder Include="Data\" />
<Folder Include="Services\" /> <Folder Include="Services\" />
<Folder Include="Models\" /> <Folder Include="Models\" />
@ -42,4 +42,28 @@
<Folder Include="Tests\" /> <Folder Include="Tests\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Resources\SharedResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SharedResource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\SharedResource.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\SharedResource.es.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\SharedResource.pt-BR.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\SharedResource.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>SharedResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

693
Resources/SharedResource.Designer.cs generated Normal file
View File

@ -0,0 +1,693 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace QRRapidoApp.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class SharedResource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal SharedResource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QRRapidoApp.Resources.SharedResource", typeof(SharedResource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Unlock all premium features!.
/// </summary>
public static string AdFreeOffer {
get {
return ResourceManager.GetString("AdFreeOffer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Advanced Customization.
/// </summary>
public static string AdvancedCustomization {
get {
return ResourceManager.GetString("AdvancedCustomization", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Background Color.
/// </summary>
public static string BackgroundColor {
get {
return ResourceManager.GetString("BackgroundColor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Back to generator.
/// </summary>
public static string BackToGenerator {
get {
return ResourceManager.GetString("BackToGenerator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Classic.
/// </summary>
public static string Classic {
get {
return ResourceManager.GetString("Classic", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Classic.
/// </summary>
public static string ClassicStyle {
get {
return ResourceManager.GetString("ClassicStyle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Colorful.
/// </summary>
public static string Colorful {
get {
return ResourceManager.GetString("Colorful", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Colorful.
/// </summary>
public static string ColorfulStyle {
get {
return ResourceManager.GetString("ColorfulStyle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Content.
/// </summary>
public static string Content {
get {
return ResourceManager.GetString("Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Content hints.
/// </summary>
public static string ContentHints {
get {
return ResourceManager.GetString("ContentHints", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter your QR code content here....
/// </summary>
public static string ContentPlaceholder {
get {
return ResourceManager.GetString("ContentPlaceholder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Corner Style.
/// </summary>
public static string CornerStyle {
get {
return ResourceManager.GetString("CornerStyle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Create QR Code Quickly.
/// </summary>
public static string CreateQRCodeQuickly {
get {
return ResourceManager.GetString("CreateQRCodeQuickly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Download PDF.
/// </summary>
public static string DownloadPDF {
get {
return ResourceManager.GetString("DownloadPDF", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Download PNG.
/// </summary>
public static string DownloadPNG {
get {
return ResourceManager.GetString("DownloadPNG", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Download SVG (Vector).
/// </summary>
public static string DownloadSVG {
get {
return ResourceManager.GetString("DownloadSVG", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Dynamic QR (Premium).
/// </summary>
public static string DynamicQRPremium {
get {
return ResourceManager.GetString("DynamicQRPremium", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Dynamic QR (Premium).
/// </summary>
public static string DynamicType {
get {
return ResourceManager.GetString("DynamicType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Email.
/// </summary>
public static string EmailType {
get {
return ResourceManager.GetString("EmailType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter your QR code content here....
/// </summary>
public static string EnterQRCodeContent {
get {
return ResourceManager.GetString("EnterQRCodeContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generation error. Try again..
/// </summary>
public static string Error {
get {
return ResourceManager.GetString("Error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fast generation!.
/// </summary>
public static string Fast {
get {
return ResourceManager.GetString("Fast", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generated in.
/// </summary>
public static string GeneratedIn {
get {
return ResourceManager.GetString("GeneratedIn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate QR Code.
/// </summary>
public static string GenerateQR {
get {
return ResourceManager.GetString("GenerateQR", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate QR Code Rapidly.
/// </summary>
public static string GenerateRapidly {
get {
return ResourceManager.GetString("GenerateRapidly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Google.
/// </summary>
public static string Google {
get {
return ResourceManager.GetString("Google", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
public static string Login {
get {
return ResourceManager.GetString("Login", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!.
/// </summary>
public static string LoginBenefits {
get {
return ResourceManager.GetString("LoginBenefits", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login to save to history.
/// </summary>
public static string LoginToSave {
get {
return ResourceManager.GetString("LoginToSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login with.
/// </summary>
public static string LoginWith {
get {
return ResourceManager.GetString("LoginWith", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logo/Icon.
/// </summary>
public static string Logo {
get {
return ResourceManager.GetString("Logo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Margin.
/// </summary>
public static string Margin {
get {
return ResourceManager.GetString("Margin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Microsoft.
/// </summary>
public static string Microsoft {
get {
return ResourceManager.GetString("Microsoft", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Modern.
/// </summary>
public static string Modern {
get {
return ResourceManager.GetString("Modern", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Modern.
/// </summary>
public static string ModernStyle {
get {
return ResourceManager.GetString("ModernStyle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No Ads • History • Unlimited QR.
/// </summary>
public static string NoAdsHistoryUnlimitedQR {
get {
return ResourceManager.GetString("NoAdsHistoryUnlimitedQR", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Normal generation.
/// </summary>
public static string Normal {
get {
return ResourceManager.GetString("Normal", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to QR Rapido Premium.
/// </summary>
public static string PremiumTitle {
get {
return ResourceManager.GetString("PremiumTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Premium User Active.
/// </summary>
public static string PremiumUserActive {
get {
return ResourceManager.GetString("PremiumUserActive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Preview.
/// </summary>
public static string Preview {
get {
return ResourceManager.GetString("Preview", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your QR code will appear here in seconds.
/// </summary>
public static string PreviewPlaceholder {
get {
return ResourceManager.GetString("PreviewPlaceholder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Primary Color.
/// </summary>
public static string PrimaryColor {
get {
return ResourceManager.GetString("PrimaryColor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Privacy Policy.
/// </summary>
public static string Privacy {
get {
return ResourceManager.GetString("Privacy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to QR codes remaining.
/// </summary>
public static string QRCodesRemaining {
get {
return ResourceManager.GetString("QRCodesRemaining", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to QR Code Type.
/// </summary>
public static string QRCodeType {
get {
return ResourceManager.GetString("QRCodeType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to QR Code Type.
/// </summary>
public static string QRType {
get {
return ResourceManager.GetString("QRType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Quick Style.
/// </summary>
public static string QuickStyle {
get {
return ResourceManager.GetString("QuickStyle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save to History.
/// </summary>
public static string SaveToHistory {
get {
return ResourceManager.GetString("SaveToHistory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to s.
/// </summary>
public static string Seconds {
get {
return ResourceManager.GetString("Seconds", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select type.
/// </summary>
public static string SelectType {
get {
return ResourceManager.GetString("SelectType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Simple Text.
/// </summary>
public static string SimpleText {
get {
return ResourceManager.GetString("SimpleText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Size.
/// </summary>
public static string Size {
get {
return ResourceManager.GetString("Size", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SMS.
/// </summary>
public static string SMSType {
get {
return ResourceManager.GetString("SMSType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Premium Offer!.
/// </summary>
public static string SpecialOffer {
get {
return ResourceManager.GetString("SpecialOffer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Short URLs generate faster.
/// </summary>
public static string SpeedTip1 {
get {
return ResourceManager.GetString("SpeedTip1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Less text = higher speed.
/// </summary>
public static string SpeedTip2 {
get {
return ResourceManager.GetString("SpeedTip2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Solid colors optimize the process.
/// </summary>
public static string SpeedTip3 {
get {
return ResourceManager.GetString("SpeedTip3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Smaller sizes speed up downloads.
/// </summary>
public static string SpeedTip4 {
get {
return ResourceManager.GetString("SpeedTip4", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tips for Faster QR.
/// </summary>
public static string SpeedTipsTitle {
get {
return ResourceManager.GetString("SpeedTipsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to QR Code saved to history!.
/// </summary>
public static string Success {
get {
return ResourceManager.GetString("Success", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate QR codes in seconds!.
/// </summary>
public static string Tagline {
get {
return ResourceManager.GetString("Tagline", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Plain Text.
/// </summary>
public static string TextType {
get {
return ResourceManager.GetString("TextType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ultra fast generation!.
/// </summary>
public static string UltraFast {
get {
return ResourceManager.GetString("UltraFast", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ultra-fast generation guaranteed.
/// </summary>
public static string UltraFastGeneration {
get {
return ResourceManager.GetString("UltraFastGeneration", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unlimited today.
/// </summary>
public static string UnlimitedToday {
get {
return ResourceManager.GetString("UnlimitedToday", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to URL/Link.
/// </summary>
public static string URLLink {
get {
return ResourceManager.GetString("URLLink", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to URL/Link.
/// </summary>
public static string URLType {
get {
return ResourceManager.GetString("URLType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Business Card.
/// </summary>
public static string VCard {
get {
return ResourceManager.GetString("VCard", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Business Card.
/// </summary>
public static string VCardType {
get {
return ResourceManager.GetString("VCardType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to WiFi.
/// </summary>
public static string WiFiType {
get {
return ResourceManager.GetString("WiFiType", resourceCulture);
}
}
}
}

View File

@ -59,28 +59,28 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Tagline" xml:space="preserve"> <data name="Tagline" xml:space="preserve">
<value>Gere QR codes em segundos!</value> <value>Generate QR codes in seconds!</value>
</data> </data>
<data name="GenerateQR" xml:space="preserve"> <data name="GenerateQR" xml:space="preserve">
<value>Gerar QR Code</value> <value>Generate QR Code</value>
</data> </data>
<data name="QRType" xml:space="preserve"> <data name="QRType" xml:space="preserve">
<value>Tipo de QR Code</value> <value>QR Code Type</value>
</data> </data>
<data name="Content" xml:space="preserve"> <data name="Content" xml:space="preserve">
<value>Conteúdo</value> <value>Content</value>
</data> </data>
<data name="URLType" xml:space="preserve"> <data name="URLType" xml:space="preserve">
<value>URL/Link</value> <value>URL/Link</value>
</data> </data>
<data name="TextType" xml:space="preserve"> <data name="TextType" xml:space="preserve">
<value>Texto Simples</value> <value>Plain Text</value>
</data> </data>
<data name="WiFiType" xml:space="preserve"> <data name="WiFiType" xml:space="preserve">
<value>WiFi</value> <value>WiFi</value>
</data> </data>
<data name="VCardType" xml:space="preserve"> <data name="VCardType" xml:space="preserve">
<value>Cartão de Visita</value> <value>Business Card</value>
</data> </data>
<data name="SMSType" xml:space="preserve"> <data name="SMSType" xml:space="preserve">
<value>SMS</value> <value>SMS</value>
@ -89,94 +89,94 @@
<value>Email</value> <value>Email</value>
</data> </data>
<data name="DynamicType" xml:space="preserve"> <data name="DynamicType" xml:space="preserve">
<value>QR Dinâmico (Premium)</value> <value>Dynamic QR (Premium)</value>
</data> </data>
<data name="QuickStyle" xml:space="preserve"> <data name="QuickStyle" xml:space="preserve">
<value>Estilo Rápido</value> <value>Quick Style</value>
</data> </data>
<data name="ClassicStyle" xml:space="preserve"> <data name="ClassicStyle" xml:space="preserve">
<value>Clássico</value> <value>Classic</value>
</data> </data>
<data name="ModernStyle" xml:space="preserve"> <data name="ModernStyle" xml:space="preserve">
<value>Moderno</value> <value>Modern</value>
</data> </data>
<data name="ColorfulStyle" xml:space="preserve"> <data name="ColorfulStyle" xml:space="preserve">
<value>Colorido</value> <value>Colorful</value>
</data> </data>
<data name="ContentPlaceholder" xml:space="preserve"> <data name="ContentPlaceholder" xml:space="preserve">
<value>Digite o conteúdo do seu QR code aqui...</value> <value>Enter your QR code content here...</value>
</data> </data>
<data name="AdvancedCustomization" xml:space="preserve"> <data name="AdvancedCustomization" xml:space="preserve">
<value>Personalização Avançada</value> <value>Advanced Customization</value>
</data> </data>
<data name="PrimaryColor" xml:space="preserve"> <data name="PrimaryColor" xml:space="preserve">
<value>Cor Principal</value> <value>Primary Color</value>
</data> </data>
<data name="BackgroundColor" xml:space="preserve"> <data name="BackgroundColor" xml:space="preserve">
<value>Cor de Fundo</value> <value>Background Color</value>
</data> </data>
<data name="Size" xml:space="preserve"> <data name="Size" xml:space="preserve">
<value>Tamanho</value> <value>Size</value>
</data> </data>
<data name="Margin" xml:space="preserve"> <data name="Margin" xml:space="preserve">
<value>Margem</value> <value>Margin</value>
</data> </data>
<data name="Logo" xml:space="preserve"> <data name="Logo" xml:space="preserve">
<value>Logo/Ícone</value> <value>Logo/Icon</value>
</data> </data>
<data name="CornerStyle" xml:space="preserve"> <data name="CornerStyle" xml:space="preserve">
<value>Estilo das Bordas</value> <value>Corner Style</value>
</data> </data>
<data name="GenerateRapidly" xml:space="preserve"> <data name="GenerateRapidly" xml:space="preserve">
<value>Gerar QR Code Rapidamente</value> <value>Generate QR Code Rapidly</value>
</data> </data>
<data name="Preview" xml:space="preserve"> <data name="Preview" xml:space="preserve">
<value>Preview</value> <value>Preview</value>
</data> </data>
<data name="PreviewPlaceholder" xml:space="preserve"> <data name="PreviewPlaceholder" xml:space="preserve">
<value>Seu QR code aparecerá aqui em segundos</value> <value>Your QR code will appear here in seconds</value>
</data> </data>
<data name="UltraFastGeneration" xml:space="preserve"> <data name="UltraFastGeneration" xml:space="preserve">
<value>Geração ultra-rápida garantida</value> <value>Ultra-fast generation guaranteed</value>
</data> </data>
<data name="DownloadPNG" xml:space="preserve"> <data name="DownloadPNG" xml:space="preserve">
<value>Download PNG</value> <value>Download PNG</value>
</data> </data>
<data name="DownloadSVG" xml:space="preserve"> <data name="DownloadSVG" xml:space="preserve">
<value>Download SVG (Vetorial)</value> <value>Download SVG (Vector)</value>
</data> </data>
<data name="DownloadPDF" xml:space="preserve"> <data name="DownloadPDF" xml:space="preserve">
<value>Download PDF</value> <value>Download PDF</value>
</data> </data>
<data name="SaveToHistory" xml:space="preserve"> <data name="SaveToHistory" xml:space="preserve">
<value>Salvar no Histórico</value> <value>Save to History</value>
</data> </data>
<data name="LoginToSave" xml:space="preserve"> <data name="LoginToSave" xml:space="preserve">
<value>Faça login para salvar no histórico</value> <value>Login to save to history</value>
</data> </data>
<data name="PremiumTitle" xml:space="preserve"> <data name="PremiumTitle" xml:space="preserve">
<value>QR Rapido Premium</value> <value>QR Rapido Premium</value>
</data> </data>
<data name="SpeedTipsTitle" xml:space="preserve"> <data name="SpeedTipsTitle" xml:space="preserve">
<value>Dicas para QR Mais Rápidos</value> <value>Tips for Faster QR</value>
</data> </data>
<data name="SpeedTip1" xml:space="preserve"> <data name="SpeedTip1" xml:space="preserve">
<value>URLs curtas geram mais rápido</value> <value>Short URLs generate faster</value>
</data> </data>
<data name="SpeedTip2" xml:space="preserve"> <data name="SpeedTip2" xml:space="preserve">
<value>Menos texto = maior velocidade</value> <value>Less text = higher speed</value>
</data> </data>
<data name="SpeedTip3" xml:space="preserve"> <data name="SpeedTip3" xml:space="preserve">
<value>Cores sólidas otimizam o processo</value> <value>Solid colors optimize the process</value>
</data> </data>
<data name="SpeedTip4" xml:space="preserve"> <data name="SpeedTip4" xml:space="preserve">
<value>Tamanhos menores aceleram o download</value> <value>Smaller sizes speed up downloads</value>
</data> </data>
<data name="Login" xml:space="preserve"> <data name="Login" xml:space="preserve">
<value>Login</value> <value>Login</value>
</data> </data>
<data name="LoginWith" xml:space="preserve"> <data name="LoginWith" xml:space="preserve">
<value>Entrar com</value> <value>Login with</value>
</data> </data>
<data name="Google" xml:space="preserve"> <data name="Google" xml:space="preserve">
<value>Google</value> <value>Google</value>
@ -185,39 +185,39 @@
<value>Microsoft</value> <value>Microsoft</value>
</data> </data>
<data name="AdFreeOffer" xml:space="preserve"> <data name="AdFreeOffer" xml:space="preserve">
<value>Login = 30 dias sem anúncios!</value> <value>Unlock all premium features!</value>
</data> </data>
<data name="SpecialOffer" xml:space="preserve"> <data name="SpecialOffer" xml:space="preserve">
<value>Oferta Especial!</value> <value>Premium Offer!</value>
</data> </data>
<data name="LoginBenefits" xml:space="preserve"> <data name="LoginBenefits" xml:space="preserve">
<value>Ao fazer login, você ganha automaticamente 30 dias sem anúncios e pode gerar até 50 QR codes por dia gratuitamente.</value> <value>Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
</data> </data>
<data name="Privacy" xml:space="preserve"> <data name="Privacy" xml:space="preserve">
<value>Política de Privacidade</value> <value>Privacy Policy</value>
</data> </data>
<data name="BackToGenerator" xml:space="preserve"> <data name="BackToGenerator" xml:space="preserve">
<value>Voltar ao gerador</value> <value>Back to generator</value>
</data> </data>
<data name="GeneratedIn" xml:space="preserve"> <data name="GeneratedIn" xml:space="preserve">
<value>Gerado em</value> <value>Generated in</value>
</data> </data>
<data name="Seconds" xml:space="preserve"> <data name="Seconds" xml:space="preserve">
<value>s</value> <value>s</value>
</data> </data>
<data name="UltraFast" xml:space="preserve"> <data name="UltraFast" xml:space="preserve">
<value>Geração ultra rápida!</value> <value>Ultra fast generation!</value>
</data> </data>
<data name="Fast" xml:space="preserve"> <data name="Fast" xml:space="preserve">
<value>Geração rápida!</value> <value>Fast generation!</value>
</data> </data>
<data name="Normal" xml:space="preserve"> <data name="Normal" xml:space="preserve">
<value>Geração normal</value> <value>Normal generation</value>
</data> </data>
<data name="Error" xml:space="preserve"> <data name="Error" xml:space="preserve">
<value>Erro na geração. Tente novamente.</value> <value>Generation error. Try again.</value>
</data> </data>
<data name="Success" xml:space="preserve"> <data name="Success" xml:space="preserve">
<value>QR Code salvo no histórico!</value> <value>QR Code saved to history!</value>
</data> </data>
</root> </root>

View File

@ -59,13 +59,13 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Tagline" xml:space="preserve"> <data name="Tagline" xml:space="preserve">
<value>¡Genera códigos QR en segundos!</value> <value>Genera codigos QR en segundos!</value>
</data> </data>
<data name="GenerateQR" xml:space="preserve"> <data name="GenerateQR" xml:space="preserve">
<value>Generar Código QR</value> <value>Generar Codigo QR</value>
</data> </data>
<data name="QRType" xml:space="preserve"> <data name="QRType" xml:space="preserve">
<value>Tipo de Código QR</value> <value>Tipo de Codigo QR</value>
</data> </data>
<data name="Content" xml:space="preserve"> <data name="Content" xml:space="preserve">
<value>Contenido</value> <value>Contenido</value>
@ -89,13 +89,13 @@
<value>Email</value> <value>Email</value>
</data> </data>
<data name="DynamicType" xml:space="preserve"> <data name="DynamicType" xml:space="preserve">
<value>QR Dinámico (Premium)</value> <value>QR Dinamico (Premium)</value>
</data> </data>
<data name="QuickStyle" xml:space="preserve"> <data name="QuickStyle" xml:space="preserve">
<value>Estilo Rápido</value> <value>Estilo Rapido</value>
</data> </data>
<data name="ClassicStyle" xml:space="preserve"> <data name="ClassicStyle" xml:space="preserve">
<value>Clásico</value> <value>Clasico</value>
</data> </data>
<data name="ModernStyle" xml:space="preserve"> <data name="ModernStyle" xml:space="preserve">
<value>Moderno</value> <value>Moderno</value>
@ -104,10 +104,10 @@
<value>Colorido</value> <value>Colorido</value>
</data> </data>
<data name="ContentPlaceholder" xml:space="preserve"> <data name="ContentPlaceholder" xml:space="preserve">
<value>Escribe el contenido de tu código QR aquí...</value> <value>Ingrese el contenido de su codigo QR aqui...</value>
</data> </data>
<data name="AdvancedCustomization" xml:space="preserve"> <data name="AdvancedCustomization" xml:space="preserve">
<value>Personalización Avanzada</value> <value>Personalizacion Avanzada</value>
</data> </data>
<data name="PrimaryColor" xml:space="preserve"> <data name="PrimaryColor" xml:space="preserve">
<value>Color Principal</value> <value>Color Principal</value>
@ -116,7 +116,7 @@
<value>Color de Fondo</value> <value>Color de Fondo</value>
</data> </data>
<data name="Size" xml:space="preserve"> <data name="Size" xml:space="preserve">
<value>Tamaño</value> <value>Tamano</value>
</data> </data>
<data name="Margin" xml:space="preserve"> <data name="Margin" xml:space="preserve">
<value>Margen</value> <value>Margen</value>
@ -125,19 +125,19 @@
<value>Logo/Icono</value> <value>Logo/Icono</value>
</data> </data>
<data name="CornerStyle" xml:space="preserve"> <data name="CornerStyle" xml:space="preserve">
<value>Estilo de Bordes</value> <value>Estilo de Esquinas</value>
</data> </data>
<data name="GenerateRapidly" xml:space="preserve"> <data name="GenerateRapidly" xml:space="preserve">
<value>Generar Código QR Rápidamente</value> <value>Generar Codigo QR Rapidamente</value>
</data> </data>
<data name="Preview" xml:space="preserve"> <data name="Preview" xml:space="preserve">
<value>Vista Previa</value> <value>Vista Previa</value>
</data> </data>
<data name="PreviewPlaceholder" xml:space="preserve"> <data name="PreviewPlaceholder" xml:space="preserve">
<value>Tu código QR aparecerá aquí en segundos</value> <value>Su codigo QR aparecera aqui en segundos</value>
</data> </data>
<data name="UltraFastGeneration" xml:space="preserve"> <data name="UltraFastGeneration" xml:space="preserve">
<value>Generación ultra-rápida garantizada</value> <value>Generacion ultra-rapida garantizada</value>
</data> </data>
<data name="DownloadPNG" xml:space="preserve"> <data name="DownloadPNG" xml:space="preserve">
<value>Descargar PNG</value> <value>Descargar PNG</value>
@ -152,31 +152,31 @@
<value>Guardar en Historial</value> <value>Guardar en Historial</value>
</data> </data>
<data name="LoginToSave" xml:space="preserve"> <data name="LoginToSave" xml:space="preserve">
<value>Inicia sesión para guardar en el historial</value> <value>Inicie sesion para guardar en el historial</value>
</data> </data>
<data name="PremiumTitle" xml:space="preserve"> <data name="PremiumTitle" xml:space="preserve">
<value>QR Rapido Premium</value> <value>QR Rapido Premium</value>
</data> </data>
<data name="SpeedTipsTitle" xml:space="preserve"> <data name="SpeedTipsTitle" xml:space="preserve">
<value>Consejos para QR Más Rápidos</value> <value>Consejos para QR Mas Rapidos</value>
</data> </data>
<data name="SpeedTip1" xml:space="preserve"> <data name="SpeedTip1" xml:space="preserve">
<value>URLs cortas se generan más rápido</value> <value>URLs cortas se generan mas rapido</value>
</data> </data>
<data name="SpeedTip2" xml:space="preserve"> <data name="SpeedTip2" xml:space="preserve">
<value>Menos texto = mayor velocidad</value> <value>Menos texto = mayor velocidad</value>
</data> </data>
<data name="SpeedTip3" xml:space="preserve"> <data name="SpeedTip3" xml:space="preserve">
<value>Colores sólidos optimizan el proceso</value> <value>Colores solidos optimizan el proceso</value>
</data> </data>
<data name="SpeedTip4" xml:space="preserve"> <data name="SpeedTip4" xml:space="preserve">
<value>Tamaños menores aceleran la descarga</value> <value>Tamanos menores aceleran la descarga</value>
</data> </data>
<data name="Login" xml:space="preserve"> <data name="Login" xml:space="preserve">
<value>Iniciar Sesión</value> <value>Iniciar Sesion</value>
</data> </data>
<data name="LoginWith" xml:space="preserve"> <data name="LoginWith" xml:space="preserve">
<value>Iniciar sesión con</value> <value>Iniciar con</value>
</data> </data>
<data name="Google" xml:space="preserve"> <data name="Google" xml:space="preserve">
<value>Google</value> <value>Google</value>
@ -185,16 +185,16 @@
<value>Microsoft</value> <value>Microsoft</value>
</data> </data>
<data name="AdFreeOffer" xml:space="preserve"> <data name="AdFreeOffer" xml:space="preserve">
<value>¡Login = 30 días sin anuncios!</value> <value>Desbloquea todas las funciones premium!</value>
</data> </data>
<data name="SpecialOffer" xml:space="preserve"> <data name="SpecialOffer" xml:space="preserve">
<value>¡Oferta Especial!</value> <value>Oferta Premium!</value>
</data> </data>
<data name="LoginBenefits" xml:space="preserve"> <data name="LoginBenefits" xml:space="preserve">
<value>Al iniciar sesión, ganas automáticamente 30 días sin anuncios y puedes generar hasta 50 códigos QR por día gratis.</value> <value>Elimina anuncios, accede a analisis detallados y mucho mas por solo $12.90/mes o $129.00/ano. Inicia sesion y suscribete ahora!</value>
</data> </data>
<data name="Privacy" xml:space="preserve"> <data name="Privacy" xml:space="preserve">
<value>Política de Privacidad</value> <value>Politica de Privacidad</value>
</data> </data>
<data name="BackToGenerator" xml:space="preserve"> <data name="BackToGenerator" xml:space="preserve">
<value>Volver al generador</value> <value>Volver al generador</value>
@ -206,18 +206,66 @@
<value>s</value> <value>s</value>
</data> </data>
<data name="UltraFast" xml:space="preserve"> <data name="UltraFast" xml:space="preserve">
<value>¡Generación ultra rápida!</value> <value>Generacion ultra rapida!</value>
</data> </data>
<data name="Fast" xml:space="preserve"> <data name="Fast" xml:space="preserve">
<value>¡Generación rápida!</value> <value>Generacion rapida!</value>
</data> </data>
<data name="Normal" xml:space="preserve"> <data name="Normal" xml:space="preserve">
<value>Generación normal</value> <value>Generacion normal</value>
</data> </data>
<data name="Error" xml:space="preserve"> <data name="Error" xml:space="preserve">
<value>Error en la generación. Inténtalo de nuevo.</value> <value>Error en la generacion. Intentelo de nuevo.</value>
</data> </data>
<data name="Success" xml:space="preserve"> <data name="Success" xml:space="preserve">
<value>¡Código QR guardado en el historial!</value> <value>Codigo QR guardado en el historial!</value>
</data>
<data name="CreateQRCodeQuickly" xml:space="preserve">
<value>Crear Codigo QR Rapidamente</value>
</data>
<data name="PremiumUserActive" xml:space="preserve">
<value>Usuario Premium Activo</value>
</data>
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
<value>Sin Anuncios • Historial • QR Ilimitado</value>
</data>
<data name="UnlimitedToday" xml:space="preserve">
<value>Ilimitado hoy</value>
</data>
<data name="QRCodesRemaining" xml:space="preserve">
<value>Codigos QR restantes</value>
</data>
<data name="QRCodeType" xml:space="preserve">
<value>Tipo de Codigo QR</value>
</data>
<data name="SelectType" xml:space="preserve">
<value>Seleccionar tipo</value>
</data>
<data name="URLLink" xml:space="preserve">
<value>URL/Enlace</value>
</data>
<data name="SimpleText" xml:space="preserve">
<value>Texto Simple</value>
</data>
<data name="VCard" xml:space="preserve">
<value>Tarjeta de Visita</value>
</data>
<data name="DynamicQRPremium" xml:space="preserve">
<value>QR Dinamico (Premium)</value>
</data>
<data name="EnterQRCodeContent" xml:space="preserve">
<value>Ingrese el contenido de su codigo QR aqui...</value>
</data>
<data name="ContentHints" xml:space="preserve">
<value>Sugerencias de contenido</value>
</data>
<data name="Classic" xml:space="preserve">
<value>Clasico</value>
</data>
<data name="Modern" xml:space="preserve">
<value>Moderno</value>
</data>
<data name="Colorful" xml:space="preserve">
<value>Colorido</value>
</data> </data>
</root> </root>

View File

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<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" 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="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
</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>
<data name="Tagline" xml:space="preserve">
<value>Gere QR codes em segundos!</value>
</data>
<data name="GenerateQR" xml:space="preserve">
<value>Gerar QR Code</value>
</data>
<data name="QRType" xml:space="preserve">
<value>Tipo de QR Code</value>
</data>
<data name="Content" xml:space="preserve">
<value>Conteudo</value>
</data>
<data name="URLType" xml:space="preserve">
<value>URL/Link</value>
</data>
<data name="TextType" xml:space="preserve">
<value>Texto Simples</value>
</data>
<data name="WiFiType" xml:space="preserve">
<value>WiFi</value>
</data>
<data name="VCardType" xml:space="preserve">
<value>Cartao de Visita</value>
</data>
<data name="SMSType" xml:space="preserve">
<value>SMS</value>
</data>
<data name="EmailType" xml:space="preserve">
<value>Email</value>
</data>
<data name="DynamicType" xml:space="preserve">
<value>QR Dinamico (Premium)</value>
</data>
<data name="QuickStyle" xml:space="preserve">
<value>Estilo Rapido</value>
</data>
<data name="ClassicStyle" xml:space="preserve">
<value>Classico</value>
</data>
<data name="ModernStyle" xml:space="preserve">
<value>Moderno</value>
</data>
<data name="ColorfulStyle" xml:space="preserve">
<value>Colorido</value>
</data>
<data name="ContentPlaceholder" xml:space="preserve">
<value>Digite o conteudo do seu QR code aqui...</value>
</data>
<data name="AdvancedCustomization" xml:space="preserve">
<value>Personalizacao Avancada</value>
</data>
<data name="PrimaryColor" xml:space="preserve">
<value>Cor Principal</value>
</data>
<data name="BackgroundColor" xml:space="preserve">
<value>Cor de Fundo</value>
</data>
<data name="Size" xml:space="preserve">
<value>Tamanho</value>
</data>
<data name="Margin" xml:space="preserve">
<value>Margem</value>
</data>
<data name="Logo" xml:space="preserve">
<value>Logo/Icone</value>
</data>
<data name="CornerStyle" xml:space="preserve">
<value>Estilo das Bordas</value>
</data>
<data name="GenerateRapidly" xml:space="preserve">
<value>Gerar QR Code Rapidamente</value>
</data>
<data name="Preview" xml:space="preserve">
<value>Preview</value>
</data>
<data name="PreviewPlaceholder" xml:space="preserve">
<value>Seu QR code aparecera aqui em segundos</value>
</data>
<data name="UltraFastGeneration" xml:space="preserve">
<value>Geracao ultra-rapida garantida</value>
</data>
<data name="DownloadPNG" xml:space="preserve">
<value>Download PNG</value>
</data>
<data name="DownloadSVG" xml:space="preserve">
<value>Download SVG (Vetorial)</value>
</data>
<data name="DownloadPDF" xml:space="preserve">
<value>Download PDF</value>
</data>
<data name="SaveToHistory" xml:space="preserve">
<value>Salvar no Historico</value>
</data>
<data name="LoginToSave" xml:space="preserve">
<value>Faca login para salvar no historico</value>
</data>
<data name="PremiumTitle" xml:space="preserve">
<value>QR Rapido Premium</value>
</data>
<data name="SpeedTipsTitle" xml:space="preserve">
<value>Dicas para QR Mais Rapidos</value>
</data>
<data name="SpeedTip1" xml:space="preserve">
<value>URLs curtas geram mais rapido</value>
</data>
<data name="SpeedTip2" xml:space="preserve">
<value>Menos texto = maior velocidade</value>
</data>
<data name="SpeedTip3" xml:space="preserve">
<value>Cores solidas otimizam o processo</value>
</data>
<data name="SpeedTip4" xml:space="preserve">
<value>Tamanhos menores aceleram o download</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginWith" xml:space="preserve">
<value>Entrar com</value>
</data>
<data name="Google" xml:space="preserve">
<value>Google</value>
</data>
<data name="Microsoft" xml:space="preserve">
<value>Microsoft</value>
</data>
<data name="AdFreeOffer" xml:space="preserve">
<value>Desbloqueie todos os recursos premium!</value>
</data>
<data name="SpecialOffer" xml:space="preserve">
<value>Oferta Premium!</value>
</data>
<data name="LoginBenefits" xml:space="preserve">
<value>Remova anuncios, acesse analytics detalhados e muito mais por apenas R$ 12,90/mes ou R$ 129,00/ano. Faca login e assine agora!</value>
</data>
<data name="Privacy" xml:space="preserve">
<value>Politica de Privacidade</value>
</data>
<data name="BackToGenerator" xml:space="preserve">
<value>Voltar ao gerador</value>
</data>
<data name="GeneratedIn" xml:space="preserve">
<value>Gerado em</value>
</data>
<data name="Seconds" xml:space="preserve">
<value>s</value>
</data>
<data name="UltraFast" xml:space="preserve">
<value>Geracao ultra rapida!</value>
</data>
<data name="Fast" xml:space="preserve">
<value>Geracao rapida!</value>
</data>
<data name="Normal" xml:space="preserve">
<value>Geracao normal</value>
</data>
<data name="Error" xml:space="preserve">
<value>Erro na geracao. Tente novamente.</value>
</data>
<data name="Success" xml:space="preserve">
<value>QR Code salvo no historico!</value>
</data>
<data name="CreateQRCodeQuickly" xml:space="preserve">
<value>Criar QR Code Rapidamente</value>
</data>
<data name="PremiumUserActive" xml:space="preserve">
<value>Usuario Premium Ativo</value>
</data>
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
<value>Sem Anuncios • Historico • QR Ilimitado</value>
</data>
<data name="UnlimitedToday" xml:space="preserve">
<value>Ilimitado hoje</value>
</data>
<data name="QRCodesRemaining" xml:space="preserve">
<value>QR codes restantes</value>
</data>
<data name="QRCodeType" xml:space="preserve">
<value>Tipo de QR Code</value>
</data>
<data name="SelectType" xml:space="preserve">
<value>Selecionar tipo</value>
</data>
<data name="URLLink" xml:space="preserve">
<value>URL/Link</value>
</data>
<data name="SimpleText" xml:space="preserve">
<value>Texto Simples</value>
</data>
<data name="VCard" xml:space="preserve">
<value>Cartao de Visita</value>
</data>
<data name="DynamicQRPremium" xml:space="preserve">
<value>QR Dinamico (Premium)</value>
</data>
<data name="EnterQRCodeContent" xml:space="preserve">
<value>Digite o conteudo do seu QR code aqui...</value>
</data>
<data name="ContentHints" xml:space="preserve">
<value>Dicas de conteudo</value>
</data>
<data name="Classic" xml:space="preserve">
<value>Classico</value>
</data>
<data name="Modern" xml:space="preserve">
<value>Moderno</value>
</data>
<data name="Colorful" xml:space="preserve">
<value>Colorido</value>
</data>
</root>

View File

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<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" 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="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
</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>
<data name="Tagline" xml:space="preserve">
<value>Generate QR codes in seconds!</value>
</data>
<data name="GenerateQR" xml:space="preserve">
<value>Generate QR Code</value>
</data>
<data name="QRType" xml:space="preserve">
<value>QR Code Type</value>
</data>
<data name="Content" xml:space="preserve">
<value>Content</value>
</data>
<data name="URLType" xml:space="preserve">
<value>URL/Link</value>
</data>
<data name="TextType" xml:space="preserve">
<value>Plain Text</value>
</data>
<data name="WiFiType" xml:space="preserve">
<value>WiFi</value>
</data>
<data name="VCardType" xml:space="preserve">
<value>Business Card</value>
</data>
<data name="SMSType" xml:space="preserve">
<value>SMS</value>
</data>
<data name="EmailType" xml:space="preserve">
<value>Email</value>
</data>
<data name="DynamicType" xml:space="preserve">
<value>Dynamic QR (Premium)</value>
</data>
<data name="QuickStyle" xml:space="preserve">
<value>Quick Style</value>
</data>
<data name="ClassicStyle" xml:space="preserve">
<value>Classic</value>
</data>
<data name="ModernStyle" xml:space="preserve">
<value>Modern</value>
</data>
<data name="ColorfulStyle" xml:space="preserve">
<value>Colorful</value>
</data>
<data name="ContentPlaceholder" xml:space="preserve">
<value>Enter your QR code content here...</value>
</data>
<data name="AdvancedCustomization" xml:space="preserve">
<value>Advanced Customization</value>
</data>
<data name="PrimaryColor" xml:space="preserve">
<value>Primary Color</value>
</data>
<data name="BackgroundColor" xml:space="preserve">
<value>Background Color</value>
</data>
<data name="Size" xml:space="preserve">
<value>Size</value>
</data>
<data name="Margin" xml:space="preserve">
<value>Margin</value>
</data>
<data name="Logo" xml:space="preserve">
<value>Logo/Icon</value>
</data>
<data name="CornerStyle" xml:space="preserve">
<value>Corner Style</value>
</data>
<data name="GenerateRapidly" xml:space="preserve">
<value>Generate QR Code Rapidly</value>
</data>
<data name="Preview" xml:space="preserve">
<value>Preview</value>
</data>
<data name="PreviewPlaceholder" xml:space="preserve">
<value>Your QR code will appear here in seconds</value>
</data>
<data name="UltraFastGeneration" xml:space="preserve">
<value>Ultra-fast generation guaranteed</value>
</data>
<data name="DownloadPNG" xml:space="preserve">
<value>Download PNG</value>
</data>
<data name="DownloadSVG" xml:space="preserve">
<value>Download SVG (Vector)</value>
</data>
<data name="DownloadPDF" xml:space="preserve">
<value>Download PDF</value>
</data>
<data name="SaveToHistory" xml:space="preserve">
<value>Save to History</value>
</data>
<data name="LoginToSave" xml:space="preserve">
<value>Login to save to history</value>
</data>
<data name="PremiumTitle" xml:space="preserve">
<value>QR Rapido Premium</value>
</data>
<data name="SpeedTipsTitle" xml:space="preserve">
<value>Tips for Faster QR</value>
</data>
<data name="SpeedTip1" xml:space="preserve">
<value>Short URLs generate faster</value>
</data>
<data name="SpeedTip2" xml:space="preserve">
<value>Less text = higher speed</value>
</data>
<data name="SpeedTip3" xml:space="preserve">
<value>Solid colors optimize the process</value>
</data>
<data name="SpeedTip4" xml:space="preserve">
<value>Smaller sizes speed up downloads</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginWith" xml:space="preserve">
<value>Login with</value>
</data>
<data name="Google" xml:space="preserve">
<value>Google</value>
</data>
<data name="Microsoft" xml:space="preserve">
<value>Microsoft</value>
</data>
<data name="AdFreeOffer" xml:space="preserve">
<value>Unlock all premium features!</value>
</data>
<data name="SpecialOffer" xml:space="preserve">
<value>Premium Offer!</value>
</data>
<data name="LoginBenefits" xml:space="preserve">
<value>Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
</data>
<data name="Privacy" xml:space="preserve">
<value>Privacy Policy</value>
</data>
<data name="BackToGenerator" xml:space="preserve">
<value>Back to generator</value>
</data>
<data name="GeneratedIn" xml:space="preserve">
<value>Generated in</value>
</data>
<data name="Seconds" xml:space="preserve">
<value>s</value>
</data>
<data name="UltraFast" xml:space="preserve">
<value>Ultra fast generation!</value>
</data>
<data name="Fast" xml:space="preserve">
<value>Fast generation!</value>
</data>
<data name="Normal" xml:space="preserve">
<value>Normal generation</value>
</data>
<data name="Error" xml:space="preserve">
<value>Generation error. Try again.</value>
</data>
<data name="Success" xml:space="preserve">
<value>QR Code saved to history!</value>
</data>
<data name="CreateQRCodeQuickly" xml:space="preserve">
<value>Create QR Code Quickly</value>
</data>
<data name="PremiumUserActive" xml:space="preserve">
<value>Premium User Active</value>
</data>
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
<value>No Ads • History • Unlimited QR</value>
</data>
<data name="UnlimitedToday" xml:space="preserve">
<value>Unlimited today</value>
</data>
<data name="QRCodesRemaining" xml:space="preserve">
<value>QR codes remaining</value>
</data>
<data name="QRCodeType" xml:space="preserve">
<value>QR Code Type</value>
</data>
<data name="SelectType" xml:space="preserve">
<value>Select type</value>
</data>
<data name="URLLink" xml:space="preserve">
<value>URL/Link</value>
</data>
<data name="SimpleText" xml:space="preserve">
<value>Simple Text</value>
</data>
<data name="VCard" xml:space="preserve">
<value>Business Card</value>
</data>
<data name="DynamicQRPremium" xml:space="preserve">
<value>Dynamic QR (Premium)</value>
</data>
<data name="EnterQRCodeContent" xml:space="preserve">
<value>Enter your QR code content here...</value>
</data>
<data name="ContentHints" xml:space="preserve">
<value>Content hints</value>
</data>
<data name="Classic" xml:space="preserve">
<value>Classic</value>
</data>
<data name="Modern" xml:space="preserve">
<value>Modern</value>
</data>
<data name="Colorful" xml:space="preserve">
<value>Colorful</value>
</data>
</root>

14
Services/IPlanService.cs Normal file
View File

@ -0,0 +1,14 @@
using QRRapidoApp.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace QRRapidoApp.Services
{
public interface IPlanService
{
Task<List<Plan>> GetActivePlansAsync();
Task<List<Plan>> GetPlansByLanguageAsync(string languageCode, string countryCode = "BRL");
Task<Plan?> GetPlanByIdAsync(string id);
}
}

View File

@ -10,6 +10,10 @@ namespace QRRapidoApp.Services
Task<User?> GetUserByProviderAsync(string provider, string providerId); Task<User?> GetUserByProviderAsync(string provider, string providerId);
Task<User> CreateUserAsync(string email, string name, string provider, string providerId); Task<User> CreateUserAsync(string email, string name, string provider, string providerId);
Task UpdateLastLoginAsync(string userId); Task UpdateLastLoginAsync(string userId);
Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate);
Task DeactivatePremiumStatus(string stripeSubscriptionId);
Task UpdateUserStripeCustomerIdAsync(string userId, string stripeCustomerId);
Task<User?> GetUserByStripeCustomerIdAsync(string customerId);
Task<bool> UpdateUserAsync(User user); Task<bool> UpdateUserAsync(User user);
Task<int> GetDailyQRCountAsync(string? userId); Task<int> GetDailyQRCountAsync(string? userId);
Task<int> DecrementDailyQRCountAsync(string userId); Task<int> DecrementDailyQRCountAsync(string userId);

35
Services/PlanService.cs Normal file
View File

@ -0,0 +1,35 @@
using MongoDB.Driver;
using QRRapidoApp.Data;
using QRRapidoApp.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace QRRapidoApp.Services
{
public class PlanService : IPlanService
{
private readonly IMongoCollection<Plan> _plans;
public PlanService(MongoDbContext context)
{
_plans = context.Plans;
}
public async Task<List<Plan>> GetActivePlansAsync()
{
return await _plans.Find(p => p.IsActive).SortBy(p => p.PricesByCountry["BRL"].Amount).ToListAsync();
}
public async Task<List<Plan>> GetPlansByLanguageAsync(string languageCode, string countryCode = "BRL")
{
var plans = await _plans.Find(p => p.IsActive).SortBy(p => p.PricesByCountry[countryCode].Amount).ToListAsync();
return plans;
}
public async Task<Plan?> GetPlanByIdAsync(string id)
{
return await _plans.Find(p => p.Id == id).FirstOrDefaultAsync();
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Localization;
namespace QRRapidoApp.Services
{
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext?.Request?.RouteValues == null)
return Task.FromResult<ProviderCultureResult?>(null);
var routeValues = httpContext.Request.RouteValues;
if (!routeValues.ContainsKey("culture"))
return Task.FromResult<ProviderCultureResult?>(null);
var culture = routeValues["culture"]?.ToString();
if (string.IsNullOrEmpty(culture))
return Task.FromResult<ProviderCultureResult?>(null);
var supportedCultures = new[] { "pt-BR", "es", "en" };
if (!supportedCultures.Contains(culture))
return Task.FromResult<ProviderCultureResult?>(null);
return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(culture));
}
}
}

View File

@ -1,6 +1,12 @@
using Stripe; using Stripe;
using Stripe.Checkout; using Stripe.Checkout;
using QRRapidoApp.Models; using QRRapidoApp.Models;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System;
namespace QRRapidoApp.Services namespace QRRapidoApp.Services
{ {
@ -15,253 +21,128 @@ namespace QRRapidoApp.Services
_config = config; _config = config;
_userService = userService; _userService = userService;
_logger = logger; _logger = logger;
StripeConfiguration.ApiKey = _config["Stripe:SecretKey"];
} }
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId) public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
{ {
try var user = await _userService.GetUserAsync(userId);
if (user == null)
{ {
var options = new SessionCreateOptions throw new Exception("User not found");
}
var customerId = user.StripeCustomerId;
if (string.IsNullOrEmpty(customerId))
{
var customerOptions = new CustomerCreateOptions
{ {
PaymentMethodTypes = new List<string> { "card" }, Email = user.Email,
Mode = "subscription", Name = user.Name,
LineItems = new List<SessionLineItemOptions> Metadata = new Dictionary<string, string> { { "app_user_id", user.Id } }
{
new SessionLineItemOptions
{
Price = priceId,
Quantity = 1
}
},
ClientReferenceId = userId,
SuccessUrl = $"{_config["App:BaseUrl"]}/Premium/Success?session_id={{CHECKOUT_SESSION_ID}}",
CancelUrl = $"{_config["App:BaseUrl"]}/Premium/Cancel",
CustomerEmail = await _userService.GetUserEmailAsync(userId),
AllowPromotionCodes = true,
BillingAddressCollection = "auto",
Metadata = new Dictionary<string, string>
{
{ "userId", userId },
{ "product", "QR Rapido Premium" }
}
}; };
var customerService = new CustomerService();
var service = new SessionService(); var customer = await customerService.CreateAsync(customerOptions);
var session = await service.CreateAsync(options); customerId = customer.Id;
await _userService.UpdateUserStripeCustomerIdAsync(userId, customerId);
_logger.LogInformation($"Created Stripe checkout session for user {userId}: {session.Id}");
return session.Url;
} }
catch (Exception ex)
var options = new SessionCreateOptions
{ {
_logger.LogError(ex, $"Error creating Stripe checkout session for user {userId}: {ex.Message}"); PaymentMethodTypes = new List<string> { "card" },
throw; Mode = "subscription",
} LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions { Price = priceId, Quantity = 1 }
},
Customer = customerId,
ClientReferenceId = userId,
SuccessUrl = $"{_config["App:BaseUrl"]}/Pagamento/Sucesso",
CancelUrl = $"{_config["App:BaseUrl"]}/Pagamento/Cancelar",
AllowPromotionCodes = true,
Metadata = new Dictionary<string, string> { { "user_id", userId } }
};
var service = new SessionService();
var session = await service.CreateAsync(options);
_logger.LogInformation($"Created Stripe checkout session {session.Id} for user {userId}");
return session.Url;
} }
public async Task HandleWebhookAsync(string json, string signature) public async Task HandleWebhookAsync(string json, string signature)
{ {
var webhookSecret = _config["Stripe:WebhookSecret"]; var webhookSecret = _config["Stripe:WebhookSecret"];
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
try _logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
switch (stripeEvent.Type)
{ {
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret); case Events.CheckoutSessionCompleted:
var session = stripeEvent.Data.Object as Session;
if (session?.SubscriptionId != null)
{
var subscriptionService = new SubscriptionService();
var subscription = await subscriptionService.GetAsync(session.SubscriptionId);
await ProcessSubscriptionActivation(session.ClientReferenceId, subscription);
}
break;
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}"); case Events.InvoicePaymentSucceeded:
var invoice = stripeEvent.Data.Object as Invoice;
switch (stripeEvent.Type) if (invoice?.SubscriptionId != null)
{ {
case "checkout.session.completed": var subscriptionService = new SubscriptionService();
var session = stripeEvent.Data.Object as Session; var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
if (session != null) var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId);
if (user != null)
{ {
await ActivatePremiumAsync(session.ClientReferenceId, session.CustomerId, session.SubscriptionId); await ProcessSubscriptionActivation(user.Id, subscription);
} }
break; }
break;
case "invoice.payment_succeeded": case Events.CustomerSubscriptionDeleted:
var invoice = stripeEvent.Data.Object as Invoice; var deletedSubscription = stripeEvent.Data.Object as Subscription;
if (invoice != null && invoice.SubscriptionId != null) if (deletedSubscription != null)
{ {
await RenewPremiumSubscriptionAsync(invoice.SubscriptionId); await _userService.DeactivatePremiumStatus(deletedSubscription.Id);
} }
break; break;
case "invoice.payment_failed": default:
var failedInvoice = stripeEvent.Data.Object as Invoice; _logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
if (failedInvoice != null && failedInvoice.SubscriptionId != null) break;
{
await HandleFailedPaymentAsync(failedInvoice.SubscriptionId);
}
break;
case "customer.subscription.deleted":
var deletedSubscription = stripeEvent.Data.Object as Subscription;
if (deletedSubscription != null)
{
await DeactivatePremiumAsync(deletedSubscription);
}
break;
case "customer.subscription.updated":
var updatedSubscription = stripeEvent.Data.Object as Subscription;
if (updatedSubscription != null)
{
await UpdateSubscriptionAsync(updatedSubscription);
}
break;
default:
_logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
break;
}
}
catch (StripeException ex)
{
_logger.LogError(ex, $"Stripe webhook error: {ex.Message}");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error processing Stripe webhook: {ex.Message}");
throw;
} }
} }
private async Task ActivatePremiumAsync(string? userId, string? customerId, string? subscriptionId) private async Task ProcessSubscriptionActivation(string userId, Subscription subscription)
{ {
if (string.IsNullOrEmpty(userId)) return; if (string.IsNullOrEmpty(userId) || subscription == null)
try
{ {
var user = await _userService.GetUserAsync(userId); _logger.LogWarning("Could not process subscription activation due to missing userId or subscription data.");
if (user == null) return;
{
_logger.LogWarning($"User not found for premium activation: {userId}");
return;
}
user.IsPremium = true;
user.StripeCustomerId = customerId;
user.StripeSubscriptionId = subscriptionId;
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32); // Buffer for billing cycles
await _userService.UpdateUserAsync(user);
_logger.LogInformation($"Activated premium for user {userId}");
} }
catch (Exception ex)
var user = await _userService.GetUserAsync(userId);
if (user == null)
{ {
_logger.LogError(ex, $"Error activating premium for user {userId}: {ex.Message}"); _logger.LogWarning($"User not found for premium activation: {userId}");
return;
} }
}
private async Task RenewPremiumSubscriptionAsync(string subscriptionId) if (string.IsNullOrEmpty(user.StripeCustomerId))
{
try
{ {
// Find user by subscription ID await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId);
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
if (user == null) return;
// Extend premium expiry
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32);
await _userService.UpdateUserAsync(user);
_logger.LogInformation($"Renewed premium subscription for user {user.Id}");
} }
catch (Exception ex)
{
_logger.LogError(ex, $"Error renewing premium subscription {subscriptionId}: {ex.Message}");
}
}
private async Task HandleFailedPaymentAsync(string subscriptionId) await _userService.ActivatePremiumStatus(userId, subscription.Id, subscription.CurrentPeriodEnd);
{ _logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}.");
try
{
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
if (user == null) return;
// Don't immediately deactivate - Stripe will retry
_logger.LogWarning($"Payment failed for user {user.Id}, subscription {subscriptionId}");
// Could send notification email here
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error handling failed payment for subscription {subscriptionId}: {ex.Message}");
}
}
private async Task DeactivatePremiumAsync(Subscription subscription)
{
try
{
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
if (user == null) return;
// ADICIONAR: marcar data de cancelamento
await _userService.MarkPremiumCancelledAsync(user.Id, DateTime.UtcNow);
_logger.LogInformation($"Deactivated premium for user {user.Id}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error deactivating premium for subscription {subscription.Id}: {ex.Message}");
}
}
private async Task UpdateSubscriptionAsync(Subscription subscription)
{
try
{
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
if (user == null) return;
// Update based on subscription status
if (subscription.Status == "active")
{
user.IsPremium = true;
user.PremiumExpiresAt = subscription.CurrentPeriodEnd.AddDays(2); // Small buffer
}
else if (subscription.Status == "canceled" || subscription.Status == "unpaid")
{
user.IsPremium = false;
user.PremiumExpiresAt = DateTime.UtcNow;
}
await _userService.UpdateUserAsync(user);
_logger.LogInformation($"Updated subscription for user {user.Id}: {subscription.Status}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error updating subscription {subscription.Id}: {ex.Message}");
}
}
private async Task<User?> FindUserBySubscriptionIdAsync(string subscriptionId)
{
try
{
// This would require implementing a method in UserService to find by subscription ID
// For now, we'll leave this as a placeholder
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error finding user by subscription ID {subscriptionId}: {ex.Message}");
return null;
}
} }
public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId) public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId)
{ {
if (string.IsNullOrEmpty(subscriptionId)) if (string.IsNullOrEmpty(subscriptionId)) return "None";
return "None";
try try
{ {
var service = new SubscriptionService(); var service = new SubscriptionService();
@ -270,7 +151,7 @@ namespace QRRapidoApp.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, $"Error getting subscription status for {subscriptionId}: {ex.Message}"); _logger.LogError(ex, $"Error getting subscription status for {subscriptionId}");
return "Unknown"; return "Unknown";
} }
} }
@ -280,18 +161,13 @@ namespace QRRapidoApp.Services
try try
{ {
var service = new SubscriptionService(); var service = new SubscriptionService();
var subscription = await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions());
{ _logger.LogInformation($"Canceled subscription {subscriptionId} via API.");
InvoiceNow = false,
Prorate = false
});
_logger.LogInformation($"Canceled subscription {subscriptionId}");
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}: {ex.Message}"); _logger.LogError(ex, $"Error canceling subscription {subscriptionId}");
return false; return false;
} }
} }

View File

@ -83,9 +83,6 @@ namespace QRRapidoApp.Services
await _context.Users.InsertOneAsync(user); await _context.Users.InsertOneAsync(user);
_logger.LogInformation($"Created new user: {email} via {provider}"); _logger.LogInformation($"Created new user: {email} via {provider}");
// Create initial ad-free session for new users
await CreateAdFreeSessionAsync(user.Id, "Login");
return user; return user;
} }
@ -97,9 +94,6 @@ namespace QRRapidoApp.Services
.Set(u => u.LastLoginAt, DateTime.UtcNow); .Set(u => u.LastLoginAt, DateTime.UtcNow);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update); await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
// Create new ad-free session if needed
await CreateAdFreeSessionAsync(userId, "Login");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -352,31 +346,37 @@ namespace QRRapidoApp.Services
} }
} }
private async Task CreateAdFreeSessionAsync(string userId, string sessionType, int? customMinutes = null) public async Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate)
{ {
try var update = Builders<User>.Update
{ .Set(u => u.IsPremium, true)
var durationMinutes = customMinutes ?? _config.GetValue<int>("AdFree:LoginMinutes", 43200); .Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
.Set(u => u.PremiumExpiresAt, expiryDate)
.Unset(u => u.PremiumCancelledAt);
var session = new AdFreeSession await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
{ _logger.LogInformation($"Activated premium for user {userId}");
UserId = userId, }
StartedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddMinutes(durationMinutes),
IsActive = true,
SessionType = sessionType,
DurationMinutes = durationMinutes,
CreatedAt = DateTime.UtcNow
};
await _context.AdFreeSessions.InsertOneAsync(session); public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
{
var update = Builders<User>.Update
.Set(u => u.IsPremium, false)
.Set(u => u.PremiumCancelledAt, DateTime.UtcNow);
_logger.LogInformation($"Created {sessionType} ad-free session for user {userId} - {durationMinutes} minutes"); await _context.Users.UpdateOneAsync(u => u.StripeSubscriptionId == stripeSubscriptionId, update);
} _logger.LogInformation($"Deactivated premium for subscription {stripeSubscriptionId}");
catch (Exception ex) }
{
_logger.LogError(ex, $"Error creating ad-free session for user {userId}: {ex.Message}"); public async Task<User?> GetUserByStripeCustomerIdAsync(string customerId)
} {
return await _context.Users.Find(u => u.StripeCustomerId == customerId).FirstOrDefaultAsync();
}
public async Task UpdateUserStripeCustomerIdAsync(string userId, string stripeCustomerId)
{
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
} }
} }
} }

View File

@ -0,0 +1,158 @@
@model List<QRRapidoApp.Models.QRCodeHistory>
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
@{
ViewData["Title"] = "Histórico de QR Codes";
Layout = "~/Views/Shared/_Layout.cshtml";
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
}
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2><i class="fas fa-history text-primary"></i> Histórico de QR Codes</h2>
<p class="text-muted">Seus QR codes gerados ficam salvos aqui para download futuro</p>
</div>
<div>
<a href="/" class="btn btn-primary">
<i class="fas fa-plus"></i> Gerar Novo QRCode
</a>
</div>
</div>
@if (Model != null && Model.Any())
{
<div class="row">
@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-body">
<div class="text-center mb-3">
<img src="data:image/png;base64,@qr.QRCodeBase64"
alt="QR Code"
class="img-fluid border"
style="max-width: 150px; max-height: 150px;">
</div>
<div class="mb-2">
<small class="text-muted">Tipo:</small>
<span class="badge bg-secondary">@qr.Type</span>
</div>
<div class="mb-2">
<small class="text-muted">Conteúdo:</small>
<p class="small mb-0" style="word-break: break-all;">
@if (qr.Content.Length > 50)
{
@(qr.Content.Substring(0, 50) + "...")
}
else
{
@qr.Content
}
</p>
</div>
<div class="mb-3">
<small class="text-muted">Criado em:</small>
<br>
<small>@qr.CreatedAt.ToString("dd/MM/yyyy HH:mm")</small>
</div>
</div>
<div class="card-footer bg-light">
<div class="btn-group w-100" role="group">
<a href="/api/QR/Download/@qr.Id?format=png"
class="btn btn-sm btn-outline-primary"
title="Download PNG">
<i class="fas fa-download"></i> PNG
</a>
<a href="/api/QR/Download/@qr.Id?format=svg"
class="btn btn-sm btn-outline-primary"
title="Download SVG">
<i class="fas fa-download"></i> SVG
</a>
<a href="/api/QR/Download/@qr.Id?format=pdf"
class="btn btn-sm btn-outline-primary"
title="Download PDF">
<i class="fas fa-download"></i> PDF
</a>
<button type="button"
class="btn btn-sm btn-outline-secondary"
onclick="regenerateQR('@qr.Id')"
title="Regenerar">
<i class="fas fa-redo"></i>
</button>
</div>
</div>
</div>
</div>
}
</div>
@if (Model.Count == 50)
{
<div class="alert alert-info text-center">
<i class="fas fa-info-circle"></i>
Mostrando os 50 QR codes mais recentes. Os mais antigos são removidos automaticamente.
</div>
}
}
else
{
<div class="text-center py-5">
<i class="fas fa-qrcode fa-4x text-muted mb-3"></i>
<h4 class="text-muted">Nenhum QR Code encontrado</h4>
<p class="text-muted">
Quando você gerar QR codes estando logado, eles aparecerão aqui para download futuro.
</p>
<a href="/" class="btn btn-primary">
<i class="fas fa-plus"></i> Gerar Primeiro QRCode
</a>
</div>
}
</div>
</div>
</div>
@section Scripts {
<script>
function regenerateQR(qrId) {
// Get QR data from history and regenerate
fetch(`/api/QR/History`)
.then(response => response.json())
.then(data => {
const qrData = data.find(q => q.id === qrId);
if (qrData) {
// Parse customization settings and redirect to home with parameters
const settings = JSON.parse(qrData.customizationSettings || '{}');
const params = new URLSearchParams({
content: qrData.content,
type: qrData.type,
size: settings.size || 300,
primaryColor: settings.primaryColor || '#000000',
backgroundColor: settings.backgroundColor || '#FFFFFF'
});
window.location.href = `/?${params.toString()}`;
}
})
.catch(error => {
console.error('Error regenerating QR:', error);
alert('Erro ao regenerar QR Code. Tente novamente.');
});
}
// 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
if (document.visibilityState === 'visible' && document.querySelector('.card')) {
location.reload();
}
}, 300000); // Refresh every 5 minutes
</script>
}

View File

@ -1,11 +1,15 @@
@using QRRapidoApp.Services @using QRRapidoApp.Services
@using Microsoft.Extensions.Localization
@inject AdDisplayService AdService @inject AdDisplayService AdService
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
@{ @{
ViewData["Title"] = "Home"; ViewData["Title"] = "Home";
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<!-- QR Generator Form --> <!-- QR Generator Form -->
@ -13,7 +17,7 @@
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-primary text-white"> <div class="card-header bg-primary text-white">
<h3 class="h5 mb-0"> <h3 class="h5 mb-0">
<i class="fas fa-qrcode"></i> Criar QR Code Rapidamente <i class="fas fa-qrcode"></i> @Localizer["CreateQRCodeQuickly"]
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
@ -25,8 +29,8 @@
{ {
<div class="alert alert-success border-0"> <div class="alert alert-success border-0">
<i class="fas fa-crown text-warning"></i> <i class="fas fa-crown text-warning"></i>
<strong>Usuário Premium ativo!</strong> <strong>@Localizer["PremiumUserActive"]</strong>
<span class="badge bg-success">Sem anúncios + Histórico + QR ilimitados</span> <span class="badge bg-success">@Localizer["NoAdsHistoryUnlimitedQR"]</span>
</div> </div>
} }
} }
@ -42,7 +46,7 @@
</div> </div>
<div class="speed-badge d-none"> <div class="speed-badge d-none">
<span class="badge bg-success"> <span class="badge bg-success">
<i class="fas fa-bolt"></i> Geração ultra rápida! <i class="fas fa-bolt"></i> @Localizer["UltraFastGeneration"]
</span> </span>
</div> </div>
</div> </div>
@ -51,13 +55,13 @@
@if (User.Identity.IsAuthenticated) @if (User.Identity.IsAuthenticated)
{ {
<small class="text-muted"> <small class="text-muted">
<span class="qr-counter">Ilimitado hoje</span> <span class="qr-counter">@Localizer["UnlimitedToday"]</span>
</small> </small>
} }
else else
{ {
<small class="text-muted"> <small class="text-muted">
<span class="qr-counter">10 QR codes restantes</span> <span class="qr-counter">@Localizer["QRCodesRemaining"]</span>
</small> </small>
} }
</div> </div>
@ -66,50 +70,50 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label fw-semibold"> <label class="form-label fw-semibold">
<i class="fas fa-list"></i> Tipo de QR Code <i class="fas fa-list"></i> @Localizer["QRCodeType"]
</label> </label>
<select id="qr-type" class="form-select" required> <select id="qr-type" class="form-select" required>
<option value="">Selecione o tipo...</option> <option value="">@Localizer["SelectType"]</option>
<option value="url">🌐 URL/Link</option> <option value="url">🌐 @Localizer["URLLink"]</option>
<option value="text">📝 Texto Simples</option> <option value="text">📝 @Localizer["SimpleText"]</option>
<option value="wifi">📶 WiFi</option> <option value="wifi">📶 @Localizer["WiFi"]</option>
<option value="vcard">👤 Cartão de Visita</option> <option value="vcard">👤 @Localizer["VCard"]</option>
<option value="sms">💬 SMS</option> <option value="sms">💬 @Localizer["SMS"]</option>
<option value="email">📧 Email</option> <option value="email">📧 @Localizer["Email"]</option>
@if (User.Identity.IsAuthenticated) @if (User.Identity.IsAuthenticated)
{ {
<option value="dynamic">⚡ QR Dinâmico (Premium)</option> <option value="dynamic">⚡ @Localizer["DynamicQRPremium"]</option>
} }
</select> </select>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label fw-semibold"> <label class="form-label fw-semibold">
<i class="fas fa-palette"></i> Estilo Rápido <i class="fas fa-palette"></i> @Localizer["QuickStyle"]
</label> </label>
<div class="btn-group w-100" role="group"> <div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked> <input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked>
<label class="btn btn-outline-secondary" for="style-classic">Clássico</label> <label class="btn btn-outline-secondary" for="style-classic">@Localizer["Classic"]</label>
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern"> <input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern">
<label class="btn btn-outline-secondary" for="style-modern">Moderno</label> <label class="btn btn-outline-secondary" for="style-modern">@Localizer["Modern"]</label>
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful"> <input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful">
<label class="btn btn-outline-secondary" for="style-colorful">Colorido</label> <label class="btn btn-outline-secondary" for="style-colorful">@Localizer["Colorful"]</label>
</div> </div>
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold"> <label class="form-label fw-semibold">
<i class="fas fa-edit"></i> Conteúdo <i class="fas fa-edit"></i> @Localizer["Content"]
</label> </label>
<textarea id="qr-content" <textarea id="qr-content"
class="form-control form-control-lg" class="form-control form-control-lg"
rows="3" rows="3"
placeholder="Digite o conteúdo do seu QR code aqui..." placeholder="@Localizer["EnterQRCodeContent"]"
required></textarea> required></textarea>
<div class="form-text"> <div class="form-text">
<span id="content-hints">Dicas aparecerão aqui baseadas no tipo selecionado</span> <span id="content-hints">@Localizer["ContentHints"]</span>
</div> </div>
</div> </div>
@ -118,22 +122,22 @@
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header"> <h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
<i class="fas fa-sliders-h me-2"></i> Personalização Avançada <i class="fas fa-sliders-h me-2"></i> @Localizer["AdvancedCustomization"]
</button> </button>
</h2> </h2>
<div id="customization-panel" class="accordion-collapse collapse"> <div id="customization-panel" class="accordion-collapse collapse">
<div class="accordion-body"> <div class="accordion-body">
<div class="row"> <div class="row">
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label class="form-label">Cor Principal</label> <label class="form-label">@Localizer["PrimaryColor"]</label>
<input type="color" id="primary-color" class="form-control form-control-color" value="#007BFF"> <input type="color" id="primary-color" class="form-control form-control-color" value="#007BFF">
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label class="form-label">Cor de Fundo</label> <label class="form-label">@Localizer["BackgroundColor"]</label>
<input type="color" id="bg-color" class="form-control form-control-color" value="#FFFFFF"> <input type="color" id="bg-color" class="form-control form-control-color" value="#FFFFFF">
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label class="form-label">Tamanho</label> <label class="form-label">@Localizer["Size"]</label>
<select id="qr-size" class="form-select"> <select id="qr-size" class="form-select">
<option value="200">Pequeno (200px)</option> <option value="200">Pequeno (200px)</option>
<option value="300" selected>Médio (300px)</option> <option value="300" selected>Médio (300px)</option>

View File

@ -0,0 +1,12 @@
@{
ViewData["Title"] = "Pagamento Cancelado";
}
<div class="container text-center mt-5">
<div class="alert alert-warning">
<h4 class="alert-heading">Pagamento cancelado.</h4>
<p>@ViewBag.CancelMessage</p>
</div>
<a href="/Pagamento/SelecaoPlano" class="btn btn-primary">Ver Planos</a>
</div>

View File

@ -0,0 +1,116 @@
@model QRRapidoApp.Models.ViewModels.SelecaoPlanoViewModel
@{
ViewData["Title"] = "Escolha seu Plano Premium";
Layout = "~/Views/Shared/_Layout.cshtml";
var monthlyPlan = Model.Plans.FirstOrDefault(p => p.Interval == "month");
var yearlyPlan = Model.Plans.FirstOrDefault(p => p.Interval == "year");
var monthlyPrice = monthlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
var yearlyPrice = yearlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
var yearlySavings = (monthlyPrice * 12) - yearlyPrice;
}
<div class="container mt-5">
<div class="text-center mb-5">
<h1 class="display-4">Desbloqueie o Poder Total do QRRápido</h1>
<p class="lead text-muted">Acesso sem limites, sem anúncios e com recursos exclusivos para máxima produtividade.</p>
</div>
<div class="row justify-content-center g-4">
<!-- Plano Mensal -->
@if (monthlyPlan != null)
{
<div class="col-lg-4 col-md-6">
<div class="card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<h3 class="card-title text-center">Plano Mensal</h3>
<div class="text-center my-4">
<span class="display-4 fw-bold">R$ @monthlyPrice.ToString("0.00")</span>
<span class="text-muted">/mês</span>
</div>
<p class="text-center text-muted">Ideal para começar a explorar os recursos premium.</p>
<button class="btn btn-outline-primary mt-auto checkout-btn" data-plan-id="@monthlyPlan.Id">Assinar Agora</button>
</div>
</div>
</div>
}
<!-- Plano Anual -->
@if (yearlyPlan != null)
{
<div class="col-lg-4 col-md-6">
<div class="card h-100 shadow border-primary">
<div class="card-header bg-primary text-white text-center">
<h3 class="card-title mb-0">Plano Anual</h3>
<p class="mb-0">Recomendado</p>
</div>
<div class="card-body d-flex flex-column">
<div class="text-center my-4">
<span class="display-4 fw-bold">R$ @yearlyPrice.ToString("0.00")</span>
<span class="text-muted">/ano</span>
</div>
@if (yearlySavings > 0)
{
<div class="text-center mb-3">
<span class="badge bg-success">Economize R$ @yearlySavings.ToString("0.00")!</span>
</div>
}
<p class="text-center text-muted">O melhor custo-benefício para usuários frequentes.</p>
<button class="btn btn-primary mt-auto checkout-btn" data-plan-id="@yearlyPlan.Id">Assinar Plano Anual</button>
</div>
</div>
</div>
}
</div>
<!-- Lista de Recursos -->
<div class="row justify-content-center mt-5">
<div class="col-lg-8">
<h3 class="text-center mb-4">Todos os planos incluem:</h3>
<ul class="list-group list-group-flush">
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>QR codes ilimitados</li>
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Sem anúncios</li>
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>QR codes dinâmicos</li>
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Analytics em tempo real</li>
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Suporte prioritário</li>
</ul>
</div>
</div>
</div>
@section Scripts {
<script>
document.querySelectorAll('.checkout-btn').forEach(button => {
button.addEventListener('click', async function() {
const planId = this.dataset.planId;
this.disabled = true;
this.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Redirecionando...';
try {
const response = await fetch('/Pagamento/CreateCheckout', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `planId=${planId}`
});
const result = await response.json();
if (result.success) {
window.location.href = result.url;
} else {
alert('Erro: ' + result.error);
this.disabled = false;
this.innerHTML = 'Assinar Agora'; // Reset button text
}
} catch (error) {
console.error('Checkout error:', error);
alert('Ocorreu um erro ao iniciar o pagamento. Tente novamente.');
this.disabled = false;
this.innerHTML = 'Assinar Agora'; // Reset button text
}
});
});
</script>
}

View File

@ -0,0 +1,12 @@
@{
ViewData["Title"] = "Sucesso";
}
<div class="container text-center mt-5">
<div class="alert alert-success">
<h4 class="alert-heading">Pagamento bem-sucedido!</h4>
<p>@ViewBag.SuccessMessage</p>
</div>
<a href="/" class="btn btn-primary">Voltar ao Início</a>
</div>

View File

@ -1,6 +1,8 @@
@using QRRapidoApp.Services @using QRRapidoApp.Services
@using Microsoft.AspNetCore.Http.Extensions @using Microsoft.AspNetCore.Http.Extensions
@using Microsoft.Extensions.Localization
@inject AdDisplayService AdService @inject AdDisplayService AdService
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pt-BR"> <html lang="pt-BR">
<head> <head>
@ -40,37 +42,37 @@
<!-- Structured Data Schema.org --> <!-- Structured Data Schema.org -->
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@@context": "https://schema.org", "@@context": "https://schema.org",
"@@type": "WebApplication", "@@type": "WebApplication",
"name": "QR Rapido", "name": "QR Rapido",
"description": "Gerador de QR Code ultrarrápido em português e espanhol", "description": "Gerador de QR Code ultrarrápido em português e espanhol",
"url": "https://qrrapido.site", "url": "https://qrrapido.site",
"applicationCategory": "UtilityApplication", "applicationCategory": "UtilityApplication",
"operatingSystem": "Web", "operatingSystem": "Web",
"author": { "author": {
"@@type": "Organization", "@@type": "Organization",
"name": "QR Rapido" "name": "QR Rapido"
}, },
"offers": { "offers": {
"@@type": "Offer", "@@type": "Offer",
"price": "0", "price": "0",
"priceCurrency": "BRL", "priceCurrency": "BRL",
"description": "Geração gratuita de QR codes" "description": "Geração gratuita de QR codes"
}, },
"aggregateRating": { "aggregateRating": {
"@@type": "AggregateRating", "@@type": "AggregateRating",
"ratingValue": "4.8", "ratingValue": "4.8",
"reviewCount": "2547" "reviewCount": "2547"
}, },
"featureList": [ "featureList": [
"Geração em segundos", "Geração em segundos",
"Suporte multilíngue", "Suporte multilíngue",
"Sem cadastro obrigatório", "Sem cadastro obrigatório",
"30 dias sem anúncios", "30 dias sem anúncios",
"Download múltiplos formatos" "Download múltiplos formatos"
] ]
} }
</script> </script>
<!-- Google Analytics 4 --> <!-- Google Analytics 4 -->
@ -80,44 +82,44 @@
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID', { gtag('config', 'GA_MEASUREMENT_ID', {
send_page_view: false send_page_view: false
}); });
// Custom events for QR Rapido // Custom events for QR Rapido
window.trackQRGeneration = function(type, time, isPremium) { window.trackQRGeneration = function(type, time, isPremium) {
gtag('event', 'qr_generated', { gtag('event', 'qr_generated', {
'event_category': 'QR Generation', 'event_category': 'QR Generation',
'event_label': type, 'event_label': type,
'value': Math.round(parseFloat(time) * 1000), 'value': Math.round(parseFloat(time) * 1000),
'custom_parameters': { 'custom_parameters': {
'generation_time': parseFloat(time), 'generation_time': parseFloat(time),
'user_type': isPremium ? 'premium' : 'free', 'user_type': isPremium ? 'premium' : 'free',
'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal' 'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal'
} }
}); });
}; };
window.trackSpeedComparison = function(ourTime, competitorAvg) { window.trackSpeedComparison = function(ourTime, competitorAvg) {
gtag('event', 'speed_comparison', { gtag('event', 'speed_comparison', {
'event_category': 'Performance', 'event_category': 'Performance',
'our_time': parseFloat(ourTime), 'our_time': parseFloat(ourTime),
'competitor_avg': parseFloat(competitorAvg), 'competitor_avg': parseFloat(competitorAvg),
'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime) 'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime)
}); });
}; };
window.trackLanguageChange = function(from, to) { window.trackLanguageChange = function(from, to) {
gtag('event', 'language_change', { gtag('event', 'language_change', {
'event_category': 'Localization', 'event_category': 'Localization',
'previous_language': from, 'previous_language': from,
'new_language': to 'new_language': to
}); });
}; };
</script> </script>
<!-- AdSense --> <!-- AdSense -->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX" <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<!-- Bootstrap 5 --> <!-- Bootstrap 5 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
@ -151,7 +153,7 @@
</svg> </svg>
<div> <div>
<h1 class="h4 mb-0 text-primary fw-bold">QR Rapido</h1> <h1 class="h4 mb-0 text-primary fw-bold">QR Rapido</h1>
<small class="text-muted" id="tagline">Gere QR codes em segundos!</small> <small class="text-muted" id="tagline">@Localizer["Tagline"]</small>
</div> </div>
</a> </a>
@ -183,6 +185,9 @@
<i class="fas fa-user"></i> @User.Identity.Name <i class="fas fa-user"></i> @User.Identity.Name
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="/">
<i class="fas fa-qrcode"></i> Gerar QRCode
</a></li>
<li><a class="dropdown-item" href="/Account/Profile"> <li><a class="dropdown-item" href="/Account/Profile">
<i class="fas fa-user-cog"></i> Perfil <i class="fas fa-user-cog"></i> Perfil
</a></li> </a></li>
@ -219,7 +224,7 @@
else else
{ {
<a href="/Account/Login" class="btn btn-primary btn-sm"> <a href="/Account/Login" class="btn btn-primary btn-sm">
<i class="fas fa-sign-in-alt"></i> Login <i class="fas fa-sign-in-alt"></i> @Localizer["Login"]
</a> </a>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<small class="text-success"> <small class="text-success">
@ -287,6 +292,7 @@
<!-- Custom JS --> <!-- Custom JS -->
<script src="~/js/test.js" asp-append-version="true"></script> <script src="~/js/test.js" asp-append-version="true"></script>
<script src="~/js/qr-speed-generator.js" asp-append-version="true"></script> <script src="~/js/qr-speed-generator.js" asp-append-version="true"></script>
<script src="~/js/language-switcher.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)
</body> </body>

View File

@ -8,6 +8,7 @@
"Version": "1.0.0" "Version": "1.0.0"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"MongoDB": "mongodb://localhost:27017/QrRapido"
}, },
"Authentication": { "Authentication": {
"Google": { "Google": {
@ -15,8 +16,8 @@
"ClientSecret": "your-google-client-secret" "ClientSecret": "your-google-client-secret"
}, },
"Microsoft": { "Microsoft": {
"ClientId": "your-microsoft-client-id", "ClientId": "9bec3835-acdb-4c5a-8668-6b90955c6ad2",
"ClientSecret": "your-microsoft-client-secret" "ClientSecret": "Oe38Q~FsZ3X5ouptAB6oYyX7MXaGUvxXcqT.aaT9"
} }
}, },
"Stripe": { "Stripe": {
@ -56,6 +57,7 @@
"KeywordsEN": "fast qr, quick qr generator, rapid qr code, qr code generator" "KeywordsEN": "fast qr, quick qr generator, rapid qr code, qr code generator"
}, },
"ApplicationName": "QRRapido", "ApplicationName": "QRRapido",
"Environment": "Personal",
"Serilog": { "Serilog": {
"SeqUrl": "http://localhost:5341", "SeqUrl": "http://localhost:5341",
"ApiKey": "", "ApiKey": "",
@ -84,7 +86,7 @@
"DatabaseSizeErrorMB": 5120, "DatabaseSizeErrorMB": 5120,
"GrowthRateWarningMBPerHour": 100, "GrowthRateWarningMBPerHour": 100,
"IncludeCollectionStats": true, "IncludeCollectionStats": true,
"CollectionsToMonitor": ["Users", "QRCodeHistory", "AdFreeSessions"] "CollectionsToMonitor": [ "Users", "QRCodeHistory", "AdFreeSessions" ]
}, },
"HealthChecks": { "HealthChecks": {
"MongoDB": { "MongoDB": {

View File

@ -0,0 +1,144 @@
// Language switching functionality for QR Rapido
document.addEventListener('DOMContentLoaded', function () {
// FORCE: Respect the URL culture above all else
let currentCulture = getCurrentCulture();
console.log('Current culture:', currentCulture);
localStorage.setItem('preferredLanguage', currentCulture);
const languageDropdownItems = document.querySelectorAll('.dropdown-item[data-lang]');
const currentLangSpan = document.getElementById('current-lang');
// Get current culture from URL or default to pt-BR
function getCurrentCulture() {
const pathSegments = window.location.pathname.split('/').filter(segment => segment);
const supportedCultures = ['pt-BR', 'es', 'en'];
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
return pathSegments[0];
}
return 'pt-BR';
}
// Update current language display
function updateCurrentLanguageDisplay(culture) {
const langMap = {
'pt-BR': 'PT',
'es': 'ES',
'en': 'EN'
};
if (currentLangSpan) {
currentLangSpan.textContent = langMap[culture] || 'PT';
}
}
// Build new URL with selected culture
function buildLocalizedUrl(newCulture) {
const currentPath = window.location.pathname;
const queryString = window.location.search;
const hash = window.location.hash;
// Remove existing culture from path if present
const pathSegments = currentPath.split('/').filter(segment => segment);
const supportedCultures = ['pt-BR', 'es', 'en'];
// Remove current culture if it's the first segment
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
pathSegments.shift();
}
// Build new path with selected culture
const newPath = '/' + newCulture + (pathSegments.length > 0 ? '/' + pathSegments.join('/') : '');
return newPath + queryString + hash;
}
// Handle language selection
// Handle language selection
languageDropdownItems.forEach(item => {
item.addEventListener('click', function (e) {
e.preventDefault();
const selectedLang = this.getAttribute('data-lang');
currentCulture = getCurrentCulture();
// Track language change for analytics
if (typeof window.trackLanguageChange === 'function') {
window.trackLanguageChange(currentCulture, selectedLang);
}
// Store language preference in localStorage
localStorage.setItem('preferredLanguage', selectedLang);
// Set culture cookie for server-side processing
document.cookie = `culture=${selectedLang}; path=/; max-age=31536000; SameSite=Lax`;
// Clear any cache and force full reload
if ('caches' in window) {
caches.keys().then(names => {
names.forEach(name => caches.delete(name));
});
}
// Force complete page reload with cache busting
const newUrl = buildLocalizedUrl(selectedLang);
// Na função de troca de idioma, substitua:
window.location.href = newUrl;
// Por:
window.location.replace(newUrl);
// OU
window.location.href = newUrl + (newUrl.includes('?') ? '&' : '?') + '_t=' + Date.now();
//window.location.replace(newUrl + '?_refresh=' + Date.now());
});
});
// Initialize current language display
currentCulture = getCurrentCulture();
updateCurrentLanguageDisplay(currentCulture);
// Store current culture in localStorage if not already set
if (!localStorage.getItem('preferredLanguage')) {
localStorage.setItem('preferredLanguage', currentCulture);
}
});
// Utility function to get user's preferred language
function getUserPreferredLanguage() {
// Check localStorage first
const storedLang = localStorage.getItem('preferredLanguage');
if (storedLang) {
return storedLang;
}
// Check browser language
const browserLang = navigator.language || navigator.userLanguage;
// Map browser languages to supported cultures
const langMap = {
'pt': 'pt-BR',
'pt-BR': 'pt-BR',
'es': 'es',
'es-PY': 'pt-BR', // Special case: Paraguay Spanish -> Portuguese
'en': 'en'
};
// Check exact match first
if (langMap[browserLang]) {
return langMap[browserLang];
}
// Check language part only (e.g., 'es' from 'es-AR')
const langPart = browserLang.split('-')[0];
if (langMap[langPart]) {
return langMap[langPart];
}
}