Compare commits
2 Commits
9634176e18
...
286da5e5de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
286da5e5de | ||
|
|
ee160135af |
@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dotnet new:*)",
|
||||
@ -9,7 +10,9 @@
|
||||
"Bash(dotnet run:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(pkill:*)",
|
||||
"Bash(true)"
|
||||
"Bash(true)",
|
||||
"Bash(dotnet clean:*)",
|
||||
"Bash(grep:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
|
||||
|
||||
<div class="container">
|
||||
<!-- Hidden input for JavaScript premium status check -->
|
||||
<input type="hidden" id="user-premium-status" value="@(ViewBag.IsPremium == true ? "true" : "false")" />
|
||||
|
||||
<div class="row">
|
||||
<!-- QR Generator Form -->
|
||||
<div class="col-lg-8">
|
||||
@ -80,10 +83,6 @@
|
||||
<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">
|
||||
@ -103,7 +102,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-3" id="content-group">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||
</label>
|
||||
@ -118,6 +117,56 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic QR Section (Premium) -->
|
||||
<div id="dynamic-qr-section" style="display: none;">
|
||||
<div class="premium-feature-box">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h6 class="mb-1">
|
||||
<i class="fas fa-chart-line text-warning"></i> QR Dinâmico - Premium
|
||||
</h6>
|
||||
<small class="text-muted">
|
||||
Analytics em tempo real: cliques, localização, dispositivos, etc.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
@if (ViewBag.IsPremium == true)
|
||||
{
|
||||
<input class="form-check-input" type="checkbox" id="qr-dynamic-toggle">
|
||||
<label class="form-check-label" for="qr-dynamic-toggle">
|
||||
Ativar Analytics
|
||||
</label>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="premium-upgrade-prompt">
|
||||
<input class="form-check-input" type="checkbox" disabled>
|
||||
<label class="form-check-label text-muted">
|
||||
Ativar Analytics
|
||||
</label>
|
||||
<br>
|
||||
<small>
|
||||
<a href="/Pagamento/SelecaoPlano" class="text-warning">
|
||||
Upgrade Premium
|
||||
</a> para analytics
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL Preview -->
|
||||
<div id="url-preview" class="mt-3" style="display: none;">
|
||||
<h6><i class="fas fa-link"></i> Preview</h6>
|
||||
<div id="url-preview-content" class="bg-light p-3 rounded">
|
||||
<strong>URL Final:</strong> <span id="final-url-display"></span><br>
|
||||
<strong>Tipo:</strong> <span id="qr-type-display">QR Code Estático</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VCard Interface (dynamic) -->
|
||||
<div id="vcard-interface" class="mb-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
@ -250,15 +299,186 @@
|
||||
<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
|
||||
CHARSET=UTF-8
|
||||
Preencha os campos acima para ver o preview...
|
||||
END:VCARD</pre>
|
||||
VERSION:3.0
|
||||
CHARSET=UTF-8
|
||||
Preencha os campos acima para ver o preview...
|
||||
END:VCARD
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WiFi Interface (dynamic) -->
|
||||
<div id="wifi-interface" class="mb-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-wifi"></i>
|
||||
<strong>@Localizer["WiFiQRTitle"]</strong> - @Localizer["WiFiQRDescription"]
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">@Localizer["NetworkName"] *</label>
|
||||
<input type="text" id="wifi-ssid" name="wifi-ssid" class="form-control" placeholder="NomeDaSuaRede" required>
|
||||
<div class="invalid-feedback">@Localizer["NetworkNameRequired"]</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">@Localizer["SecurityType"]</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-wpa" value="WPA" checked>
|
||||
<label class="form-check-label" for="wifi-security-wpa">@Localizer["WPARecommended"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-wep" value="WEP">
|
||||
<label class="form-check-label" for="wifi-security-wep">@Localizer["WEPLegacy"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-nopass" value="nopass">
|
||||
<label class="form-check-label" for="wifi-security-nopass">@Localizer["OpenNetwork"]</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3" id="wifi-password-group">
|
||||
<label class="form-label fw-semibold">@Localizer["NetworkPassword"] *</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="wifi-password" name="wifi-password" class="form-control" placeholder="SenhaDaSuaRede" required>
|
||||
<button class="btn btn-outline-secondary" type="button" id="toggle-password">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="invalid-feedback">@Localizer["PasswordRequiredForSecure"]</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="wifi-hidden" name="wifi-hidden">
|
||||
<label class="form-check-label" for="wifi-hidden">@Localizer["HiddenNetwork"]</label>
|
||||
</div>
|
||||
|
||||
<div class="wifi-preview">
|
||||
<h6 class="fw-bold text-success mb-2">
|
||||
<i class="fas fa-eye"></i> @Localizer["WiFiPreviewTitle"]
|
||||
</h6>
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<pre id="wifi-preview-text" class="mb-0 small text-muted">WIFI:T:WPA;S:;P:;H:false;;</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WiFi Interface (dynamic) -->
|
||||
<div id="wifi-interface" class="mb-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-wifi"></i>
|
||||
<strong>@Localizer["WiFiQRTitle"]</strong> - @Localizer["WiFiQRDescription"]
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">@Localizer["NetworkName"] *</label>
|
||||
<input type="text" id="wifi-ssid2" class="form-control" placeholder="NomeDaSuaRede" required>
|
||||
<div class="invalid-feedback">@Localizer["NetworkNameRequired"]</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">@Localizer["SecurityType"]</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-wpa2" value="WPA" checked>
|
||||
<label class="form-check-label" for="wifi-security-wpa">@Localizer["WPARecommended"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-wep2" value="WEP">
|
||||
<label class="form-check-label" for="wifi-security-wep">@Localizer["WEPLegacy"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="wifi-security" id="wifi-security-nopass2" value="nopass">
|
||||
<label class="form-check-label" for="wifi-security-nopass">@Localizer["OpenNetwork"]</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3" id="wifi-password-group">
|
||||
<label class="form-label fw-semibold">@Localizer["NetworkPassword"] *</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="wifi-password2" class="form-control" placeholder="SenhaDaSuaRede" required>
|
||||
<button class="btn btn-outline-secondary" type="button" id="toggle-password2">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="invalid-feedback">@Localizer["PasswordRequiredForSecure"]</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="wifi-hidden2">
|
||||
<label class="form-check-label" for="wifi-hidden">@Localizer["HiddenNetwork"]</label>
|
||||
</div>
|
||||
|
||||
<div class="wifi-preview">
|
||||
<h6 class="fw-bold text-success mb-2">
|
||||
<i class="fas fa-eye"></i> @Localizer["WiFiPreviewTitle"]
|
||||
</h6>
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<pre id="wifi-preview-text" class="mb-0 small text-muted">WIFI:T:WPA;S:;P:;H:false;;</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMS Interface (dynamic) -->
|
||||
<div id="sms-interface" class="mb-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<strong>QR Code SMS:</strong> Permite enviar SMS automático com número e mensagem pré-definidos.
|
||||
Compatível com Android, iOS e dispositivos modernos.
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">Número do Celular *</label>
|
||||
<input type="tel" id="sms-number" name="sms-number" class="form-control" placeholder="11999998888" required>
|
||||
<small class="text-muted">Apenas números (DDD + número, sem espaços)</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">Mensagem *</label>
|
||||
<textarea id="sms-message" name="sms-message" class="form-control" rows="3" placeholder="Sua mensagem aqui..." required></textarea>
|
||||
<small class="text-muted">Mensagem que será pré-preenchida no SMS</small>
|
||||
</div>
|
||||
|
||||
<div class="sms-preview">
|
||||
<h6>️ Preview do SMS</h6>
|
||||
<pre id="sms-preview-text" class="bg-light p-3 rounded">SMSTO::</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Interface (dynamic) -->
|
||||
<div id="email-interface" class="mb-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<strong>QR Code Email:</strong> Permite enviar email automático com destinatário, assunto e mensagem pré-definidos.
|
||||
Compatível com Android, iOS e dispositivos modernos.
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">Email Destinatário *</label>
|
||||
<input type="email" id="email-to" name="email-to" class="form-control" placeholder="contato@empresa.com" required>
|
||||
<small class="text-muted">Email que receberá a mensagem</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">Assunto *</label>
|
||||
<input type="text" id="email-subject" name="email-subject" class="form-control" placeholder="Assunto do email" required>
|
||||
<small class="text-muted">Linha de assunto do email</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-semibold">Mensagem</label>
|
||||
<textarea id="email-body" name="email-body" class="form-control" rows="4" placeholder="Corpo da mensagem..."></textarea>
|
||||
<small class="text-muted">Corpo da mensagem (opcional)</small>
|
||||
</div>
|
||||
|
||||
<div class="email-preview">
|
||||
<h6>️ Preview do Email</h6>
|
||||
<pre id="email-preview-text" class="bg-light p-3 rounded">mailto:?subject=&body=</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced customization (collapsible) -->
|
||||
<div class="accordion mb-3" id="customization-accordion">
|
||||
<div class="accordion-item">
|
||||
|
||||
@ -18,6 +18,24 @@ body {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
/* URL Validation Feedback */
|
||||
#url-validation-error {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
||||
}
|
||||
|
||||
.form-control.is-valid {
|
||||
border-color: #28a745;
|
||||
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
|
||||
}
|
||||
|
||||
/* Share Button Styles */
|
||||
#share-qr-btn {
|
||||
position: relative;
|
||||
@ -123,4 +141,45 @@ body {
|
||||
|
||||
.share-success.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.premium-feature-box {
|
||||
border: 2px solid #ffc107;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
|
||||
}
|
||||
|
||||
.premium-feature-box h6 {
|
||||
color: #856404;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: #ffc107;
|
||||
border-color: #ffc107;
|
||||
}
|
||||
|
||||
.premium-upgrade-prompt {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.premium-upgrade-prompt a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.premium-upgrade-prompt a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#url-preview-content {
|
||||
font-family: monospace;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #28a745 !important;
|
||||
}
|
||||
|
||||
@ -106,16 +106,21 @@ class QRRapidoGenerator {
|
||||
});
|
||||
});
|
||||
|
||||
// VCard fields validation
|
||||
const vcardFields = ['vcard-name', 'vcard-mobile', 'vcard-email'];
|
||||
vcardFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.addEventListener('input', () => {
|
||||
this.updateGenerateButton();
|
||||
});
|
||||
// Add listeners to all relevant fields to update button state
|
||||
const fieldsToWatch = [
|
||||
'qr-content', 'vcard-name', 'vcard-mobile', 'vcard-email',
|
||||
'wifi-ssid', 'wifi-password', 'sms-number', 'sms-message',
|
||||
'email-to', 'email-subject', 'email-body'
|
||||
];
|
||||
fieldsToWatch.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.addEventListener('input', () => this.updateGenerateButton());
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('input[name="wifi-security"]').forEach(radio => {
|
||||
radio.addEventListener('change', () => this.updateGenerateButton());
|
||||
});
|
||||
|
||||
// Language selector
|
||||
document.querySelectorAll('[data-lang]').forEach(link => {
|
||||
@ -431,6 +436,27 @@ class QRRapidoGenerator {
|
||||
this.showError('Erro na validação do VCard: ' + error.message);
|
||||
return false;
|
||||
}
|
||||
} else if (qrType === 'wifi') {
|
||||
const errors = window.wifiGenerator.validateWiFiData();
|
||||
if (errors.length > 0) {
|
||||
this.showError(errors.join('<br>'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (qrType === 'sms') {
|
||||
const errors = window.smsGenerator.validateSMSData();
|
||||
if (errors.length > 0) {
|
||||
this.showError(errors.join('<br>'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (qrType === 'email') {
|
||||
const errors = window.emailGenerator.validateEmailData();
|
||||
if (errors.length > 0) {
|
||||
this.showError(errors.join('<br>'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal validation for other types
|
||||
@ -453,7 +479,87 @@ class QRRapidoGenerator {
|
||||
const type = document.getElementById('qr-type').value;
|
||||
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
||||
const styleSettings = this.getStyleSettings(quickStyle);
|
||||
|
||||
if (type === 'url') {
|
||||
const dynamicData = window.dynamicQRManager.getDynamicQRData();
|
||||
if (dynamicData.requiresPremium) {
|
||||
throw new Error('QR Dinâmico é exclusivo para usuários Premium. Faça upgrade para usar analytics.');
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
type: 'url',
|
||||
content: dynamicData.originalUrl,
|
||||
isDynamic: dynamicData.isDynamic,
|
||||
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'
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'wifi') {
|
||||
const wifiContent = window.wifiGenerator.generateWiFiString();
|
||||
return {
|
||||
data: {
|
||||
type: 'text', // WiFi is treated as text in the backend
|
||||
content: wifiContent,
|
||||
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 if (type === 'sms') {
|
||||
const smsContent = window.smsGenerator.generateSMSString();
|
||||
return {
|
||||
data: {
|
||||
type: 'text',
|
||||
content: smsContent,
|
||||
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 if (type === 'email') {
|
||||
const emailContent = window.emailGenerator.generateEmailString();
|
||||
return {
|
||||
data: {
|
||||
type: 'text',
|
||||
content: emailContent,
|
||||
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'
|
||||
};
|
||||
}
|
||||
|
||||
// Handle VCard type
|
||||
if (type === 'vcard') {
|
||||
if (window.vcardGenerator) {
|
||||
@ -729,7 +835,7 @@ class QRRapidoGenerator {
|
||||
'wifi': 'Nombre de red;Contraseña;Tipo de seguridad (WPA/WEP)',
|
||||
'vcard': 'Nombre;Teléfono;Email;Empresa',
|
||||
'sms': 'Número;Mensaje',
|
||||
'email': 'email@ejemplo.com;Asunto;Mensaje'
|
||||
'email': 'email@ejemplo.com;Asunto;Mensagem'
|
||||
}
|
||||
};
|
||||
|
||||
@ -888,11 +994,15 @@ class QRRapidoGenerator {
|
||||
const timeFloat = parseFloat(generationTime);
|
||||
|
||||
// Update average time display in the stats cards
|
||||
const avgElement = document.querySelector('.card-body h5:contains("1.2s")');
|
||||
if (avgElement) {
|
||||
const avgTime = window.qrRapidoStats ? window.qrRapidoStats.getAverageTime() : generationTime;
|
||||
avgElement.innerHTML = `<i class="fas fa-stopwatch"></i> ${avgTime}s`;
|
||||
}
|
||||
// Find h5 elements in card bodies and look for one containing time info
|
||||
const cardElements = document.querySelectorAll('.card-body h5');
|
||||
cardElements.forEach(element => {
|
||||
// Look for elements containing time format (e.g., "1.2s", "0.8s", etc.)
|
||||
if (element.textContent.includes('s') && element.textContent.match(/\d+\.\d+s/)) {
|
||||
const avgTime = window.qrRapidoStats ? window.qrRapidoStats.getAverageTime() : generationTime;
|
||||
element.innerHTML = `<i class="fas fa-stopwatch"></i> ${avgTime}s`;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the generation timer in the header
|
||||
const timerElement = document.querySelector('.generation-timer span');
|
||||
@ -1162,10 +1272,8 @@ class QRRapidoGenerator {
|
||||
const qrContent = document.getElementById('qr-content');
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
|
||||
// Inicialmente desabilitar campo de conteúdo
|
||||
if (qrContent) {
|
||||
qrContent.disabled = true;
|
||||
}
|
||||
// FIXED: Don't disable content field initially - let enableContentFields handle it
|
||||
// The field should be available for typing once a type is selected
|
||||
|
||||
// Ocultar interface vCard
|
||||
if (vcardInterface) {
|
||||
@ -1173,6 +1281,45 @@ class QRRapidoGenerator {
|
||||
}
|
||||
|
||||
this.updateGenerateButton();
|
||||
|
||||
// Setup URL validation event listeners
|
||||
this.setupURLValidationListeners();
|
||||
}
|
||||
|
||||
setupURLValidationListeners() {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
if (!contentField) return;
|
||||
|
||||
// Auto-fix URL on blur (when user leaves the field)
|
||||
contentField.addEventListener('blur', () => {
|
||||
const type = document.getElementById('qr-type')?.value;
|
||||
|
||||
if (type === 'url' && contentField.value.trim()) {
|
||||
const originalValue = contentField.value.trim();
|
||||
const fixedValue = this.autoFixURL(originalValue);
|
||||
|
||||
if (originalValue !== fixedValue) {
|
||||
contentField.value = fixedValue;
|
||||
console.log('🔧 URL auto-corrigida:', originalValue, '→', fixedValue);
|
||||
|
||||
// Revalidar após correção
|
||||
this.updateGenerateButton();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Real-time validation with debounce
|
||||
contentField.addEventListener('input', () => {
|
||||
const type = document.getElementById('qr-type')?.value;
|
||||
|
||||
if (type === 'url') {
|
||||
// Debounce para não validar a cada caractere
|
||||
clearTimeout(contentField.validationTimeout);
|
||||
contentField.validationTimeout = setTimeout(() => {
|
||||
this.updateGenerateButton();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleTypeSelection(type) {
|
||||
@ -1207,28 +1354,62 @@ class QRRapidoGenerator {
|
||||
}
|
||||
|
||||
enableContentFields(type) {
|
||||
const qrContent = document.getElementById('qr-content');
|
||||
const contentGroup = document.getElementById('content-group');
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
|
||||
const wifiInterface = document.getElementById('wifi-interface');
|
||||
const smsInterface = document.getElementById('sms-interface');
|
||||
const emailInterface = document.getElementById('email-interface');
|
||||
const dynamicQRSection = document.getElementById('dynamic-qr-section');
|
||||
const urlPreview = document.getElementById('url-preview');
|
||||
|
||||
// Hide all interfaces by default
|
||||
if (vcardInterface) vcardInterface.style.display = 'none';
|
||||
if (wifiInterface) wifiInterface.style.display = 'none';
|
||||
if (smsInterface) smsInterface.style.display = 'none';
|
||||
if (emailInterface) emailInterface.style.display = 'none';
|
||||
if (dynamicQRSection) dynamicQRSection.style.display = 'none';
|
||||
if (urlPreview) urlPreview.style.display = 'none';
|
||||
if (contentGroup) contentGroup.style.display = 'block';
|
||||
|
||||
if (type === 'vcard') {
|
||||
// Para vCard, ocultar textarea e mostrar interface específica
|
||||
if (qrContent) {
|
||||
qrContent.style.display = 'none';
|
||||
qrContent.removeAttribute('required');
|
||||
}
|
||||
if (contentGroup) contentGroup.style.display = 'none';
|
||||
if (vcardInterface) {
|
||||
vcardInterface.style.display = 'block';
|
||||
this.enableVCardFields();
|
||||
}
|
||||
} else {
|
||||
// Para outros tipos, mostrar textarea
|
||||
if (qrContent) {
|
||||
qrContent.style.display = 'block';
|
||||
qrContent.setAttribute('required', 'required');
|
||||
} else if (type === 'wifi') {
|
||||
// Para WiFi, ocultar textarea e mostrar interface específica
|
||||
if (contentGroup) contentGroup.style.display = 'none';
|
||||
if (wifiInterface) {
|
||||
wifiInterface.style.display = 'block';
|
||||
}
|
||||
} else if (type === 'sms') {
|
||||
// Para SMS, ocultar textarea e mostrar interface específica
|
||||
if (contentGroup) contentGroup.style.display = 'none';
|
||||
if (smsInterface) {
|
||||
smsInterface.style.display = 'block';
|
||||
}
|
||||
} else if (type === 'email') {
|
||||
// Para Email, ocultar textarea e mostrar interface específica
|
||||
if (contentGroup) contentGroup.style.display = 'none';
|
||||
if (emailInterface) {
|
||||
emailInterface.style.display = 'block';
|
||||
}
|
||||
} else if (type === 'url') {
|
||||
if (dynamicQRSection) dynamicQRSection.style.display = 'block';
|
||||
if (urlPreview) urlPreview.style.display = 'block';
|
||||
// CRITICAL FIX: Enable content field for URL type
|
||||
const qrContent = document.getElementById('qr-content');
|
||||
if(qrContent) {
|
||||
qrContent.disabled = false;
|
||||
}
|
||||
if (vcardInterface) {
|
||||
vcardInterface.style.display = 'none';
|
||||
} else {
|
||||
// Para outros tipos, mostrar textarea
|
||||
if (contentGroup) contentGroup.style.display = 'block';
|
||||
const qrContent = document.getElementById('qr-content');
|
||||
if(qrContent) {
|
||||
qrContent.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1250,7 +1431,7 @@ class QRRapidoGenerator {
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
|
||||
if (qrContent) {
|
||||
qrContent.disabled = true;
|
||||
// FIXED: Don't disable content field, just clear it
|
||||
qrContent.value = '';
|
||||
}
|
||||
|
||||
@ -1320,33 +1501,137 @@ class QRRapidoGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
updateGenerateButton() {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
if (!generateBtn) return;
|
||||
// ============================================
|
||||
// URL VALIDATION FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
isValidURL(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
|
||||
let shouldEnable = false;
|
||||
const trimmedUrl = url.trim();
|
||||
|
||||
// Verificar se tem tipo selecionado
|
||||
if (this.selectedType) {
|
||||
if (this.selectedType === 'vcard') {
|
||||
// Para vCard, validar campos obrigatórios
|
||||
shouldEnable = this.validateVCardFields();
|
||||
} else {
|
||||
// Para outros tipos, validar conteúdo
|
||||
const content = document.getElementById('qr-content')?.value || '';
|
||||
shouldEnable = this.validateContent(content);
|
||||
// Verificar se começa com http:// ou https://
|
||||
const hasProtocol = trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://');
|
||||
|
||||
// Verificar se tem pelo menos um ponto
|
||||
const hasDot = trimmedUrl.includes('.');
|
||||
|
||||
// Verificar se não é só o protocolo
|
||||
const hasContent = trimmedUrl.length > 8; // mais que "https://"
|
||||
|
||||
// Regex mais robusta
|
||||
const urlRegex = /^https?:\/\/.+\..+/i;
|
||||
|
||||
return hasProtocol && hasDot && hasContent && urlRegex.test(trimmedUrl);
|
||||
}
|
||||
|
||||
autoFixURL(url) {
|
||||
if (!url || typeof url !== 'string') return '';
|
||||
|
||||
let fixedUrl = url.trim();
|
||||
|
||||
// Se não tem protocolo, adicionar https://
|
||||
if (!fixedUrl.startsWith('http://') && !fixedUrl.startsWith('https://')) {
|
||||
fixedUrl = 'https://' + fixedUrl;
|
||||
}
|
||||
|
||||
return fixedUrl;
|
||||
}
|
||||
|
||||
showValidationError(message) {
|
||||
let errorDiv = document.getElementById('url-validation-error');
|
||||
|
||||
if (!errorDiv) {
|
||||
// Criar div de erro se não existir
|
||||
errorDiv = document.createElement('div');
|
||||
errorDiv.id = 'url-validation-error';
|
||||
errorDiv.className = 'alert alert-danger mt-2';
|
||||
errorDiv.style.display = 'none';
|
||||
|
||||
// Inserir após o campo de conteúdo
|
||||
const contentGroup = document.getElementById('content-group');
|
||||
if (contentGroup) {
|
||||
contentGroup.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
generateBtn.disabled = !shouldEnable;
|
||||
|
||||
// Feedback visual
|
||||
if (shouldEnable) {
|
||||
generateBtn.classList.remove('btn-secondary');
|
||||
errorDiv.innerHTML = `<small><i class="fas fa-exclamation-triangle"></i> ${message}</small>`;
|
||||
errorDiv.style.display = 'block';
|
||||
}
|
||||
|
||||
clearValidationError() {
|
||||
const errorDiv = document.getElementById('url-validation-error');
|
||||
if (errorDiv) {
|
||||
errorDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
updateGenerateButton() {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
if (!generateBtn) return;
|
||||
|
||||
let isValid = false;
|
||||
const type = this.selectedType;
|
||||
|
||||
if (type === 'url') {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const content = contentField?.value || '';
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
if (!trimmedContent) {
|
||||
this.showValidationError('URL é obrigatória');
|
||||
if (contentField) {
|
||||
contentField.classList.remove('is-valid');
|
||||
contentField.classList.add('is-invalid');
|
||||
}
|
||||
isValid = false;
|
||||
} else if (!this.isValidURL(trimmedContent)) {
|
||||
this.showValidationError('URL deve começar com http:// ou https:// e conter pelo menos um ponto (ex: https://google.com)');
|
||||
if (contentField) {
|
||||
contentField.classList.remove('is-valid');
|
||||
contentField.classList.add('is-invalid');
|
||||
}
|
||||
isValid = false;
|
||||
} else {
|
||||
this.clearValidationError();
|
||||
if (contentField) {
|
||||
contentField.classList.remove('is-invalid');
|
||||
contentField.classList.add('is-valid');
|
||||
}
|
||||
isValid = true;
|
||||
}
|
||||
} else if (type === 'text') {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const content = contentField?.value || '';
|
||||
isValid = content.trim().length >= 3;
|
||||
this.clearValidationError(); // Clear any URL validation errors
|
||||
if (contentField) {
|
||||
contentField.classList.remove('is-valid', 'is-invalid');
|
||||
}
|
||||
} else if (type === 'vcard') {
|
||||
isValid = this.validateVCardFields();
|
||||
} else if (type === 'wifi') {
|
||||
const data = window.wifiGenerator.collectWiFiData();
|
||||
isValid = data.ssid.trim() !== '';
|
||||
if (data.security !== 'nopass' && !data.password.trim()) {
|
||||
isValid = false;
|
||||
}
|
||||
} else if (type === 'sms') {
|
||||
const data = window.smsGenerator.collectSMSData();
|
||||
isValid = data.number.trim() !== '' && data.message.trim() !== '';
|
||||
} else if (type === 'email') {
|
||||
const data = window.emailGenerator.collectEmailData();
|
||||
isValid = data.to.trim() !== '' && data.subject.trim() !== '';
|
||||
}
|
||||
|
||||
generateBtn.disabled = !isValid;
|
||||
|
||||
if (isValid) {
|
||||
generateBtn.classList.remove('btn-secondary', 'disabled');
|
||||
generateBtn.classList.add('btn-primary');
|
||||
} else {
|
||||
generateBtn.classList.remove('btn-primary');
|
||||
generateBtn.classList.add('btn-secondary');
|
||||
generateBtn.classList.add('btn-secondary', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1369,7 +1654,7 @@ class QRRapidoGenerator {
|
||||
// Garantir que o conteúdo seja tratado como UTF-8
|
||||
try {
|
||||
// Verificar se há caracteres especiais
|
||||
const hasSpecialChars = /[^\x00-\x7F]/.test(content);
|
||||
const hasSpecialChars = /[^ | ||||