feat: cartões de visita
This commit is contained in:
parent
00d924ce3b
commit
2a623d1fd5
@ -7,7 +7,9 @@
|
|||||||
"Bash(timeout:*)",
|
"Bash(timeout:*)",
|
||||||
"Bash(rm:*)",
|
"Bash(rm:*)",
|
||||||
"Bash(dotnet run:*)",
|
"Bash(dotnet run:*)",
|
||||||
"Bash(curl:*)"
|
"Bash(curl:*)",
|
||||||
|
"Bash(pkill:*)",
|
||||||
|
"Bash(true)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,146 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- VCard Interface (dynamic) -->
|
||||||
|
<div id="vcard-interface" class="mb-3" 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) -->
|
<!-- Advanced customization (collapsible) -->
|
||||||
<div class="accordion mb-3" id="customization-accordion">
|
<div class="accordion mb-3" id="customization-accordion">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
|
|||||||
@ -367,13 +367,35 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
@ -388,9 +410,35 @@ 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];
|
||||||
@ -597,8 +645,28 @@ 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';
|
||||||
|
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',
|
||||||
@ -999,7 +1067,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 = `
|
||||||
${message}
|
<div>${message}</div>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -1042,6 +1110,7 @@ class QRRapidoGenerator {
|
|||||||
// 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')) {
|
||||||
@ -1049,6 +1118,212 @@ 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