Compare commits
No commits in common. "e8009002033dfbf247a66c5bbfe62b7e1e53a675" and "00d924ce3bdf79b52cc64187a8bc28c87f0dd836" have entirely different histories.
e800900203
...
00d924ce3b
@ -7,13 +7,7 @@
|
|||||||
"Bash(timeout:*)",
|
"Bash(timeout:*)",
|
||||||
"Bash(rm:*)",
|
"Bash(rm:*)",
|
||||||
"Bash(dotnet run:*)",
|
"Bash(dotnet run:*)",
|
||||||
"Bash(curl:*)",
|
"Bash(curl:*)"
|
||||||
"Bash(pkill:*)",
|
|
||||||
"Bash(true)",
|
|
||||||
"Bash(node:*)",
|
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(mv:*)",
|
|
||||||
"Bash(sed:*)"
|
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,11 +68,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3" id="qr-type-container">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-list"></i> @Localizer["QRCodeType"]
|
<i class="fas fa-list"></i> @Localizer["QRCodeType"]
|
||||||
</label>
|
</label>
|
||||||
<select id="qr-type" class="form-select qr-field-highlight" required>
|
<select id="qr-type" class="form-select" required>
|
||||||
<option value="">@Localizer["SelectType"]</option>
|
<option value="">@Localizer["SelectType"]</option>
|
||||||
<option value="url">🌐 @Localizer["URLLink"]</option>
|
<option value="url">🌐 @Localizer["URLLink"]</option>
|
||||||
<option value="text">📝 @Localizer["SimpleText"]</option>
|
<option value="text">📝 @Localizer["SimpleText"]</option>
|
||||||
@ -87,178 +87,38 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold qr-flow-disabled">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
|
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
|
||||||
</label>
|
</label>
|
||||||
<div class="btn-group w-100 qr-flow-disabled" role="group" id="style-selector">
|
<div class="btn-group w-100" role="group">
|
||||||
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked disabled>
|
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked>
|
||||||
<label class="btn btn-outline-secondary" for="style-classic">@Localizer["Classic"]</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" disabled>
|
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern">
|
||||||
<label class="btn btn-outline-secondary" for="style-modern">@Localizer["Modern"]</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" disabled>
|
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful">
|
||||||
<label class="btn btn-outline-secondary" for="style-colorful">@Localizer["Colorful"]</label>
|
<label class="btn btn-outline-secondary" for="style-colorful">@Localizer["Colorful"]</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold qr-flow-disabled">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-edit"></i> @Localizer["Content"]
|
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||||
</label>
|
</label>
|
||||||
<textarea id="qr-content"
|
<textarea id="qr-content"
|
||||||
class="form-control form-control-lg qr-flow-disabled"
|
class="form-control form-control-lg"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="@Localizer["EnterQRCodeContent"]"
|
placeholder="@Localizer["EnterQRCodeContent"]"
|
||||||
required disabled></textarea>
|
required></textarea>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<span id="content-hints">@Localizer["ContentHints"]</span>
|
<span id="content-hints">@Localizer["ContentHints"]</span>
|
||||||
</div>
|
</div>
|
||||||
</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) - Escolha abaixo
|
|
||||||
</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) -->
|
<!-- Advanced customization (collapsible) -->
|
||||||
<div class="accordion mb-3 qr-flow-disabled" id="customization-accordion">
|
<div class="accordion mb-3" id="customization-accordion">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
||||||
@ -370,8 +230,8 @@ END:VCARD</pre>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-grid" id="generate-button-container" style="display: none;">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn" disabled>
|
<button type="submit" class="btn btn-primary btn-lg" id="generate-btn">
|
||||||
<i class="fas fa-bolt"></i> @Localizer["GenerateQRCodeQuickly"]
|
<i class="fas fa-bolt"></i> @Localizer["GenerateQRCodeQuickly"]
|
||||||
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status">
|
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status">
|
||||||
<span class="visually-hidden">@Localizer["Generating"]</span>
|
<span class="visually-hidden">@Localizer["Generating"]</span>
|
||||||
@ -626,6 +486,5 @@ END:VCARD</pre>
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<!-- Ad Space Footer (conditional) -->
|
<!-- Ad Space Footer (conditional) -->
|
||||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||||
@ -1,643 +0,0 @@
|
|||||||
@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,334 +376,6 @@ footer a:hover {
|
|||||||
border-image: linear-gradient(135deg, var(--qr-primary) 0%, var(--qr-accent) 100%) 1;
|
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 */
|
/* Print Styles */
|
||||||
@media print {
|
@media print {
|
||||||
.ad-container,
|
.ad-container,
|
||||||
|
|||||||
@ -4,9 +4,6 @@ class QRRapidoGenerator {
|
|||||||
this.startTime = 0;
|
this.startTime = 0;
|
||||||
this.currentQR = null;
|
this.currentQR = null;
|
||||||
this.timerInterval = null;
|
this.timerInterval = null;
|
||||||
this.selectedType = null;
|
|
||||||
this.selectedStyle = null;
|
|
||||||
this.contentValid = false;
|
|
||||||
this.languageStrings = {
|
this.languageStrings = {
|
||||||
'pt-BR': {
|
'pt-BR': {
|
||||||
tagline: 'Gere QR codes em segundos!',
|
tagline: 'Gere QR codes em segundos!',
|
||||||
@ -48,7 +45,6 @@ class QRRapidoGenerator {
|
|||||||
this.checkAdFreeStatus();
|
this.checkAdFreeStatus();
|
||||||
this.updateLanguage();
|
this.updateLanguage();
|
||||||
this.updateStatsCounters();
|
this.updateStatsCounters();
|
||||||
this.initializeProgressiveFlow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeEvents() {
|
initializeEvents() {
|
||||||
@ -58,21 +54,10 @@ class QRRapidoGenerator {
|
|||||||
form.addEventListener('submit', this.generateQRWithTimer.bind(this));
|
form.addEventListener('submit', this.generateQRWithTimer.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick style selection with flow control
|
// Quick style selection
|
||||||
document.querySelectorAll('input[name="quick-style"]').forEach(radio => {
|
document.querySelectorAll('input[name="quick-style"]').forEach(radio => {
|
||||||
radio.addEventListener('change', (e) => {
|
radio.addEventListener('change', this.applyQuickStyle.bind(this));
|
||||||
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
|
// Logo upload feedback
|
||||||
const logoUpload = document.getElementById('logo-upload');
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
@ -86,13 +71,10 @@ class QRRapidoGenerator {
|
|||||||
cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this));
|
cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// QR type change with hints and flow control
|
// QR type change with hints
|
||||||
const qrType = document.getElementById('qr-type');
|
const qrType = document.getElementById('qr-type');
|
||||||
if (qrType) {
|
if (qrType) {
|
||||||
qrType.addEventListener('change', (e) => {
|
qrType.addEventListener('change', this.updateContentHints.bind(this));
|
||||||
this.handleTypeSelection(e.target.value);
|
|
||||||
this.updateContentHints();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language selector
|
// Language selector
|
||||||
@ -116,7 +98,6 @@ class QRRapidoGenerator {
|
|||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDownloadButtons() {
|
setupDownloadButtons() {
|
||||||
@ -386,41 +367,13 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
validateForm() {
|
validateForm() {
|
||||||
const qrType = document.getElementById('qr-type').value;
|
const qrType = document.getElementById('qr-type').value;
|
||||||
|
const qrContent = document.getElementById('qr-content').value.trim();
|
||||||
|
|
||||||
if (!qrType) {
|
if (!qrType) {
|
||||||
this.showError('Selecione o tipo de QR code');
|
this.showError('Selecione o tipo de QR code');
|
||||||
return false;
|
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 {
|
|
||||||
if (window.vcardGenerator) {
|
|
||||||
const errors = window.vcardGenerator.validateVCardData();
|
|
||||||
if (errors.length > 0) {
|
|
||||||
this.showError(errors.join('<br>'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
this.showError('VCard generator não está disponível');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.showError('Erro na validação do VCard: ' + error.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal validation for other types
|
|
||||||
const qrContent = document.getElementById('qr-content').value.trim();
|
|
||||||
|
|
||||||
if (!qrContent) {
|
if (!qrContent) {
|
||||||
this.showError('Digite o conteúdo do QR code');
|
this.showError('Digite o conteúdo do QR code');
|
||||||
return false;
|
return false;
|
||||||
@ -435,35 +388,9 @@ class QRRapidoGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collectFormData() {
|
collectFormData() {
|
||||||
const type = document.getElementById('qr-type').value;
|
|
||||||
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
||||||
const styleSettings = this.getStyleSettings(quickStyle);
|
const styleSettings = this.getStyleSettings(quickStyle);
|
||||||
|
|
||||||
// Handle VCard type
|
|
||||||
if (type === 'vcard') {
|
|
||||||
if (window.vcardGenerator) {
|
|
||||||
const vcardContent = window.vcardGenerator.getVCardContent();
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
type: 'vcard', // Keep as vcard type for tracking
|
|
||||||
content: vcardContent,
|
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error('VCard generator não está disponível');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if logo is selected for premium users
|
// Check if logo is selected for premium users
|
||||||
const logoUpload = document.getElementById('logo-upload');
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0];
|
const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0];
|
||||||
@ -670,32 +597,8 @@ class QRRapidoGenerator {
|
|||||||
updateContentHints() {
|
updateContentHints() {
|
||||||
const type = document.getElementById('qr-type')?.value;
|
const type = document.getElementById('qr-type')?.value;
|
||||||
const hintsElement = document.getElementById('content-hints');
|
const hintsElement = document.getElementById('content-hints');
|
||||||
const vcardInterface = document.getElementById('vcard-interface');
|
|
||||||
const contentTextarea = document.getElementById('qr-content');
|
|
||||||
|
|
||||||
if (!hintsElement || !type) return;
|
if (!hintsElement || !type) return;
|
||||||
|
|
||||||
// Show/hide VCard interface based on type
|
|
||||||
if (type === 'vcard') {
|
|
||||||
if (vcardInterface) {
|
|
||||||
vcardInterface.style.display = 'block';
|
|
||||||
// Add VCard input monitoring for progressive flow
|
|
||||||
this.setupVCardMonitoring();
|
|
||||||
}
|
|
||||||
if (contentTextarea) {
|
|
||||||
contentTextarea.style.display = 'none';
|
|
||||||
contentTextarea.removeAttribute('required');
|
|
||||||
}
|
|
||||||
hintsElement.textContent = 'Preencha os campos acima para criar seu cartão de visita digital';
|
|
||||||
return; // Skip normal hints for VCard
|
|
||||||
} else {
|
|
||||||
if (vcardInterface) vcardInterface.style.display = 'none';
|
|
||||||
if (contentTextarea) {
|
|
||||||
contentTextarea.style.display = 'block';
|
|
||||||
contentTextarea.setAttribute('required', 'required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hints = {
|
const hints = {
|
||||||
'pt-BR': {
|
'pt-BR': {
|
||||||
'url': 'Ex: https://www.exemplo.com.br',
|
'url': 'Ex: https://www.exemplo.com.br',
|
||||||
@ -718,30 +621,6 @@ class QRRapidoGenerator {
|
|||||||
const langHints = hints[this.currentLang] || hints['pt-BR'];
|
const langHints = hints[this.currentLang] || hints['pt-BR'];
|
||||||
hintsElement.textContent = langHints[type] || 'Digite o conteúdo apropriado para o tipo selecionado';
|
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) {
|
changeLanguage(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -1120,7 +999,7 @@ class QRRapidoGenerator {
|
|||||||
const alert = document.createElement('div');
|
const alert = document.createElement('div');
|
||||||
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||||||
alert.innerHTML = `
|
alert.innerHTML = `
|
||||||
<div>${message}</div>
|
${message}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -1138,281 +1017,6 @@ class QRRapidoGenerator {
|
|||||||
}, type === 'success' ? 3000 : 5000);
|
}, 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() {
|
setupRealTimePreview() {
|
||||||
const contentField = document.getElementById('qr-content');
|
const contentField = document.getElementById('qr-content');
|
||||||
const typeField = document.getElementById('qr-type');
|
const typeField = document.getElementById('qr-type');
|
||||||
@ -1433,20 +1037,11 @@ class QRRapidoGenerator {
|
|||||||
typeField.addEventListener('change', updatePreview);
|
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
|
// Initialize when DOM loads
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
window.qrGenerator = new QRRapidoGenerator();
|
window.qrGenerator = new QRRapidoGenerator();
|
||||||
window.vcardGenerator = new VCardGenerator();
|
|
||||||
|
|
||||||
// Initialize AdSense if necessary
|
// Initialize AdSense if necessary
|
||||||
if (window.adsbygoogle && document.querySelector('.adsbygoogle')) {
|
if (window.adsbygoogle && document.querySelector('.adsbygoogle')) {
|
||||||
@ -1454,212 +1049,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// VCard Generator Class
|
|
||||||
class VCardGenerator {
|
|
||||||
constructor() {
|
|
||||||
this.initializeVCardInterface();
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeVCardInterface() {
|
|
||||||
// Show/hide optional fields based on checkboxes
|
|
||||||
document.querySelectorAll('#vcard-interface .form-check-input').forEach(checkbox => {
|
|
||||||
checkbox.addEventListener('change', (e) => {
|
|
||||||
const groupId = e.target.id.replace('enable-', '') + '-group';
|
|
||||||
const group = document.getElementById(groupId);
|
|
||||||
if (group) {
|
|
||||||
group.style.display = e.target.checked ? 'block' : 'none';
|
|
||||||
// Clear values when hiding fields
|
|
||||||
if (!e.target.checked) {
|
|
||||||
const inputs = group.querySelectorAll('input');
|
|
||||||
inputs.forEach(input => input.value = '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updatePreview();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update preview in real-time
|
|
||||||
document.querySelectorAll('#vcard-interface input').forEach(input => {
|
|
||||||
input.addEventListener('input', () => this.updatePreview());
|
|
||||||
input.addEventListener('blur', () => this.validateField(input));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePreview() {
|
|
||||||
const vcard = this.generateVCardContent();
|
|
||||||
const previewElement = document.getElementById('vcard-preview-text');
|
|
||||||
if (previewElement) {
|
|
||||||
previewElement.textContent = vcard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
generateVCardContent() {
|
|
||||||
const data = this.collectVCardData();
|
|
||||||
|
|
||||||
let vcard = 'BEGIN:VCARD\nVERSION:3.0\n';
|
|
||||||
|
|
||||||
// Nome (obrigatório)
|
|
||||||
if (data.name) {
|
|
||||||
const nameParts = data.name.trim().split(' ');
|
|
||||||
const firstName = nameParts[0] || '';
|
|
||||||
const lastName = nameParts.slice(1).join(' ') || '';
|
|
||||||
vcard += `N:${lastName};${firstName}\n`;
|
|
||||||
vcard += `FN:${data.name}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empresa
|
|
||||||
if (data.company) vcard += `ORG:${data.company}\n`;
|
|
||||||
|
|
||||||
// Título
|
|
||||||
if (data.title) vcard += `TITLE:${data.title}\n`;
|
|
||||||
|
|
||||||
// Endereço
|
|
||||||
if (data.address || data.city || data.state || data.zip) {
|
|
||||||
const addr = `;;${data.address || ''};${data.city || ''};${data.state || ''};${data.zip || ''};Brasil`;
|
|
||||||
vcard += `ADR:${addr}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Telefones
|
|
||||||
if (data.phone) vcard += `TEL;WORK;VOICE:${data.phone}\n`;
|
|
||||||
if (data.mobile) vcard += `TEL;CELL:${data.mobile}\n`;
|
|
||||||
|
|
||||||
// Email
|
|
||||||
if (data.email) vcard += `EMAIL;WORK;INTERNET:${data.email}\n`;
|
|
||||||
|
|
||||||
// Website
|
|
||||||
if (data.website) vcard += `URL:${data.website}\n`;
|
|
||||||
|
|
||||||
vcard += 'END:VCARD';
|
|
||||||
|
|
||||||
return vcard;
|
|
||||||
}
|
|
||||||
|
|
||||||
collectVCardData() {
|
|
||||||
return {
|
|
||||||
name: document.getElementById('vcard-name')?.value?.trim() || '',
|
|
||||||
mobile: document.getElementById('vcard-mobile')?.value?.trim() || '',
|
|
||||||
email: document.getElementById('vcard-email')?.value?.trim() || '',
|
|
||||||
company: document.getElementById('enable-company')?.checked ?
|
|
||||||
(document.getElementById('vcard-company')?.value?.trim() || '') : '',
|
|
||||||
title: document.getElementById('enable-title')?.checked ?
|
|
||||||
(document.getElementById('vcard-title')?.value?.trim() || '') : '',
|
|
||||||
website: document.getElementById('enable-website')?.checked ?
|
|
||||||
(document.getElementById('vcard-website')?.value?.trim() || '') : '',
|
|
||||||
address: document.getElementById('enable-address')?.checked ?
|
|
||||||
(document.getElementById('vcard-address')?.value?.trim() || '') : '',
|
|
||||||
city: document.getElementById('enable-address')?.checked ?
|
|
||||||
(document.getElementById('vcard-city')?.value?.trim() || '') : '',
|
|
||||||
state: document.getElementById('enable-address')?.checked ?
|
|
||||||
(document.getElementById('vcard-state')?.value?.trim() || '') : '',
|
|
||||||
zip: document.getElementById('enable-address')?.checked ?
|
|
||||||
(document.getElementById('vcard-zip')?.value?.trim() || '') : '',
|
|
||||||
phone: document.getElementById('enable-phone')?.checked ?
|
|
||||||
(document.getElementById('vcard-phone')?.value?.trim() || '') : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
validateVCardData() {
|
|
||||||
const data = this.collectVCardData();
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
// Required field validations
|
|
||||||
if (!data.name) errors.push('Nome é obrigatório');
|
|
||||||
if (!data.mobile) errors.push('Telefone celular é obrigatório');
|
|
||||||
if (!data.email) errors.push('Email é obrigatório');
|
|
||||||
|
|
||||||
// Format validations
|
|
||||||
if (data.email && !this.isValidEmail(data.email)) {
|
|
||||||
errors.push('Email inválido');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.website && !this.isValidURL(data.website)) {
|
|
||||||
errors.push('Website inválido (deve começar com http:// ou https://)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.mobile && !this.isValidPhone(data.mobile)) {
|
|
||||||
errors.push('Telefone celular inválido (deve ter 10-11 dígitos)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.phone && !this.isValidPhone(data.phone)) {
|
|
||||||
errors.push('Telefone fixo inválido (deve ter 10-11 dígitos)');
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateField(input) {
|
|
||||||
const value = input.value.trim();
|
|
||||||
let isValid = true;
|
|
||||||
let message = '';
|
|
||||||
|
|
||||||
switch (input.id) {
|
|
||||||
case 'vcard-email':
|
|
||||||
if (value && !this.isValidEmail(value)) {
|
|
||||||
isValid = false;
|
|
||||||
message = 'Email inválido';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'vcard-website':
|
|
||||||
if (value && !this.isValidURL(value)) {
|
|
||||||
isValid = false;
|
|
||||||
message = 'Website inválido (deve começar com http:// ou https://)';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'vcard-mobile':
|
|
||||||
case 'vcard-phone':
|
|
||||||
if (value && !this.isValidPhone(value)) {
|
|
||||||
isValid = false;
|
|
||||||
message = 'Telefone inválido (apenas números, 10-11 dígitos)';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update field validation state
|
|
||||||
if (isValid) {
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
} else {
|
|
||||||
input.classList.remove('is-valid');
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
// Show error message
|
|
||||||
const feedback = input.parentNode.querySelector('.invalid-feedback');
|
|
||||||
if (feedback) {
|
|
||||||
feedback.textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidEmail(email) {
|
|
||||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidURL(url) {
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
return url.startsWith('http://') || url.startsWith('https://');
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidPhone(phone) {
|
|
||||||
// Brazilian phone validation (DDD + number)
|
|
||||||
const digitsOnly = phone.replace(/\D/g, '');
|
|
||||||
return /^\d{10,11}$/.test(digitsOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to be called by main QR generator
|
|
||||||
getVCardContent() {
|
|
||||||
const errors = this.validateVCardData();
|
|
||||||
if (errors.length > 0) {
|
|
||||||
throw new Error('Erro na validação: ' + errors.join(', '));
|
|
||||||
}
|
|
||||||
return this.generateVCardContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global functions for ad control
|
// Global functions for ad control
|
||||||
window.QRApp = {
|
window.QRApp = {
|
||||||
refreshAds: function() {
|
refreshAds: function() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user