feat: Onboarding
This commit is contained in:
parent
2a623d1fd5
commit
e800900203
@ -9,7 +9,11 @@
|
||||
"Bash(dotnet run:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(pkill:*)",
|
||||
"Bash(true)"
|
||||
"Bash(true)",
|
||||
"Bash(node:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(mv:*)",
|
||||
"Bash(sed:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@ -68,11 +68,11 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="col-md-6 mb-3" id="qr-type-container">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-list"></i> @Localizer["QRCodeType"]
|
||||
</label>
|
||||
<select id="qr-type" class="form-select" required>
|
||||
<select id="qr-type" class="form-select qr-field-highlight" required>
|
||||
<option value="">@Localizer["SelectType"]</option>
|
||||
<option value="url">🌐 @Localizer["URLLink"]</option>
|
||||
<option value="text">📝 @Localizer["SimpleText"]</option>
|
||||
@ -87,38 +87,38 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-semibold">
|
||||
<label class="form-label fw-semibold qr-flow-disabled">
|
||||
<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>
|
||||
<div class="btn-group w-100 qr-flow-disabled" role="group" id="style-selector">
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked disabled>
|
||||
<label class="btn btn-outline-secondary" for="style-classic">@Localizer["Classic"]</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern">
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern" disabled>
|
||||
<label class="btn btn-outline-secondary" for="style-modern">@Localizer["Modern"]</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful">
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful" disabled>
|
||||
<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">
|
||||
<label class="form-label fw-semibold qr-flow-disabled">
|
||||
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||
</label>
|
||||
<textarea id="qr-content"
|
||||
class="form-control form-control-lg"
|
||||
class="form-control form-control-lg qr-flow-disabled"
|
||||
rows="3"
|
||||
placeholder="@Localizer["EnterQRCodeContent"]"
|
||||
required></textarea>
|
||||
required disabled></textarea>
|
||||
<div class="form-text">
|
||||
<span id="content-hints">@Localizer["ContentHints"]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VCard Interface (dynamic) -->
|
||||
<div id="vcard-interface" class="mb-3" style="display: none;">
|
||||
<div id="vcard-interface" class="mb-3 qr-flow-disabled" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-address-card"></i>
|
||||
<strong>Cartão de Visita Digital</strong> - Este QR Code criará um cartão de visita digital.
|
||||
@ -158,7 +158,7 @@
|
||||
<!-- Campos Opcionais -->
|
||||
<div class="optional-fields mb-4">
|
||||
<h6 class="fw-bold text-secondary mb-3">
|
||||
<i class="fas fa-plus-circle"></i> Informações Adicionais (Opcionais)
|
||||
<i class="fas fa-plus-circle"></i> Informações Adicionais (Opcionais) - Escolha abaixo
|
||||
</h6>
|
||||
|
||||
<!-- Empresa -->
|
||||
@ -258,7 +258,7 @@ END:VCARD</pre>
|
||||
</div>
|
||||
|
||||
<!-- Advanced customization (collapsible) -->
|
||||
<div class="accordion mb-3" id="customization-accordion">
|
||||
<div class="accordion mb-3 qr-flow-disabled" id="customization-accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
||||
@ -370,8 +370,8 @@ END:VCARD</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn">
|
||||
<div class="d-grid" id="generate-button-container" style="display: none;">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn" disabled>
|
||||
<i class="fas fa-bolt"></i> @Localizer["GenerateQRCodeQuickly"]
|
||||
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status">
|
||||
<span class="visually-hidden">@Localizer["Generating"]</span>
|
||||
@ -626,5 +626,6 @@ END:VCARD</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Ad Space Footer (conditional) -->
|
||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||
|
||||
643
Views/Home/sedTubiCQ
Normal file
643
Views/Home/sedTubiCQ
Normal file
@ -0,0 +1,643 @@
|
||||
@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 -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3 class="h5 mb-0">
|
||||
<i class="fas fa-qrcode"></i> @Localizer["CreateQRCodeQuickly"]
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
var isPremium = await AdService.HasValidPremiumSubscription(userId);
|
||||
@if (isPremium)
|
||||
{
|
||||
<div class="alert alert-success border-0">
|
||||
<i class="fas fa-crown text-warning"></i>
|
||||
<strong>@Localizer["PremiumUserActive"]</strong>
|
||||
<span class="badge bg-success">@Localizer["NoAdsHistoryUnlimitedQR"]</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<form id="qr-speed-form" class="needs-validation" novalidate>
|
||||
<!-- Generation timer -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="generation-timer d-none">
|
||||
<i class="fas fa-stopwatch text-primary"></i>
|
||||
<span class="fw-bold text-primary">0.0s</span>
|
||||
</div>
|
||||
<div class="speed-badge d-none">
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-bolt"></i> @Localizer["UltraFastGeneration"]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">@Localizer["UnlimitedToday"]</span>
|
||||
</small>
|
||||
}
|
||||
else
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">@Localizer["QRCodesRemaining"]</span>
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3" id="qr-type-container">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-list"></i> @Localizer["QRCodeType"]
|
||||
<i class="fas fa-chevron-down chevron-animated d-none" id="type-chevron"></i>
|
||||
</label>
|
||||
<select id="qr-type" class="form-select qr-field-highlight" required>
|
||||
<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">⚡ @Localizer["DynamicQRPremium"]</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-semibold qr-flow-disabled">
|
||||
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
|
||||
</label>
|
||||
<div class="btn-group w-100 qr-flow-disabled" role="group" id="style-selector">
|
||||
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked disabled>
|
||||
<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" disabled>
|
||||
<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" disabled>
|
||||
<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 qr-flow-disabled">
|
||||
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||
</label>
|
||||
<textarea id="qr-content"
|
||||
class="form-control form-control-lg qr-flow-disabled"
|
||||
rows="3"
|
||||
placeholder="@Localizer["EnterQRCodeContent"]"
|
||||
required disabled></textarea>
|
||||
<div class="form-text">
|
||||
<span id="content-hints">@Localizer["ContentHints"]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VCard Interface (dynamic) -->
|
||||
<div id="vcard-interface" class="mb-3 qr-flow-disabled" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-address-card"></i>
|
||||
<strong>Cartão de Visita Digital</strong> - Este QR Code criará um cartão de visita digital.
|
||||
Quando escaneado, oferecerá para salvar seus contatos automaticamente.
|
||||
</div>
|
||||
|
||||
<!-- Campos Obrigatórios -->
|
||||
<div class="required-fields mb-4">
|
||||
<h6 class="fw-bold text-primary mb-3">
|
||||
<i class="fas fa-user-check"></i> Informações Essenciais
|
||||
</h6>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label fw-semibold">Nome Completo *</label>
|
||||
<input type="text" id="vcard-name" class="form-control" placeholder="Ricardo Gonçalves" required>
|
||||
<div class="invalid-feedback">Nome é obrigatório</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-semibold">Telefone Celular *</label>
|
||||
<input type="tel" id="vcard-mobile" class="form-control" placeholder="11961534225" required>
|
||||
<small class="form-text text-muted">Apenas números (DDD + número)</small>
|
||||
<div class="invalid-feedback">Telefone celular é obrigatório</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-semibold">Email *</label>
|
||||
<input type="email" id="vcard-email" class="form-control" placeholder="seu@email.com" required>
|
||||
<div class="invalid-feedback">Email válido é obrigatório</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Campos Opcionais -->
|
||||
<div class="optional-fields mb-4">
|
||||
<h6 class="fw-bold text-secondary mb-3">
|
||||
<i class="fas fa-plus-circle"></i> Informações Adicionais (Opcionais)
|
||||
</h6>
|
||||
|
||||
<!-- Empresa -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="enable-company" class="form-check-input">
|
||||
<label for="enable-company" class="form-check-label fw-semibold">
|
||||
<i class="fas fa-building"></i> Empresa
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" id="company-group" style="display: none;">
|
||||
<input type="text" id="vcard-company" class="form-control" placeholder="Nome da Empresa">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cargo -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="enable-title" class="form-check-input">
|
||||
<label for="enable-title" class="form-check-label fw-semibold">
|
||||
<i class="fas fa-id-badge"></i> Cargo/Título
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" id="title-group" style="display: none;">
|
||||
<input type="text" id="vcard-title" class="form-control" placeholder="CEO, Gerente, Desenvolvedor, etc.">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Website -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="enable-website" class="form-check-input">
|
||||
<label for="enable-website" class="form-check-label fw-semibold">
|
||||
<i class="fas fa-globe"></i> Website
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" id="website-group" style="display: none;">
|
||||
<input type="url" id="vcard-website" class="form-control" placeholder="https://seusite.com">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Telefone Fixo -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="enable-phone" class="form-check-input">
|
||||
<label for="enable-phone" class="form-check-label fw-semibold">
|
||||
<i class="fas fa-phone"></i> Telefone Fixo
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" id="phone-group" style="display: none;">
|
||||
<input type="tel" id="vcard-phone" class="form-control" placeholder="1133334444">
|
||||
<small class="form-text text-muted">Apenas números (DDD + número)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endereço -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="enable-address" class="form-check-input">
|
||||
<label for="enable-address" class="form-check-label fw-semibold">
|
||||
<i class="fas fa-map-marker-alt"></i> Endereço
|
||||
</label>
|
||||
</div>
|
||||
<div id="address-group" style="display: none;">
|
||||
<div class="form-group mb-2">
|
||||
<input type="text" id="vcard-address" class="form-control" placeholder="Rua, número - Ex: Rua das Flores, 123">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input type="text" id="vcard-city" class="form-control" placeholder="Cidade">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" id="vcard-state" class="form-control" placeholder="Estado">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" id="vcard-zip" class="form-control" placeholder="CEP">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview do VCard -->
|
||||
<div class="vcard-preview mb-3">
|
||||
<h6 class="fw-bold text-success mb-2">
|
||||
<i class="fas fa-eye"></i> Preview do Cartão
|
||||
</h6>
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<pre id="vcard-preview-text" class="mb-0 small text-muted">BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
Preencha os campos acima para ver o preview...
|
||||
END:VCARD</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced customization (collapsible) -->
|
||||
<div class="accordion mb-3 qr-flow-disabled" id="customization-accordion">
|
||||
<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> @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">@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">@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">@Localizer["Size"]</label>
|
||||
<select id="qr-size" class="form-select">
|
||||
<option value="200">@Localizer["SmallSize200px"]</option>
|
||||
<option value="300" selected>@Localizer["MediumSize300px"]</option>
|
||||
<option value="500">@Localizer["LargeSize500px"]</option>
|
||||
<option value="800">@Localizer["XLSize800px"]</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">@Localizer["Margin"]</label>
|
||||
<select id="qr-margin" class="form-select">
|
||||
<option value="1">@Localizer["Minimal"]</option>
|
||||
<option value="2" selected>@Localizer["Normal"]</option>
|
||||
<option value="4">@Localizer["Large"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<div class="row">
|
||||
@{
|
||||
var userService = Context.RequestServices.GetService<QRRapidoApp.Services.IUserService>();
|
||||
var currentUserId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
var currentUser = currentUserId != null ? await userService.GetUserAsync(currentUserId) : null;
|
||||
var isPremium = currentUser?.IsPremium == true;
|
||||
}
|
||||
|
||||
@if (isPremium)
|
||||
{
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">
|
||||
@Localizer["LogoIcon"]
|
||||
<span class="badge bg-warning text-dark ms-1">Premium</span>
|
||||
</label>
|
||||
<input type="file" id="logo-upload" class="form-control" accept="image/png,image/jpeg,image/jpg">
|
||||
<div class="form-text">@Localizer["PNGJPGUp2MB"]</div>
|
||||
<div id="logo-preview" class="mt-2 d-none">
|
||||
<small class="text-success">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span id="logo-filename"></span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="premium-upgrade-box p-3 border rounded bg-light">
|
||||
<h6 class="fw-bold mb-2">
|
||||
<i class="fas fa-crown text-warning"></i>
|
||||
Logo Personalizado - Premium
|
||||
</h6>
|
||||
<p class="mb-2 small">Adicione sua marca aos QR Codes! Upgrade para Premium e personalize com seu logo.</p>
|
||||
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning btn-sm">
|
||||
<i class="fas fa-arrow-up"></i> Fazer Upgrade
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">@Localizer["BorderStyle"]</label>
|
||||
<select id="corner-style" class="form-select @(isPremium ? "" : "border-warning")">
|
||||
<option value="square">@Localizer["Square"] (Grátis)</option>
|
||||
@if (isPremium)
|
||||
{
|
||||
<option value="rounded">@Localizer["Rounded"] 👑</option>
|
||||
<option value="circle">Círculos 👑</option>
|
||||
<option value="leaf">Folha 👑</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="rounded" disabled>@Localizer["Rounded"] - Premium 👑</option>
|
||||
<option value="circle" disabled>Círculos - Premium 👑</option>
|
||||
<option value="leaf" disabled>Folha - Premium 👑</option>
|
||||
}
|
||||
</select>
|
||||
@if (!isPremium)
|
||||
{
|
||||
<div class="form-text text-warning">
|
||||
<i class="fas fa-crown"></i>
|
||||
<a href="/Pagamento/SelecaoPlano" class="text-warning">Upgrade Premium</a> para estilos personalizados
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid" id="generate-button-container" style="display: none;">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn" disabled>
|
||||
<i class="fas fa-bolt"></i> @Localizer["GenerateQRCodeQuickly"]
|
||||
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status">
|
||||
<span class="visually-hidden">@Localizer["Generating"]</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Speed statistics -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center border-success">
|
||||
<div class="card-body">
|
||||
<h5 class="text-success">
|
||||
<i class="fas fa-stopwatch"></i> 1.2s
|
||||
</h5>
|
||||
<small class="text-muted">@Localizer["AverageTime"]</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center border-primary">
|
||||
<div class="card-body">
|
||||
<h5 class="text-primary">
|
||||
<i class="fas fa-chart-line"></i> 99.9%
|
||||
</h5>
|
||||
<small class="text-muted">@Localizer["Availability"]</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center border-warning">
|
||||
<div class="card-body">
|
||||
<h5 class="text-warning">
|
||||
<i class="fas fa-users"></i> <span id="total-qrs">10.5K</span>
|
||||
</h5>
|
||||
<small class="text-muted">@Localizer["QRsGeneratedToday"]</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ad Space Between Content (conditional) -->
|
||||
@await Html.PartialAsync("_AdSpace", new { position = "content" })
|
||||
</div>
|
||||
|
||||
<!-- Sidebar with preview and ads -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Preview with timer -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-eye"></i> Preview
|
||||
</h5>
|
||||
<div class="generation-stats d-none">
|
||||
<small class="text-success">
|
||||
<i class="fas fa-check-circle"></i> Gerado em <span class="generation-time">0s</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div id="qr-preview" class="mb-3">
|
||||
<div class="placeholder-qr p-5">
|
||||
<i class="fas fa-qrcode fa-4x text-muted mb-3"></i>
|
||||
<p class="text-muted">@Localizer["YourQRCodeWillAppear"]</p>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-bolt"></i> @Localizer["UltraFastGenerationGuaranteed"]
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="download-section" style="display: none;">
|
||||
<div class="btn-group-vertical w-100 mb-3">
|
||||
<button id="download-png" class="btn btn-success">
|
||||
<i class="fas fa-download"></i> @Localizer["DownloadPNG"]
|
||||
</button>
|
||||
<button id="download-svg" class="btn btn-outline-success">
|
||||
<i class="fas fa-vector-square"></i> @Localizer["DownloadSVGVector"]
|
||||
</button>
|
||||
<button id="download-pdf" class="btn btn-outline-success">
|
||||
<i class="fas fa-file-pdf"></i> @Localizer["DownloadPDF"]
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Share Button with Dropdown -->
|
||||
<div class="dropdown w-100 mb-3">
|
||||
<button class="btn btn-primary dropdown-toggle w-100" type="button" id="share-qr-btn" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-share-alt"></i> @Localizer["ShareQRCode"]
|
||||
</button>
|
||||
<ul class="dropdown-menu w-100" aria-labelledby="share-qr-btn" id="share-dropdown">
|
||||
<!-- Native share option (mobile only) -->
|
||||
<li class="d-none" id="native-share-option">
|
||||
<a class="dropdown-item" href="#" id="native-share">
|
||||
<i class="fas fa-mobile-alt text-primary"></i> @Localizer["ShareSystem"]
|
||||
</a>
|
||||
</li>
|
||||
<!-- WhatsApp -->
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" id="share-whatsapp">
|
||||
<i class="fab fa-whatsapp text-success"></i> WhatsApp
|
||||
</a>
|
||||
</li>
|
||||
<!-- Telegram -->
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" id="share-telegram">
|
||||
<i class="fab fa-telegram text-info"></i> Telegram
|
||||
</a>
|
||||
</li>
|
||||
<!-- Email -->
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" id="share-email">
|
||||
<i class="fas fa-envelope text-warning"></i> Email
|
||||
</a>
|
||||
</li>
|
||||
<!-- Copy to clipboard -->
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" id="copy-qr-link">
|
||||
<i class="fas fa-copy text-secondary"></i> Copiar Link
|
||||
</a>
|
||||
</li>
|
||||
<!-- Save to gallery (mobile only) -->
|
||||
<li class="d-none" id="save-gallery-option">
|
||||
<a class="dropdown-item" href="#" id="save-to-gallery">
|
||||
<i class="fas fa-images text-purple"></i> Salvar na Galeria
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<button id="save-to-history" class="btn btn-outline-primary w-100">
|
||||
<i class="fas fa-save"></i> @Localizer["SaveToHistory"]
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<small class="text-muted">
|
||||
<a href="/Account/Login" class="text-primary">@Localizer["Login"]</a>
|
||||
@Localizer["ToSaveToHistory"]
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Premium Card for non-premium users -->
|
||||
@if (User.Identity.IsAuthenticated && await AdService.ShouldShowAds(userId))
|
||||
{
|
||||
<div class="card border-warning mb-4">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-rocket"></i> QR Rapido Premium
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="text-center mb-3">
|
||||
<div class="badge bg-success mb-2">@Localizer["ThreeTimesFaster"]</div>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["NoAdsForever"]</li>
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["UnlimitedQRCodes"]</li>
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["PriorityGeneration"]</li>
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["DynamicQRCodes"]</li>
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["RealTimeAnalytics"]</li>
|
||||
<li><i class="fas fa-check text-success"></i> @Localizer["DeveloperAPI"]</li>
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
<a href="/Premium/Upgrade" class="btn btn-warning w-100">
|
||||
<i class="fas fa-bolt"></i> @Localizer["AcceleratePrice"]
|
||||
</a>
|
||||
<small class="text-muted d-block mt-1">@Localizer["CancelAnytime"]</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Speed Tips Card -->
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-lightbulb text-warning"></i> @Localizer["TipsFasterQR"]
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled small">
|
||||
<li><i class="fas fa-arrow-right text-primary"></i> @Localizer["ShortURLsFaster"]</li>
|
||||
<li><i class="fas fa-arrow-right text-primary"></i> @Localizer["LessTextMoreSpeed"]</li>
|
||||
<li><i class="fas fa-arrow-right text-primary"></i> @Localizer["SolidColorsOptimize"]</li>
|
||||
<li><i class="fas fa-arrow-right text-primary"></i> @Localizer["SmallerSizesAccelerate"]</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ad Space Sidebar (conditional) -->
|
||||
@await Html.PartialAsync("_AdSpace", new { position = "sidebar" })
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Speed Comparison Section -->
|
||||
<section class="mt-5 mb-4">
|
||||
<div class="container">
|
||||
<div class="text-center mb-4">
|
||||
<h3><i class="fas fa-tachometer-alt text-primary"></i> @Localizer["WhyQRRapidoFaster"]</h3>
|
||||
<p class="text-muted">@Localizer["ComparisonOtherGenerators"]</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="text-success">QR Rapido</h5>
|
||||
<div class="display-4 text-success fw-bold">1.2s</div>
|
||||
<p class="text-muted">@Localizer["OptimizedForSpeed"]</p>
|
||||
<i class="fas fa-crown text-warning"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="text-muted">@Localizer["CompetitorA"]</h5>
|
||||
<div class="display-4 text-muted">3.5s</div>
|
||||
<p class="text-muted">@Localizer["TraditionalGenerator"]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="text-muted">@Localizer["CompetitorB"]</h5>
|
||||
<div class="display-4 text-muted">4.8s</div>
|
||||
<p class="text-muted">@Localizer["HeavyInterface"]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="text-muted">@Localizer["CompetitorC"]</h5>
|
||||
<div class="display-4 text-muted">6.2s</div>
|
||||
<p class="text-muted">@Localizer["ManyAds"]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Botão de ajuda flutuante -->
|
||||
<button type="button" class="help-button-floating" id="help-button" title="Guia de Preenchimento">
|
||||
<i class="fas fa-question"></i>
|
||||
</button>
|
||||
|
||||
<!-- Container para toasts do onboarding -->
|
||||
<div class="toast-container-onboarding" id="toast-container"></div>
|
||||
|
||||
|
||||
<!-- Scripts do onboarding -->
|
||||
<script src="~/js/qr-onboarding.js"></script>
|
||||
|
||||
<!-- Ad Space Footer (conditional) -->
|
||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||
@ -376,6 +376,334 @@ footer a:hover {
|
||||
border-image: linear-gradient(135deg, var(--qr-primary) 0%, var(--qr-accent) 100%) 1;
|
||||
}
|
||||
|
||||
/* =================================
|
||||
FLUXO ASCENDENTE - ESTADOS VISUAIS PROGRESSIVOS
|
||||
================================= */
|
||||
|
||||
/* Estados desabilitados - cor e opacidade reduzida */
|
||||
.qr-flow-disabled {
|
||||
opacity: 0.6 !important;
|
||||
pointer-events: none !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.qr-flow-disabled .form-control,
|
||||
.qr-flow-disabled .form-select {
|
||||
background-color: #f5f5f5 !important;
|
||||
border-color: #e0e0e0 !important;
|
||||
color: #999999 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.qr-flow-disabled .form-label {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
.qr-flow-disabled .btn-group .btn {
|
||||
background-color: #f5f5f5 !important;
|
||||
border-color: #e0e0e0 !important;
|
||||
color: #999999 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Estados habilitados - transição suave */
|
||||
.qr-flow-enabled {
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.qr-flow-enabled .form-control,
|
||||
.qr-flow-enabled .form-select {
|
||||
background-color: #ffffff !important;
|
||||
border-color: #ced4da !important;
|
||||
color: #495057 !important;
|
||||
cursor: auto !important;
|
||||
}
|
||||
|
||||
.qr-flow-enabled .form-label {
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
.qr-flow-enabled .btn-group .btn {
|
||||
background-color: #ffffff !important;
|
||||
border-color: #6c757d !important;
|
||||
color: #6c757d !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* Estados para o tema escuro */
|
||||
html[data-theme="dark"] .qr-flow-disabled .form-control,
|
||||
html[data-theme="dark"] .qr-flow-disabled .form-select {
|
||||
background-color: #3a3a3a !important;
|
||||
border-color: #555555 !important;
|
||||
color: #777777 !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .qr-flow-disabled .form-label {
|
||||
color: #777777 !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .qr-flow-disabled .btn-group .btn {
|
||||
background-color: #3a3a3a !important;
|
||||
border-color: #555555 !important;
|
||||
color: #777777 !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .qr-flow-enabled .form-control,
|
||||
html[data-theme="dark"] .qr-flow-enabled .form-select {
|
||||
background-color: #4a5568 !important;
|
||||
border-color: #718096 !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .qr-flow-enabled .form-label {
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .qr-flow-enabled .btn-group .btn {
|
||||
background-color: #4a5568 !important;
|
||||
border-color: #718096 !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* Animação de ascensão */
|
||||
@keyframes ascend {
|
||||
from {
|
||||
opacity: 0.6;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.qr-flow-ascending {
|
||||
animation: ascend 0.4s ease-out forwards !important;
|
||||
}
|
||||
|
||||
/* Botão de geração - estado oculto inicial */
|
||||
#generate-button-container {
|
||||
transition: all 0.4s ease !important;
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#generate-button-container.show {
|
||||
display: block !important;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
animation: ascend 0.4s ease-out forwards;
|
||||
}
|
||||
|
||||
/* =================================
|
||||
DESTAQUE ELEGANTE PARA CAMPO INICIAL
|
||||
================================= */
|
||||
|
||||
/* Animação pulsante elegante */
|
||||
@keyframes pulseHighlight {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
}
|
||||
|
||||
/* Classe para destacar campo inicial */
|
||||
.qr-field-highlight {
|
||||
border: 2px solid #3B82F6 !important;
|
||||
box-shadow: 0 0 5px rgba(59, 130, 246, 0.3) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
/* Remover destaque suavemente */
|
||||
.qr-field-highlight.removing {
|
||||
animation: none !important;
|
||||
border-color: #ced4da !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Ícone de seta animado */
|
||||
@keyframes chevronBounce {
|
||||
0%, 20%, 50%, 80%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
60% {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.chevron-animated {
|
||||
animation: chevronBounce 2s infinite;
|
||||
color: #3B82F6;
|
||||
margin-left: 8px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Tooltip para campos bloqueados */
|
||||
.blocked-field-tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blocked-field-tooltip::after {
|
||||
content: "Selecione primeiro o Tipo de QR Code";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(33, 37, 41, 0.9);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 1000;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.blocked-field-tooltip::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(33, 37, 41, 0.9);
|
||||
margin-bottom: -5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.blocked-field-tooltip:hover::after,
|
||||
.blocked-field-tooltip:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Botão de ajuda flutuante */
|
||||
.help-button-floating {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1050;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #3B82F6, #6366F1);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.help-button-floating:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.6);
|
||||
background: linear-gradient(135deg, #2563EB, #4F46E5);
|
||||
}
|
||||
|
||||
.help-button-floating:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Seta apontando para o select */
|
||||
.pointer-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 15px solid transparent;
|
||||
border-right: 15px solid transparent;
|
||||
border-bottom: 20px solid #3B82F6;
|
||||
top: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
animation: arrowBounce 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes arrowBounce {
|
||||
0%, 100% {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-50%) translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Toasts customizados */
|
||||
.toast-onboarding {
|
||||
background: linear-gradient(135deg, #3B82F6, #6366F1);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast-onboarding .toast-header {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.toast-onboarding .toast-body {
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.toast-onboarding .btn-close {
|
||||
filter: invert(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.toast-onboarding .btn-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Container de toasts personalizado */
|
||||
.toast-container-onboarding {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1055;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
/* Tema escuro - ajustes para destaque */
|
||||
html[data-theme="dark"] .qr-field-highlight {
|
||||
border-color: #60A5FA !important;
|
||||
box-shadow: 0 0 5px rgba(96, 165, 250, 0.3) !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .chevron-animated {
|
||||
color: #60A5FA;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .blocked-field-tooltip::after {
|
||||
background: rgba(248, 250, 252, 0.95);
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .blocked-field-tooltip::before {
|
||||
border-top-color: rgba(248, 250, 252, 0.95);
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.ad-container,
|
||||
|
||||
@ -4,6 +4,9 @@ class QRRapidoGenerator {
|
||||
this.startTime = 0;
|
||||
this.currentQR = null;
|
||||
this.timerInterval = null;
|
||||
this.selectedType = null;
|
||||
this.selectedStyle = null;
|
||||
this.contentValid = false;
|
||||
this.languageStrings = {
|
||||
'pt-BR': {
|
||||
tagline: 'Gere QR codes em segundos!',
|
||||
@ -45,6 +48,7 @@ class QRRapidoGenerator {
|
||||
this.checkAdFreeStatus();
|
||||
this.updateLanguage();
|
||||
this.updateStatsCounters();
|
||||
this.initializeProgressiveFlow();
|
||||
}
|
||||
|
||||
initializeEvents() {
|
||||
@ -54,10 +58,21 @@ class QRRapidoGenerator {
|
||||
form.addEventListener('submit', this.generateQRWithTimer.bind(this));
|
||||
}
|
||||
|
||||
// Quick style selection
|
||||
// Quick style selection with flow control
|
||||
document.querySelectorAll('input[name="quick-style"]').forEach(radio => {
|
||||
radio.addEventListener('change', this.applyQuickStyle.bind(this));
|
||||
radio.addEventListener('change', (e) => {
|
||||
this.handleStyleSelection(e.target.value);
|
||||
this.applyQuickStyle(e);
|
||||
});
|
||||
});
|
||||
|
||||
// Content field monitoring
|
||||
const contentField = document.getElementById('qr-content');
|
||||
if (contentField) {
|
||||
contentField.addEventListener('input', (e) => {
|
||||
this.handleContentChange(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Logo upload feedback
|
||||
const logoUpload = document.getElementById('logo-upload');
|
||||
@ -71,10 +86,13 @@ class QRRapidoGenerator {
|
||||
cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this));
|
||||
}
|
||||
|
||||
// QR type change with hints
|
||||
// QR type change with hints and flow control
|
||||
const qrType = document.getElementById('qr-type');
|
||||
if (qrType) {
|
||||
qrType.addEventListener('change', this.updateContentHints.bind(this));
|
||||
qrType.addEventListener('change', (e) => {
|
||||
this.handleTypeSelection(e.target.value);
|
||||
this.updateContentHints();
|
||||
});
|
||||
}
|
||||
|
||||
// Language selector
|
||||
@ -98,6 +116,7 @@ class QRRapidoGenerator {
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setupDownloadButtons() {
|
||||
@ -373,6 +392,12 @@ class QRRapidoGenerator {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if flow is properly completed
|
||||
if (!this.selectedType || !this.selectedStyle || !this.contentValid) {
|
||||
this.showError('Complete todas as etapas antes de gerar o QR Code');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special validation for VCard
|
||||
if (qrType === 'vcard') {
|
||||
try {
|
||||
@ -652,7 +677,11 @@ class QRRapidoGenerator {
|
||||
|
||||
// Show/hide VCard interface based on type
|
||||
if (type === 'vcard') {
|
||||
if (vcardInterface) vcardInterface.style.display = 'block';
|
||||
if (vcardInterface) {
|
||||
vcardInterface.style.display = 'block';
|
||||
// Add VCard input monitoring for progressive flow
|
||||
this.setupVCardMonitoring();
|
||||
}
|
||||
if (contentTextarea) {
|
||||
contentTextarea.style.display = 'none';
|
||||
contentTextarea.removeAttribute('required');
|
||||
@ -689,6 +718,30 @@ class QRRapidoGenerator {
|
||||
const langHints = hints[this.currentLang] || hints['pt-BR'];
|
||||
hintsElement.textContent = langHints[type] || 'Digite o conteúdo apropriado para o tipo selecionado';
|
||||
}
|
||||
|
||||
setupVCardMonitoring() {
|
||||
// Monitor VCard required fields for progressive flow
|
||||
const requiredFields = ['vcard-name', 'vcard-mobile', 'vcard-email'];
|
||||
|
||||
requiredFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.addEventListener('input', () => {
|
||||
// Use a small delay to allow for validation
|
||||
setTimeout(() => {
|
||||
const isValid = this.validateContent(''); // VCard validation is internal
|
||||
this.contentValid = isValid;
|
||||
|
||||
if (isValid && this.selectedType && this.selectedStyle) {
|
||||
this.showGenerateButton();
|
||||
} else {
|
||||
this.hideGenerateButton();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeLanguage(e) {
|
||||
e.preventDefault();
|
||||
@ -1085,6 +1138,281 @@ class QRRapidoGenerator {
|
||||
}, type === 'success' ? 3000 : 5000);
|
||||
}
|
||||
|
||||
initializeProgressiveFlow() {
|
||||
// Reset all states to initial disabled state
|
||||
this.selectedType = null;
|
||||
this.selectedStyle = null;
|
||||
this.contentValid = false;
|
||||
|
||||
// Ensure proper initial state
|
||||
this.disableStyleSelection();
|
||||
this.disableContentFields();
|
||||
this.disableAdvancedOptions();
|
||||
this.hideGenerateButton();
|
||||
}
|
||||
|
||||
handleTypeSelection(type) {
|
||||
this.selectedType = type;
|
||||
|
||||
if (type) {
|
||||
// Remove highlight when type is selected
|
||||
this.removeInitialHighlight();
|
||||
|
||||
// Enable style selection
|
||||
this.enableStyleSelection();
|
||||
// Reset subsequent selections
|
||||
this.selectedStyle = null;
|
||||
this.contentValid = false;
|
||||
this.disableContentFields();
|
||||
this.hideGenerateButton();
|
||||
|
||||
} else {
|
||||
// Disable everything if no type selected
|
||||
this.disableStyleSelection();
|
||||
this.disableContentFields();
|
||||
this.disableAdvancedOptions();
|
||||
this.hideGenerateButton();
|
||||
}
|
||||
}
|
||||
|
||||
handleStyleSelection(style) {
|
||||
this.selectedStyle = style;
|
||||
|
||||
if (style && this.selectedType) {
|
||||
// Enable content fields
|
||||
this.enableContentFields();
|
||||
|
||||
// Open advanced panel if 'advanced' style is selected (future enhancement)
|
||||
if (style === 'advanced') {
|
||||
this.openAdvancedCustomization();
|
||||
}
|
||||
|
||||
// Reset content validation
|
||||
this.contentValid = false;
|
||||
this.hideGenerateButton();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
handleContentChange(content) {
|
||||
const isValid = this.validateContent(content);
|
||||
this.contentValid = isValid;
|
||||
|
||||
if (isValid && this.selectedType && this.selectedStyle) {
|
||||
this.showGenerateButton();
|
||||
|
||||
} else {
|
||||
this.hideGenerateButton();
|
||||
}
|
||||
}
|
||||
|
||||
validateContent(content) {
|
||||
if (!content) return false;
|
||||
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
// VCard has its own validation
|
||||
if (this.selectedType === 'vcard') {
|
||||
try {
|
||||
if (window.vcardGenerator) {
|
||||
const errors = window.vcardGenerator.validateVCardData();
|
||||
return errors.length === 0;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// General validation: at least 3 characters
|
||||
return trimmedContent.length >= 3;
|
||||
}
|
||||
|
||||
enableStyleSelection() {
|
||||
const styleContainer = document.getElementById('style-selector');
|
||||
const styleInputs = document.querySelectorAll('input[name="quick-style"]');
|
||||
const styleLabel = styleContainer?.parentElement.querySelector('.form-label');
|
||||
|
||||
if (styleContainer) {
|
||||
styleContainer.classList.remove('qr-flow-disabled');
|
||||
styleContainer.classList.add('qr-flow-enabled', 'qr-flow-ascending');
|
||||
}
|
||||
|
||||
if (styleLabel) {
|
||||
styleLabel.classList.remove('qr-flow-disabled');
|
||||
styleLabel.classList.add('qr-flow-enabled');
|
||||
}
|
||||
|
||||
styleInputs.forEach(input => {
|
||||
input.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
disableStyleSelection() {
|
||||
const styleContainer = document.getElementById('style-selector');
|
||||
const styleInputs = document.querySelectorAll('input[name="quick-style"]');
|
||||
const styleLabel = styleContainer?.parentElement.querySelector('.form-label');
|
||||
|
||||
if (styleContainer) {
|
||||
styleContainer.classList.remove('qr-flow-enabled', 'qr-flow-ascending');
|
||||
styleContainer.classList.add('qr-flow-disabled');
|
||||
}
|
||||
|
||||
if (styleLabel) {
|
||||
styleLabel.classList.remove('qr-flow-enabled');
|
||||
styleLabel.classList.add('qr-flow-disabled');
|
||||
}
|
||||
|
||||
styleInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
input.checked = false;
|
||||
});
|
||||
|
||||
// Reset to default selection
|
||||
const defaultStyle = document.getElementById('style-classic');
|
||||
if (defaultStyle) {
|
||||
defaultStyle.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
enableContentFields() {
|
||||
const contentTextarea = document.getElementById('qr-content');
|
||||
const contentLabel = contentTextarea?.parentElement.querySelector('.form-label');
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
|
||||
if (contentTextarea) {
|
||||
contentTextarea.classList.remove('qr-flow-disabled');
|
||||
contentTextarea.classList.add('qr-flow-enabled', 'qr-flow-ascending');
|
||||
contentTextarea.disabled = false;
|
||||
}
|
||||
|
||||
if (contentLabel) {
|
||||
contentLabel.classList.remove('qr-flow-disabled');
|
||||
contentLabel.classList.add('qr-flow-enabled');
|
||||
}
|
||||
|
||||
if (vcardInterface && this.selectedType === 'vcard') {
|
||||
vcardInterface.classList.remove('qr-flow-disabled');
|
||||
vcardInterface.classList.add('qr-flow-enabled', 'qr-flow-ascending');
|
||||
|
||||
// Enable VCard form fields
|
||||
const vcardInputs = vcardInterface.querySelectorAll('input, textarea, select');
|
||||
vcardInputs.forEach(input => {
|
||||
input.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Enable advanced customization
|
||||
this.enableAdvancedOptions();
|
||||
}
|
||||
|
||||
disableContentFields() {
|
||||
const contentTextarea = document.getElementById('qr-content');
|
||||
const contentLabel = contentTextarea?.parentElement.querySelector('.form-label');
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
|
||||
if (contentTextarea) {
|
||||
contentTextarea.classList.remove('qr-flow-enabled', 'qr-flow-ascending');
|
||||
contentTextarea.classList.add('qr-flow-disabled');
|
||||
contentTextarea.disabled = true;
|
||||
contentTextarea.value = '';
|
||||
}
|
||||
|
||||
if (contentLabel) {
|
||||
contentLabel.classList.remove('qr-flow-enabled');
|
||||
contentLabel.classList.add('qr-flow-disabled');
|
||||
}
|
||||
|
||||
if (vcardInterface) {
|
||||
vcardInterface.classList.remove('qr-flow-enabled', 'qr-flow-ascending');
|
||||
vcardInterface.classList.add('qr-flow-disabled');
|
||||
|
||||
// Disable VCard form fields
|
||||
const vcardInputs = vcardInterface.querySelectorAll('input, textarea, select');
|
||||
vcardInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
input.value = '';
|
||||
input.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
this.disableAdvancedOptions();
|
||||
}
|
||||
|
||||
enableAdvancedOptions() {
|
||||
const advancedAccordion = document.getElementById('customization-accordion');
|
||||
|
||||
if (advancedAccordion) {
|
||||
advancedAccordion.classList.remove('qr-flow-disabled');
|
||||
advancedAccordion.classList.add('qr-flow-enabled', 'qr-flow-ascending');
|
||||
|
||||
// Enable form controls inside accordion
|
||||
const advancedInputs = advancedAccordion.querySelectorAll('input, select');
|
||||
advancedInputs.forEach(input => {
|
||||
input.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disableAdvancedOptions() {
|
||||
const advancedAccordion = document.getElementById('customization-accordion');
|
||||
|
||||
if (advancedAccordion) {
|
||||
advancedAccordion.classList.remove('qr-flow-enabled', 'qr-flow-ascending');
|
||||
advancedAccordion.classList.add('qr-flow-disabled');
|
||||
|
||||
// Disable form controls inside accordion
|
||||
const advancedInputs = advancedAccordion.querySelectorAll('input, select');
|
||||
advancedInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showGenerateButton() {
|
||||
const buttonContainer = document.getElementById('generate-button-container');
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
|
||||
if (buttonContainer) {
|
||||
buttonContainer.style.display = 'block';
|
||||
buttonContainer.classList.add('show');
|
||||
}
|
||||
|
||||
if (generateBtn) {
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
hideGenerateButton() {
|
||||
const buttonContainer = document.getElementById('generate-button-container');
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
|
||||
if (buttonContainer) {
|
||||
buttonContainer.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
if (!buttonContainer.classList.contains('show')) {
|
||||
buttonContainer.style.display = 'none';
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
if (generateBtn) {
|
||||
generateBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
openAdvancedCustomization() {
|
||||
const advancedPanel = document.getElementById('customization-panel');
|
||||
const accordionButton = document.querySelector('[data-bs-target="#customization-panel"]');
|
||||
|
||||
if (advancedPanel && accordionButton) {
|
||||
// Use Bootstrap's collapse to open the panel
|
||||
const collapse = new bootstrap.Collapse(advancedPanel, {
|
||||
show: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupRealTimePreview() {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const typeField = document.getElementById('qr-type');
|
||||
@ -1105,6 +1433,14 @@ class QRRapidoGenerator {
|
||||
typeField.addEventListener('change', updatePreview);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove destaque inicial quando tipo for selecionado
|
||||
removeInitialHighlight() {
|
||||
const typeField = document.getElementById('qr-type');
|
||||
if (typeField && typeField.classList.contains('qr-field-highlight')) {
|
||||
typeField.classList.remove('qr-field-highlight');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM loads
|
||||
|
||||
Loading…
Reference in New Issue
Block a user