feat: Historico e localização!
This commit is contained in:
parent
c80b73e32f
commit
b189ea7275
@ -4,7 +4,8 @@
|
||||
"Bash(dotnet new:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(dotnet build:*)",
|
||||
"Bash(timeout:*)"
|
||||
"Bash(timeout:*)",
|
||||
"Bash(rm:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@ -15,12 +15,15 @@ namespace QRRapidoApp.Controllers
|
||||
private readonly IUserService _userService;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
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;
|
||||
_adDisplayService = adDisplayService;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -33,9 +36,10 @@ namespace QRRapidoApp.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult LoginGoogle(string returnUrl = "/")
|
||||
{
|
||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Url.Action("GoogleCallback"),
|
||||
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
||||
Items = { { "returnUrl", returnUrl } }
|
||||
};
|
||||
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
||||
@ -44,11 +48,8 @@ namespace QRRapidoApp.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
||||
{
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Url.Action("MicrosoftCallback"),
|
||||
Items = { { "returnUrl", returnUrl } }
|
||||
};
|
||||
var redirectUrl = Url.Action("MicrosoftCallback", "Account", new { returnUrl });
|
||||
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
|
||||
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
|
||||
120
Controllers/PagamentoController.cs
Normal file
120
Controllers/PagamentoController.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,111 +25,9 @@ namespace QRRapidoApp.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Upgrade()
|
||||
public IActionResult Upgrade()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
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);
|
||||
}
|
||||
return RedirectToAction("SelecaoPlano", "Pagamento");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
||||
@ -30,8 +30,9 @@ namespace QRRapidoApp.Data
|
||||
}
|
||||
}
|
||||
|
||||
public IMongoCollection<User>? Users => _isConnected ? _database?.GetCollection<User>("users") : null;
|
||||
public IMongoCollection<QRCodeHistory>? QRCodeHistory => _isConnected ? _database?.GetCollection<QRCodeHistory>("qr_codes") : null;
|
||||
public IMongoCollection<User> Users => _database.GetCollection<User>("users");
|
||||
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 IMongoDatabase? Database => _isConnected ? _database : null;
|
||||
|
||||
111
Middleware/LanguageRedirectionMiddleware.cs
Normal file
111
Middleware/LanguageRedirectionMiddleware.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Models/Extensions/PlanExtensions.cs
Normal file
58
Models/Extensions/PlanExtensions.cs
Normal 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
47
Models/Plan.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
12
Models/ViewModels/SelecaoPlanoViewModel.cs
Normal file
12
Models/ViewModels/SelecaoPlanoViewModel.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
69
Program.cs
69
Program.cs
@ -17,6 +17,7 @@ using System.Globalization;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -28,33 +29,28 @@ Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.WithProcessId()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido")
|
||||
.Enrich.WithProperty("Environment", "Dev")
|
||||
.WriteTo.Async(a => a.Console(
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
|
||||
theme: AnsiConsoleTheme.Code))
|
||||
.WriteTo.Async(a => {
|
||||
var seqUrl = builder.Configuration["Serilog:SeqUrl"];
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
.WriteTo.Async(a => a.Seq(builder.Configuration["Serilog:SeqUrl"],
|
||||
apiKey: builder.Configuration["Serilog:ApiKey"]=="" ? null : builder.Configuration["Serilog:ApiKey"]))
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
// 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
|
||||
builder.Services.AddHttpClient();
|
||||
@ -132,7 +128,8 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
||||
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
|
||||
|
||||
// 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 =>
|
||||
{
|
||||
var supportedCultures = new[]
|
||||
@ -142,17 +139,24 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
new CultureInfo("en")
|
||||
};
|
||||
|
||||
options.DefaultRequestCulture = new RequestCulture("pt-BR");
|
||||
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||
options.SupportedCultures = supportedCultures;
|
||||
options.SupportedUICultures = supportedCultures;
|
||||
|
||||
options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider());
|
||||
options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider());
|
||||
|
||||
options.FallBackToParentCultures = true;
|
||||
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
|
||||
builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<IPlanService, QRRapidoApp.Services.PlanService>();
|
||||
builder.Services.AddScoped<AdDisplayService>();
|
||||
builder.Services.AddScoped<StripeService>();
|
||||
|
||||
@ -206,6 +210,9 @@ app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Language redirection middleware (before routing)
|
||||
app.UseMiddleware<LanguageRedirectionMiddleware>();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseCors("AllowSpecificOrigins");
|
||||
@ -216,26 +223,28 @@ app.UseRequestLocalization();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseSession();
|
||||
|
||||
// Custom middleware
|
||||
app.UseMiddleware<LastLoginUpdateMiddleware>();
|
||||
|
||||
// Health check endpoint
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
// Controller routes
|
||||
// Language routes (must be first)
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
name: "localized",
|
||||
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
// API routes
|
||||
app.MapControllerRoute(
|
||||
name: "api",
|
||||
pattern: "api/{controller}/{action=Index}/{id?}");
|
||||
|
||||
// Language routes
|
||||
// Default fallback route (for development/testing without culture)
|
||||
app.MapControllerRoute(
|
||||
name: "localized",
|
||||
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.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.Seq" Version="9.0.0" />
|
||||
<PackageReference Include="Stripe.net" Version="43.15.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
||||
@ -34,7 +35,6 @@
|
||||
<Folder Include="wwwroot\images\" />
|
||||
<Folder Include="wwwroot\css\" />
|
||||
<Folder Include="wwwroot\js\" />
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Data\" />
|
||||
<Folder Include="Services\" />
|
||||
<Folder Include="Models\" />
|
||||
@ -42,4 +42,28 @@
|
||||
<Folder Include="Tests\" />
|
||||
</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>
|
||||
693
Resources/SharedResource.Designer.cs
generated
Normal file
693
Resources/SharedResource.Designer.cs
generated
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,28 +59,28 @@
|
||||
<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>
|
||||
<value>Generate QR codes in seconds!</value>
|
||||
</data>
|
||||
<data name="GenerateQR" xml:space="preserve">
|
||||
<value>Gerar QR Code</value>
|
||||
<value>Generate QR Code</value>
|
||||
</data>
|
||||
<data name="QRType" xml:space="preserve">
|
||||
<value>Tipo de QR Code</value>
|
||||
<value>QR Code Type</value>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Conteúdo</value>
|
||||
<value>Content</value>
|
||||
</data>
|
||||
<data name="URLType" xml:space="preserve">
|
||||
<value>URL/Link</value>
|
||||
</data>
|
||||
<data name="TextType" xml:space="preserve">
|
||||
<value>Texto Simples</value>
|
||||
<value>Plain Text</value>
|
||||
</data>
|
||||
<data name="WiFiType" xml:space="preserve">
|
||||
<value>WiFi</value>
|
||||
</data>
|
||||
<data name="VCardType" xml:space="preserve">
|
||||
<value>Cartão de Visita</value>
|
||||
<value>Business Card</value>
|
||||
</data>
|
||||
<data name="SMSType" xml:space="preserve">
|
||||
<value>SMS</value>
|
||||
@ -89,94 +89,94 @@
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="DynamicType" xml:space="preserve">
|
||||
<value>QR Dinâmico (Premium)</value>
|
||||
<value>Dynamic QR (Premium)</value>
|
||||
</data>
|
||||
<data name="QuickStyle" xml:space="preserve">
|
||||
<value>Estilo Rápido</value>
|
||||
<value>Quick Style</value>
|
||||
</data>
|
||||
<data name="ClassicStyle" xml:space="preserve">
|
||||
<value>Clássico</value>
|
||||
<value>Classic</value>
|
||||
</data>
|
||||
<data name="ModernStyle" xml:space="preserve">
|
||||
<value>Moderno</value>
|
||||
<value>Modern</value>
|
||||
</data>
|
||||
<data name="ColorfulStyle" xml:space="preserve">
|
||||
<value>Colorido</value>
|
||||
<value>Colorful</value>
|
||||
</data>
|
||||
<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 name="AdvancedCustomization" xml:space="preserve">
|
||||
<value>Personalização Avançada</value>
|
||||
<value>Advanced Customization</value>
|
||||
</data>
|
||||
<data name="PrimaryColor" xml:space="preserve">
|
||||
<value>Cor Principal</value>
|
||||
<value>Primary Color</value>
|
||||
</data>
|
||||
<data name="BackgroundColor" xml:space="preserve">
|
||||
<value>Cor de Fundo</value>
|
||||
<value>Background Color</value>
|
||||
</data>
|
||||
<data name="Size" xml:space="preserve">
|
||||
<value>Tamanho</value>
|
||||
<value>Size</value>
|
||||
</data>
|
||||
<data name="Margin" xml:space="preserve">
|
||||
<value>Margem</value>
|
||||
<value>Margin</value>
|
||||
</data>
|
||||
<data name="Logo" xml:space="preserve">
|
||||
<value>Logo/Ícone</value>
|
||||
<value>Logo/Icon</value>
|
||||
</data>
|
||||
<data name="CornerStyle" xml:space="preserve">
|
||||
<value>Estilo das Bordas</value>
|
||||
<value>Corner Style</value>
|
||||
</data>
|
||||
<data name="GenerateRapidly" xml:space="preserve">
|
||||
<value>Gerar QR Code Rapidamente</value>
|
||||
<value>Generate QR Code Rapidly</value>
|
||||
</data>
|
||||
<data name="Preview" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<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 name="UltraFastGeneration" xml:space="preserve">
|
||||
<value>Geração ultra-rápida garantida</value>
|
||||
<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 (Vetorial)</value>
|
||||
<value>Download SVG (Vector)</value>
|
||||
</data>
|
||||
<data name="DownloadPDF" xml:space="preserve">
|
||||
<value>Download PDF</value>
|
||||
</data>
|
||||
<data name="SaveToHistory" xml:space="preserve">
|
||||
<value>Salvar no Histórico</value>
|
||||
<value>Save to History</value>
|
||||
</data>
|
||||
<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 name="PremiumTitle" xml:space="preserve">
|
||||
<value>QR Rapido Premium</value>
|
||||
</data>
|
||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||
<value>Dicas para QR Mais Rápidos</value>
|
||||
<value>Tips for Faster QR</value>
|
||||
</data>
|
||||
<data name="SpeedTip1" xml:space="preserve">
|
||||
<value>URLs curtas geram mais rápido</value>
|
||||
<value>Short URLs generate faster</value>
|
||||
</data>
|
||||
<data name="SpeedTip2" xml:space="preserve">
|
||||
<value>Menos texto = maior velocidade</value>
|
||||
<value>Less text = higher speed</value>
|
||||
</data>
|
||||
<data name="SpeedTip3" xml:space="preserve">
|
||||
<value>Cores sólidas otimizam o processo</value>
|
||||
<value>Solid colors optimize the process</value>
|
||||
</data>
|
||||
<data name="SpeedTip4" xml:space="preserve">
|
||||
<value>Tamanhos menores aceleram o download</value>
|
||||
<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>Entrar com</value>
|
||||
<value>Login with</value>
|
||||
</data>
|
||||
<data name="Google" xml:space="preserve">
|
||||
<value>Google</value>
|
||||
@ -185,39 +185,39 @@
|
||||
<value>Microsoft</value>
|
||||
</data>
|
||||
<data name="AdFreeOffer" xml:space="preserve">
|
||||
<value>Login = 30 dias sem anúncios!</value>
|
||||
<value>Unlock all premium features!</value>
|
||||
</data>
|
||||
<data name="SpecialOffer" xml:space="preserve">
|
||||
<value>Oferta Especial!</value>
|
||||
<value>Premium Offer!</value>
|
||||
</data>
|
||||
<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 name="Privacy" xml:space="preserve">
|
||||
<value>Política de Privacidade</value>
|
||||
<value>Privacy Policy</value>
|
||||
</data>
|
||||
<data name="BackToGenerator" xml:space="preserve">
|
||||
<value>Voltar ao gerador</value>
|
||||
<value>Back to generator</value>
|
||||
</data>
|
||||
<data name="GeneratedIn" xml:space="preserve">
|
||||
<value>Gerado em</value>
|
||||
<value>Generated in</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>s</value>
|
||||
</data>
|
||||
<data name="UltraFast" xml:space="preserve">
|
||||
<value>Geração ultra rápida!</value>
|
||||
<value>Ultra fast generation!</value>
|
||||
</data>
|
||||
<data name="Fast" xml:space="preserve">
|
||||
<value>Geração rápida!</value>
|
||||
<value>Fast generation!</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>Geração normal</value>
|
||||
<value>Normal generation</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Erro na geração. Tente novamente.</value>
|
||||
<value>Generation error. Try again.</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>QR Code salvo no histórico!</value>
|
||||
<value>QR Code saved to history!</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -59,13 +59,13 @@
|
||||
<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>¡Genera códigos QR en segundos!</value>
|
||||
<value>Genera codigos QR en segundos!</value>
|
||||
</data>
|
||||
<data name="GenerateQR" xml:space="preserve">
|
||||
<value>Generar Código QR</value>
|
||||
<value>Generar Codigo QR</value>
|
||||
</data>
|
||||
<data name="QRType" xml:space="preserve">
|
||||
<value>Tipo de Código QR</value>
|
||||
<value>Tipo de Codigo QR</value>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Contenido</value>
|
||||
@ -89,13 +89,13 @@
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="DynamicType" xml:space="preserve">
|
||||
<value>QR Dinámico (Premium)</value>
|
||||
<value>QR Dinamico (Premium)</value>
|
||||
</data>
|
||||
<data name="QuickStyle" xml:space="preserve">
|
||||
<value>Estilo Rápido</value>
|
||||
<value>Estilo Rapido</value>
|
||||
</data>
|
||||
<data name="ClassicStyle" xml:space="preserve">
|
||||
<value>Clásico</value>
|
||||
<value>Clasico</value>
|
||||
</data>
|
||||
<data name="ModernStyle" xml:space="preserve">
|
||||
<value>Moderno</value>
|
||||
@ -104,10 +104,10 @@
|
||||
<value>Colorido</value>
|
||||
</data>
|
||||
<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 name="AdvancedCustomization" xml:space="preserve">
|
||||
<value>Personalización Avanzada</value>
|
||||
<value>Personalizacion Avanzada</value>
|
||||
</data>
|
||||
<data name="PrimaryColor" xml:space="preserve">
|
||||
<value>Color Principal</value>
|
||||
@ -116,7 +116,7 @@
|
||||
<value>Color de Fondo</value>
|
||||
</data>
|
||||
<data name="Size" xml:space="preserve">
|
||||
<value>Tamaño</value>
|
||||
<value>Tamano</value>
|
||||
</data>
|
||||
<data name="Margin" xml:space="preserve">
|
||||
<value>Margen</value>
|
||||
@ -125,19 +125,19 @@
|
||||
<value>Logo/Icono</value>
|
||||
</data>
|
||||
<data name="CornerStyle" xml:space="preserve">
|
||||
<value>Estilo de Bordes</value>
|
||||
<value>Estilo de Esquinas</value>
|
||||
</data>
|
||||
<data name="GenerateRapidly" xml:space="preserve">
|
||||
<value>Generar Código QR Rápidamente</value>
|
||||
<value>Generar Codigo QR Rapidamente</value>
|
||||
</data>
|
||||
<data name="Preview" xml:space="preserve">
|
||||
<value>Vista Previa</value>
|
||||
</data>
|
||||
<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 name="UltraFastGeneration" xml:space="preserve">
|
||||
<value>Generación ultra-rápida garantizada</value>
|
||||
<value>Generacion ultra-rapida garantizada</value>
|
||||
</data>
|
||||
<data name="DownloadPNG" xml:space="preserve">
|
||||
<value>Descargar PNG</value>
|
||||
@ -152,31 +152,31 @@
|
||||
<value>Guardar en Historial</value>
|
||||
</data>
|
||||
<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 name="PremiumTitle" xml:space="preserve">
|
||||
<value>QR Rapido Premium</value>
|
||||
</data>
|
||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||
<value>Consejos para QR Más Rápidos</value>
|
||||
<value>Consejos para QR Mas Rapidos</value>
|
||||
</data>
|
||||
<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 name="SpeedTip2" xml:space="preserve">
|
||||
<value>Menos texto = mayor velocidad</value>
|
||||
</data>
|
||||
<data name="SpeedTip3" xml:space="preserve">
|
||||
<value>Colores sólidos optimizan el proceso</value>
|
||||
<value>Colores solidos optimizan el proceso</value>
|
||||
</data>
|
||||
<data name="SpeedTip4" xml:space="preserve">
|
||||
<value>Tamaños menores aceleran la descarga</value>
|
||||
<value>Tamanos menores aceleran la descarga</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Iniciar Sesión</value>
|
||||
<value>Iniciar Sesion</value>
|
||||
</data>
|
||||
<data name="LoginWith" xml:space="preserve">
|
||||
<value>Iniciar sesión con</value>
|
||||
<value>Iniciar con</value>
|
||||
</data>
|
||||
<data name="Google" xml:space="preserve">
|
||||
<value>Google</value>
|
||||
@ -185,16 +185,16 @@
|
||||
<value>Microsoft</value>
|
||||
</data>
|
||||
<data name="AdFreeOffer" xml:space="preserve">
|
||||
<value>¡Login = 30 días sin anuncios!</value>
|
||||
<value>Desbloquea todas las funciones premium!</value>
|
||||
</data>
|
||||
<data name="SpecialOffer" xml:space="preserve">
|
||||
<value>¡Oferta Especial!</value>
|
||||
<value>Oferta Premium!</value>
|
||||
</data>
|
||||
<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 name="Privacy" xml:space="preserve">
|
||||
<value>Política de Privacidad</value>
|
||||
<value>Politica de Privacidad</value>
|
||||
</data>
|
||||
<data name="BackToGenerator" xml:space="preserve">
|
||||
<value>Volver al generador</value>
|
||||
@ -206,18 +206,66 @@
|
||||
<value>s</value>
|
||||
</data>
|
||||
<data name="UltraFast" xml:space="preserve">
|
||||
<value>¡Generación ultra rápida!</value>
|
||||
<value>Generacion ultra rapida!</value>
|
||||
</data>
|
||||
<data name="Fast" xml:space="preserve">
|
||||
<value>¡Generación rápida!</value>
|
||||
<value>Generacion rapida!</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>Generación normal</value>
|
||||
<value>Generacion normal</value>
|
||||
</data>
|
||||
<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 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>
|
||||
</root>
|
||||
271
Resources/SharedResource.pt-BR.resx
Normal file
271
Resources/SharedResource.pt-BR.resx
Normal 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>
|
||||
271
Resources/SharedResource.resx
Normal file
271
Resources/SharedResource.resx
Normal 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
14
Services/IPlanService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,10 @@ namespace QRRapidoApp.Services
|
||||
Task<User?> GetUserByProviderAsync(string provider, string providerId);
|
||||
Task<User> CreateUserAsync(string email, string name, string provider, string providerId);
|
||||
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<int> GetDailyQRCountAsync(string? userId);
|
||||
Task<int> DecrementDailyQRCountAsync(string userId);
|
||||
|
||||
35
Services/PlanService.cs
Normal file
35
Services/PlanService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Services/RouteDataRequestCultureProvider.cs
Normal file
27
Services/RouteDataRequestCultureProvider.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,12 @@
|
||||
|
||||
using Stripe;
|
||||
using Stripe.Checkout;
|
||||
using QRRapidoApp.Models;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace QRRapidoApp.Services
|
||||
{
|
||||
@ -15,253 +21,128 @@ namespace QRRapidoApp.Services
|
||||
_config = config;
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
StripeConfiguration.ApiKey = _config["Stripe:SecretKey"];
|
||||
}
|
||||
|
||||
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
|
||||
{
|
||||
try
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
var options = new SessionCreateOptions
|
||||
{
|
||||
PaymentMethodTypes = new List<string> { "card" },
|
||||
Mode = "subscription",
|
||||
LineItems = new List<SessionLineItemOptions>
|
||||
{
|
||||
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" }
|
||||
}
|
||||
};
|
||||
throw new Exception("User not found");
|
||||
}
|
||||
|
||||
var service = new SessionService();
|
||||
var session = await service.CreateAsync(options);
|
||||
|
||||
_logger.LogInformation($"Created Stripe checkout session for user {userId}: {session.Id}");
|
||||
|
||||
return session.Url;
|
||||
}
|
||||
catch (Exception ex)
|
||||
var customerId = user.StripeCustomerId;
|
||||
if (string.IsNullOrEmpty(customerId))
|
||||
{
|
||||
_logger.LogError(ex, $"Error creating Stripe checkout session for user {userId}: {ex.Message}");
|
||||
throw;
|
||||
var customerOptions = new CustomerCreateOptions
|
||||
{
|
||||
Email = user.Email,
|
||||
Name = user.Name,
|
||||
Metadata = new Dictionary<string, string> { { "app_user_id", user.Id } }
|
||||
};
|
||||
var customerService = new CustomerService();
|
||||
var customer = await customerService.CreateAsync(customerOptions);
|
||||
customerId = customer.Id;
|
||||
await _userService.UpdateUserStripeCustomerIdAsync(userId, customerId);
|
||||
}
|
||||
|
||||
var options = new SessionCreateOptions
|
||||
{
|
||||
PaymentMethodTypes = new List<string> { "card" },
|
||||
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)
|
||||
{
|
||||
var webhookSecret = _config["Stripe:WebhookSecret"];
|
||||
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
|
||||
|
||||
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
|
||||
|
||||
switch (stripeEvent.Type)
|
||||
{
|
||||
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;
|
||||
|
||||
case Events.InvoicePaymentSucceeded:
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
if (invoice?.SubscriptionId != null)
|
||||
{
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
|
||||
var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId);
|
||||
if (user != null)
|
||||
{
|
||||
await ProcessSubscriptionActivation(user.Id, subscription);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Events.CustomerSubscriptionDeleted:
|
||||
var deletedSubscription = stripeEvent.Data.Object as Subscription;
|
||||
if (deletedSubscription != null)
|
||||
{
|
||||
await _userService.DeactivatePremiumStatus(deletedSubscription.Id);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessSubscriptionActivation(string userId, Subscription subscription)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId) || subscription == null)
|
||||
{
|
||||
_logger.LogWarning("Could not process subscription activation due to missing userId or subscription data.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
|
||||
|
||||
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
|
||||
|
||||
switch (stripeEvent.Type)
|
||||
{
|
||||
case "checkout.session.completed":
|
||||
var session = stripeEvent.Data.Object as Session;
|
||||
if (session != null)
|
||||
{
|
||||
await ActivatePremiumAsync(session.ClientReferenceId, session.CustomerId, session.SubscriptionId);
|
||||
}
|
||||
break;
|
||||
|
||||
case "invoice.payment_succeeded":
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
if (invoice != null && invoice.SubscriptionId != null)
|
||||
{
|
||||
await RenewPremiumSubscriptionAsync(invoice.SubscriptionId);
|
||||
}
|
||||
break;
|
||||
|
||||
case "invoice.payment_failed":
|
||||
var failedInvoice = stripeEvent.Data.Object as Invoice;
|
||||
if (failedInvoice != null && failedInvoice.SubscriptionId != null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
_logger.LogWarning($"User not found for premium activation: {userId}");
|
||||
return;
|
||||
}
|
||||
catch (StripeException ex)
|
||||
|
||||
if (string.IsNullOrEmpty(user.StripeCustomerId))
|
||||
{
|
||||
_logger.LogError(ex, $"Stripe webhook error: {ex.Message}");
|
||||
throw;
|
||||
await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error processing Stripe webhook: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ActivatePremiumAsync(string? userId, string? customerId, string? subscriptionId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_logger.LogError(ex, $"Error activating premium for user {userId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenewPremiumSubscriptionAsync(string subscriptionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find user by subscription ID
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
await _userService.ActivatePremiumStatus(userId, subscription.Id, subscription.CurrentPeriodEnd);
|
||||
_logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}.");
|
||||
}
|
||||
|
||||
public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(subscriptionId))
|
||||
return "None";
|
||||
|
||||
if (string.IsNullOrEmpty(subscriptionId)) return "None";
|
||||
try
|
||||
{
|
||||
var service = new SubscriptionService();
|
||||
@ -270,7 +151,7 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
@ -280,20 +161,15 @@ namespace QRRapidoApp.Services
|
||||
try
|
||||
{
|
||||
var service = new SubscriptionService();
|
||||
var subscription = await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions
|
||||
{
|
||||
InvoiceNow = false,
|
||||
Prorate = false
|
||||
});
|
||||
|
||||
_logger.LogInformation($"Canceled subscription {subscriptionId}");
|
||||
await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions());
|
||||
_logger.LogInformation($"Canceled subscription {subscriptionId} via API.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}: {ex.Message}");
|
||||
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +83,6 @@ namespace QRRapidoApp.Services
|
||||
await _context.Users.InsertOneAsync(user);
|
||||
_logger.LogInformation($"Created new user: {email} via {provider}");
|
||||
|
||||
// Create initial ad-free session for new users
|
||||
await CreateAdFreeSessionAsync(user.Id, "Login");
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@ -97,9 +94,6 @@ namespace QRRapidoApp.Services
|
||||
.Set(u => u.LastLoginAt, DateTime.UtcNow);
|
||||
|
||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||
|
||||
// Create new ad-free session if needed
|
||||
await CreateAdFreeSessionAsync(userId, "Login");
|
||||
}
|
||||
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 durationMinutes = customMinutes ?? _config.GetValue<int>("AdFree:LoginMinutes", 43200);
|
||||
|
||||
var session = new AdFreeSession
|
||||
{
|
||||
UserId = userId,
|
||||
StartedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddMinutes(durationMinutes),
|
||||
IsActive = true,
|
||||
SessionType = sessionType,
|
||||
DurationMinutes = durationMinutes,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
var update = Builders<User>.Update
|
||||
.Set(u => u.IsPremium, true)
|
||||
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
||||
.Set(u => u.PremiumExpiresAt, expiryDate)
|
||||
.Unset(u => u.PremiumCancelledAt);
|
||||
|
||||
await _context.AdFreeSessions.InsertOneAsync(session);
|
||||
|
||||
_logger.LogInformation($"Created {sessionType} ad-free session for user {userId} - {durationMinutes} minutes");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error creating ad-free session for user {userId}: {ex.Message}");
|
||||
}
|
||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||
_logger.LogInformation($"Activated premium for user {userId}");
|
||||
}
|
||||
|
||||
public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
|
||||
{
|
||||
var update = Builders<User>.Update
|
||||
.Set(u => u.IsPremium, false)
|
||||
.Set(u => u.PremiumCancelledAt, DateTime.UtcNow);
|
||||
|
||||
await _context.Users.UpdateOneAsync(u => u.StripeSubscriptionId == stripeSubscriptionId, update);
|
||||
_logger.LogInformation($"Deactivated premium for subscription {stripeSubscriptionId}");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
158
Views/Account/History.cshtml
Normal file
158
Views/Account/History.cshtml
Normal 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>
|
||||
}
|
||||
@ -1,11 +1,15 @@
|
||||
@using QRRapidoApp.Services
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject AdDisplayService AdService
|
||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Home";
|
||||
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<!-- QR Generator Form -->
|
||||
@ -13,7 +17,7 @@
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3 class="h5 mb-0">
|
||||
<i class="fas fa-qrcode"></i> Criar QR Code Rapidamente
|
||||
<i class="fas fa-qrcode"></i> @Localizer["CreateQRCodeQuickly"]
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@ -25,8 +29,8 @@
|
||||
{
|
||||
<div class="alert alert-success border-0">
|
||||
<i class="fas fa-crown text-warning"></i>
|
||||
<strong>Usuário Premium ativo!</strong>
|
||||
<span class="badge bg-success">Sem anúncios + Histórico + QR ilimitados</span>
|
||||
<strong>@Localizer["PremiumUserActive"]</strong>
|
||||
<span class="badge bg-success">@Localizer["NoAdsHistoryUnlimitedQR"]</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -42,7 +46,7 @@
|
||||
</div>
|
||||
<div class="speed-badge d-none">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,13 +55,13 @@
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">Ilimitado hoje</span>
|
||||
<span class="qr-counter">@Localizer["UnlimitedToday"]</span>
|
||||
</small>
|
||||
}
|
||||
else
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">10 QR codes restantes</span>
|
||||
<span class="qr-counter">@Localizer["QRCodesRemaining"]</span>
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
@ -66,50 +70,50 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<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>
|
||||
<select id="qr-type" class="form-select" required>
|
||||
<option value="">Selecione o tipo...</option>
|
||||
<option value="url">🌐 URL/Link</option>
|
||||
<option value="text">📝 Texto Simples</option>
|
||||
<option value="wifi">📶 WiFi</option>
|
||||
<option value="vcard">👤 Cartão de Visita</option>
|
||||
<option value="sms">💬 SMS</option>
|
||||
<option value="email">📧 Email</option>
|
||||
<option value="">@Localizer["SelectType"]</option>
|
||||
<option value="url">🌐 @Localizer["URLLink"]</option>
|
||||
<option value="text">📝 @Localizer["SimpleText"]</option>
|
||||
<option value="wifi">📶 @Localizer["WiFi"]</option>
|
||||
<option value="vcard">👤 @Localizer["VCard"]</option>
|
||||
<option value="sms">💬 @Localizer["SMS"]</option>
|
||||
<option value="email">📧 @Localizer["Email"]</option>
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<option value="dynamic">⚡ QR Dinâmico (Premium)</option>
|
||||
<option value="dynamic">⚡ @Localizer["DynamicQRPremium"]</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-palette"></i> Estilo Rápido
|
||||
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
|
||||
</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<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">
|
||||
<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">
|
||||
<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 class="mb-3">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-edit"></i> Conteúdo
|
||||
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||
</label>
|
||||
<textarea id="qr-content"
|
||||
class="form-control form-control-lg"
|
||||
rows="3"
|
||||
placeholder="Digite o conteúdo do seu QR code aqui..."
|
||||
placeholder="@Localizer["EnterQRCodeContent"]"
|
||||
required></textarea>
|
||||
<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>
|
||||
|
||||
@ -118,22 +122,22 @@
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<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>
|
||||
</h2>
|
||||
<div id="customization-panel" class="accordion-collapse collapse">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<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">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<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">
|
||||
<option value="200">Pequeno (200px)</option>
|
||||
<option value="300" selected>Médio (300px)</option>
|
||||
|
||||
12
Views/Pagamento/Cancelar.cshtml
Normal file
12
Views/Pagamento/Cancelar.cshtml
Normal 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>
|
||||
116
Views/Pagamento/SelecaoPlano.cshtml
Normal file
116
Views/Pagamento/SelecaoPlano.cshtml
Normal 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>
|
||||
}
|
||||
12
Views/Pagamento/Sucesso.cshtml
Normal file
12
Views/Pagamento/Sucesso.cshtml
Normal 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>
|
||||
@ -1,28 +1,30 @@
|
||||
@using QRRapidoApp.Services
|
||||
@using Microsoft.AspNetCore.Http.Extensions
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject AdDisplayService AdService
|
||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - QR Rapido | Gerador QR Code Ultrarrápido</title>
|
||||
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta name="description" content="QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login.">
|
||||
<meta name="keywords" content="qr rapido, gerador qr rapido, qr code rapido, codigo qr rapido, qr gratis rapido, generador qr rapido, qr ultrarapido">
|
||||
<meta name="author" content="QR Rapido">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="@Context.Request.GetDisplayUrl()">
|
||||
|
||||
|
||||
<!-- Hreflang for multilingual -->
|
||||
<link rel="alternate" hreflang="pt-BR" href="https://qrrapido.site/pt/">
|
||||
<link rel="alternate" hreflang="es" href="https://qrrapido.site/es/">
|
||||
<link rel="alternate" hreflang="en" href="https://qrrapido.site/en/">
|
||||
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
|
||||
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
||||
<meta property="og:description" content="Gere códigos QR em segundos! Grátis, rápido e fácil. 30 dias sem anúncios após login.">
|
||||
@ -31,48 +33,48 @@
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="QR Rapido">
|
||||
<meta property="og:locale" content="pt_BR">
|
||||
|
||||
|
||||
<!-- Twitter Cards -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="QR Rapido - Gerador QR Code Ultrarrápido">
|
||||
<meta name="twitter:description" content="Gere códigos QR em segundos! Grátis, rápido e fácil.">
|
||||
<meta name="twitter:image" content="https://qrrapido.site/images/qrrapido-twitter-card.png">
|
||||
|
||||
|
||||
<!-- Structured Data Schema.org -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebApplication",
|
||||
"name": "QR Rapido",
|
||||
"description": "Gerador de QR Code ultrarrápido em português e espanhol",
|
||||
"url": "https://qrrapido.site",
|
||||
"applicationCategory": "UtilityApplication",
|
||||
"operatingSystem": "Web",
|
||||
"author": {
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebApplication",
|
||||
"name": "QR Rapido",
|
||||
"description": "Gerador de QR Code ultrarrápido em português e espanhol",
|
||||
"url": "https://qrrapido.site",
|
||||
"applicationCategory": "UtilityApplication",
|
||||
"operatingSystem": "Web",
|
||||
"author": {
|
||||
"@@type": "Organization",
|
||||
"name": "QR Rapido"
|
||||
},
|
||||
"offers": {
|
||||
},
|
||||
"offers": {
|
||||
"@@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "BRL",
|
||||
"description": "Geração gratuita de QR codes"
|
||||
},
|
||||
"aggregateRating": {
|
||||
},
|
||||
"aggregateRating": {
|
||||
"@@type": "AggregateRating",
|
||||
"ratingValue": "4.8",
|
||||
"reviewCount": "2547"
|
||||
},
|
||||
"featureList": [
|
||||
},
|
||||
"featureList": [
|
||||
"Geração em segundos",
|
||||
"Suporte multilíngue",
|
||||
"Sem cadastro obrigatório",
|
||||
"30 dias sem anúncios",
|
||||
"Download múltiplos formatos"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Google Analytics 4 -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
|
||||
<script>
|
||||
@ -80,57 +82,57 @@
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'GA_MEASUREMENT_ID', {
|
||||
send_page_view: false
|
||||
send_page_view: false
|
||||
});
|
||||
|
||||
|
||||
// Custom events for QR Rapido
|
||||
window.trackQRGeneration = function(type, time, isPremium) {
|
||||
gtag('event', 'qr_generated', {
|
||||
'event_category': 'QR Generation',
|
||||
'event_label': type,
|
||||
'value': Math.round(parseFloat(time) * 1000),
|
||||
'custom_parameters': {
|
||||
'generation_time': parseFloat(time),
|
||||
'user_type': isPremium ? 'premium' : 'free',
|
||||
'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal'
|
||||
}
|
||||
});
|
||||
gtag('event', 'qr_generated', {
|
||||
'event_category': 'QR Generation',
|
||||
'event_label': type,
|
||||
'value': Math.round(parseFloat(time) * 1000),
|
||||
'custom_parameters': {
|
||||
'generation_time': parseFloat(time),
|
||||
'user_type': isPremium ? 'premium' : 'free',
|
||||
'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
window.trackSpeedComparison = function(ourTime, competitorAvg) {
|
||||
gtag('event', 'speed_comparison', {
|
||||
'event_category': 'Performance',
|
||||
'our_time': parseFloat(ourTime),
|
||||
'competitor_avg': parseFloat(competitorAvg),
|
||||
'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime)
|
||||
});
|
||||
gtag('event', 'speed_comparison', {
|
||||
'event_category': 'Performance',
|
||||
'our_time': parseFloat(ourTime),
|
||||
'competitor_avg': parseFloat(competitorAvg),
|
||||
'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
window.trackLanguageChange = function(from, to) {
|
||||
gtag('event', 'language_change', {
|
||||
'event_category': 'Localization',
|
||||
'previous_language': from,
|
||||
'new_language': to
|
||||
});
|
||||
gtag('event', 'language_change', {
|
||||
'event_category': 'Localization',
|
||||
'previous_language': from,
|
||||
'new_language': to
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<!-- AdSense -->
|
||||
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/qrrapido-theme.css" asp-append-version="true" />
|
||||
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/images/qrrapido-favicon.svg">
|
||||
<link rel="icon" type="image/png" href="/images/qrrapido-favicon-32x32.png">
|
||||
|
||||
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="theme-color" content="#007BFF">
|
||||
@ -151,7 +153,7 @@
|
||||
</svg>
|
||||
<div>
|
||||
<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>
|
||||
</a>
|
||||
|
||||
@ -183,6 +185,9 @@
|
||||
<i class="fas fa-user"></i> @User.Identity.Name
|
||||
</button>
|
||||
<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">
|
||||
<i class="fas fa-user-cog"></i> Perfil
|
||||
</a></li>
|
||||
@ -219,7 +224,7 @@
|
||||
else
|
||||
{
|
||||
<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>
|
||||
<div class="d-none d-md-block">
|
||||
<small class="text-success">
|
||||
@ -287,6 +292,7 @@
|
||||
<!-- Custom JS -->
|
||||
<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/language-switcher.js" asp-append-version="true"></script>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"Version": "1.0.0"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"MongoDB": "mongodb://localhost:27017/QrRapido"
|
||||
},
|
||||
"Authentication": {
|
||||
"Google": {
|
||||
@ -15,8 +16,8 @@
|
||||
"ClientSecret": "your-google-client-secret"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "your-microsoft-client-id",
|
||||
"ClientSecret": "your-microsoft-client-secret"
|
||||
"ClientId": "9bec3835-acdb-4c5a-8668-6b90955c6ad2",
|
||||
"ClientSecret": "Oe38Q~FsZ3X5ouptAB6oYyX7MXaGUvxXcqT.aaT9"
|
||||
}
|
||||
},
|
||||
"Stripe": {
|
||||
@ -56,6 +57,7 @@
|
||||
"KeywordsEN": "fast qr, quick qr generator, rapid qr code, qr code generator"
|
||||
},
|
||||
"ApplicationName": "QRRapido",
|
||||
"Environment": "Personal",
|
||||
"Serilog": {
|
||||
"SeqUrl": "http://localhost:5341",
|
||||
"ApiKey": "",
|
||||
@ -84,7 +86,7 @@
|
||||
"DatabaseSizeErrorMB": 5120,
|
||||
"GrowthRateWarningMBPerHour": 100,
|
||||
"IncludeCollectionStats": true,
|
||||
"CollectionsToMonitor": ["Users", "QRCodeHistory", "AdFreeSessions"]
|
||||
"CollectionsToMonitor": [ "Users", "QRCodeHistory", "AdFreeSessions" ]
|
||||
},
|
||||
"HealthChecks": {
|
||||
"MongoDB": {
|
||||
|
||||
144
wwwroot/js/language-switcher.js
Normal file
144
wwwroot/js/language-switcher.js
Normal 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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user