From 2a623d1fd5033a66ce9e3b7773f0ea6a5be9857b Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Thu, 31 Jul 2025 11:39:31 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20cart=C3=B5es=20de=20visita?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 4 +- Views/Home/Index.cshtml | 140 ++++++++++++++++ wwwroot/js/qr-speed-generator.js | 279 ++++++++++++++++++++++++++++++- 3 files changed, 420 insertions(+), 3 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9895e44..0dc554e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,9 @@ "Bash(timeout:*)", "Bash(rm:*)", "Bash(dotnet run:*)", - "Bash(curl:*)" + "Bash(curl:*)", + "Bash(pkill:*)", + "Bash(true)" ], "deny": [] } diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 3e7ca44..36ee0d3 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -117,6 +117,146 @@ + + +
diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index a657960..ed32cef 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -367,13 +367,35 @@ class QRRapidoGenerator { validateForm() { const qrType = document.getElementById('qr-type').value; - const qrContent = document.getElementById('qr-content').value.trim(); if (!qrType) { this.showError('Selecione o tipo de 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('
')); + 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) { this.showError('Digite o conteúdo do QR code'); return false; @@ -388,9 +410,35 @@ class QRRapidoGenerator { } collectFormData() { + const type = document.getElementById('qr-type').value; const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic'; 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 const logoUpload = document.getElementById('logo-upload'); const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0]; @@ -597,8 +645,28 @@ class QRRapidoGenerator { updateContentHints() { const type = document.getElementById('qr-type')?.value; const hintsElement = document.getElementById('content-hints'); + const vcardInterface = document.getElementById('vcard-interface'); + const contentTextarea = document.getElementById('qr-content'); + 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 = { 'pt-BR': { 'url': 'Ex: https://www.exemplo.com.br', @@ -999,7 +1067,7 @@ class QRRapidoGenerator { const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible fade show`; alert.innerHTML = ` - ${message} +
${message}
`; @@ -1042,6 +1110,7 @@ class QRRapidoGenerator { // Initialize when DOM loads document.addEventListener('DOMContentLoaded', () => { window.qrGenerator = new QRRapidoGenerator(); + window.vcardGenerator = new VCardGenerator(); // Initialize AdSense if necessary 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 window.QRApp = { refreshAds: function() {