PageSourceSearch

https://www.cref11.org.br/wp-content/plugins/wordpress-certificate…/assets/js/enrollment-form.js?ver=1.7.0

js cref11.org.br collected 2026-10-05 06:16:38 UTC 18,914 bytes, 396 lines download raw bytes

1/**
2 * enrollment-form.js
3 *
4 * Modal de inscrição em cursos: abertura, validação client-side, submissão AJAX
5 * e geração de protocolo de matrícula em PDF.
6 *
7 * @package WordPressCertificatePlugin
8 */
9
10/* global wcpEnrollment */
11
12( function () {
13	'use strict';
14
15	document.addEventListener( 'DOMContentLoaded', function () {
16		var modal       = document.getElementById( 'wcp-enrollment-modal' );
17		var form        = document.getElementById( 'wcp-enrollment-form' );
18		var closeBtn    = document.getElementById( 'wcp-modal-close' );
19		var titleEl     = document.getElementById( 'wcp-modal-title' );
20		var cursoIdEl   = document.getElementById( 'wcp-curso-matricula-id' );
21		var cursoSrcEl  = document.getElementById( 'wcp-curso-source' );
22		var nonceEl     = document.getElementById( 'wcp-enrollment-nonce' );
23		var submitBtn   = document.getElementById( 'wcp-enrollment-submit' );
24		var successArea = document.getElementById( 'wcp-enrollment-success' );
25		var successMsg  = document.getElementById( 'wcp-success-message' );
26		var generalErr  = document.getElementById( 'wcp-form-error-general' );
27
28		if ( ! modal || ! form ) { return; }
29
30		var fields = {
31			nome:         document.getElementById( 'wcp-enrollment-nome' ),
32			cpf:          document.getElementById( 'wcp-enrollment-cpf' ),
33			email:        document.getElementById( 'wcp-enrollment-email' ),
34			telefone:     document.getElementById( 'wcp-enrollment-telefone' ),
35			numero_cref:  document.getElementById( 'wcp-enrollment-cref' ),
36			validade_cip: document.getElementById( 'wcp-enrollment-validade-cip' )
37		};
38
39		var errorEls = {
40			nome:         document.getElementById( 'wcp-error-nome' ),
41			cpf:          document.getElementById( 'wcp-error-cpf' ),
42			email:        document.getElementById( 'wcp-error-email' ),
43			telefone:     document.getElementById( 'wcp-error-telefone' ),
44			numero_cref:  document.getElementById( 'wcp-error-cref' ),
45			validade_cip: document.getElementById( 'wcp-error-validade-cip' )
46		};
47
48		/* ── Máscara de CPF ───────────────────────────────────────────────── */
49
50		if ( fields.cpf ) {
51			fields.cpf.addEventListener( 'input', function () {
52				var v = this.value.replace( /\D/g, '' ).substring( 0, 11 );
53				if ( v.length > 9 ) {
54					this.value = v.replace( /(\d{3})(\d{3})(\d{3})(\d{1,2})/, '$1.$2.$3-$4' );
55				} else if ( v.length > 6 ) {
56					this.value = v.replace( /(\d{3})(\d{3})(\d{1,3})/, '$1.$2.$3' );
57				} else if ( v.length > 3 ) {
58					this.value = v.replace( /(\d{3})(\d{1,3})/, '$1.$2' );
59				} else {
60					this.value = v;
61				}
62			} );
63		}
64
65		/* ── Máscara de WhatsApp / telefone ───────────────────────────────── */
66
67		if ( fields.telefone ) {
68			fields.telefone.addEventListener( 'input', function () {
69				var v = this.value.replace( /\D/g, '' ).substring( 0, 11 );
70				if ( v.length > 10 ) {
71					// Celular com 11 dígitos: (XX) XXXXX-XXXX
72					this.value = v.replace( /(\d{2})(\d{5})(\d{1,4})/, '($1) $2-$3' );
73				} else if ( v.length > 6 ) {
74					// Fixo com 10 dígitos: (XX) XXXX-XXXX
75					this.value = v.replace( /(\d{2})(\d{4})(\d{1,4})/, '($1) $2-$3' );
76				} else if ( v.length > 2 ) {
77					this.value = v.replace( /(\d{2})(\d{1,5})/, '($1) $2' );
78				} else if ( v.length > 0 ) {
79					this.value = v.replace( /(\d{1,2})/, '($1' );
80				} else {
81					this.value = v;
82				}
83			} );
84		}
85
86		/* ── Abrir Modal ──────────────────────────────────────────────── */
87
88		var tsEl = document.getElementById( 'wcp-form-ts' );
89
90		function openModal( cursoNome, cursoId, cursoSource ) {
91			titleEl.textContent = cursoNome;
92			cursoIdEl.value     = cursoId;
93			if ( cursoSrcEl ) { cursoSrcEl.value = cursoSource || 'legacy'; }
94			nonceEl.value       = wcpEnrollment.nonce;
95
96			form.removeAttribute( 'hidden' );
97			successArea.setAttribute( 'hidden', '' );
98			generalErr.setAttribute( 'hidden', '' );
99			generalErr.textContent = '';
100			clearAllErrors();
101			form.reset();
102
103			// Marca o instante de abertura para o time-trap anti-bot.
104			if ( tsEl ) { tsEl.value = String( Date.now() ); }
105
106			modal.removeAttribute( 'hidden' );
107			modal.setAttribute( 'aria-hidden', 'false' );
108
109			if ( fields.nome ) { fields.nome.focus(); }
110		}
111
112		function closeModal() {
113			modal.setAttribute( 'hidden', '' );
114			modal.setAttribute( 'aria-hidden', 'true' );
115		}
116
117		/* ── Delegação de clique nos botões "Inscrever-se" ────────────── */
118
119		document.addEventListener( 'click', function ( e ) {
120			var btn = e.target.closest( '.wcp-btn-inscrever' );
121			if ( ! btn ) { return; }
122
123			var nome   = btn.getAttribute( 'data-curso-nome' );
124			var id     = btn.getAttribute( 'data-curso-id' );
125			var source = btn.getAttribute( 'data-curso-source' ) || 'legacy';
126
127			if ( nome && id ) {
128				openModal( nome, id, source );
129			}
130		} );
131
132		/* ── Fechar modal ─────────────────────────────────────────────── */
133
134		if ( closeBtn ) {
135			closeBtn.addEventListener( 'click', closeModal );
136		}
137
138		modal.addEventListener( 'click', function ( e ) {
139			if ( e.target === modal ) { closeModal(); }
140		} );
141
142		document.addEventListener( 'keydown', function ( e ) {
143			if ( e.key === 'Escape' && ! modal.hasAttribute( 'hidden' ) ) {
144				closeModal();
145			}
146		} );
147
148		/* ── Limpar erros ao digitar ──────────────────────────────────── */
149
150		Object.keys( fields ).forEach( function ( key ) {
151			if ( fields[ key ] ) {
152				fields[ key ].addEventListener( 'input', function () {
153					clearFieldError( key );
154				} );
155			}
156		} );
157
158		/* ── Validação ────────────────────────────────────────────────── */
159
160		function validateFields() {
161			var errors = {};
162			var nome = ( fields.nome ? fields.nome.value.trim() : '' );
163			if ( ! nome ) { errors.nome = 'O nome é obrigatório.'; }
164
165			// CPF validation
166			var cpfRaw = ( fields.cpf ? fields.cpf.value.replace( /\D/g, '' ) : '' );
167			if ( ! cpfRaw ) { errors.cpf = 'O CPF é obrigatório.'; }
168			else if ( cpfRaw.length !== 11 ) { errors.cpf = 'CPF deve ter 11 dígitos.'; }
169			else if ( ! validateCpfDigits( cpfRaw ) ) { errors.cpf = 'CPF inválido.'; }
170
171			// E-mail é opcional; valida formato apenas se preenchido.
172			var email = ( fields.email ? fields.email.value.trim() : '' );
173			if ( email && ! /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test( email ) ) { errors.email = 'Formato de e-mail inválido.'; }
174
175			var telefone = ( fields.telefone ? fields.telefone.value.replace( /\D/g, '' ) : '' );
176			if ( ! telefone ) { errors.telefone = 'O WhatsApp é obrigatório.'; }
177			else if ( ! /^\d{10,11}$/.test( telefone ) ) { errors.telefone = 'O WhatsApp deve ter 10 ou 11 dígitos (com DDD).'; }
178
179			var cref = ( fields.numero_cref ? fields.numero_cref.value.trim() : '' );
180			if ( ! cref ) { errors.numero_cref = 'O número CREF é obrigatório.'; }
181			else if ( ! /^[a-zA-Z0-9]{4,20}$/.test( cref.replace( /[^a-zA-Z0-9]/g, '' ) ) ) { errors.numero_cref = 'Entre 4 e 20 caracteres alfanuméricos.'; }
182
183			return errors;
184		}
185
186		function validateCpfDigits( cpf ) {
187			if ( /^(\d)\1{10}$/.test( cpf ) ) return false;
188			var sum = 0, i;
189			for ( i = 0; i < 9; i++ ) sum += parseInt( cpf[i] ) * ( 10 - i );
190			var rem = sum % 11;
191			var d1 = rem < 2 ? 0 : 11 - rem;
192			if ( parseInt( cpf[9] ) !== d1 ) return false;
193			sum = 0;
194			for ( i = 0; i < 10; i++ ) sum += parseInt( cpf[i] ) * ( 11 - i );
195			rem = sum % 11;
196			var d2 = rem < 2 ? 0 : 11 - rem;
197			return parseInt( cpf[10] ) === d2;
198		}
199
200		/* ── Utilitários de erro ──────────────────────────────────────── */
201
202		function showFieldError( field, message ) {
203			if ( errorEls[ field ] ) { errorEls[ field ].textContent = message; }
204			if ( fields[ field ] ) { fields[ field ].classList.add( 'wcp-input-error' ); }
205		}
206
207		function clearFieldError( field ) {
208			if ( errorEls[ field ] ) { errorEls[ field ].textContent = ''; }
209			if ( fields[ field ] ) { fields[ field ].classList.remove( 'wcp-input-error' ); }
210		}
211
212		function clearAllErrors() {
213			Object.keys( errorEls ).forEach( function ( key ) { clearFieldError( key ); } );
214		}
215
216		function showFieldErrors( errors ) {
217			Object.keys( errors ).forEach( function ( key ) { showFieldError( key, errors[ key ] ); } );
218		}
219
220		function showGeneralError( message ) {
221			if ( generalErr ) {
222				generalErr.textContent = message;
223				generalErr.removeAttribute( 'hidden' );
224			}
225		}
226
227		/* ── Geração de Protocolo PDF (padrão governamental) ──────────── */
228
229		function generateProtocol( data ) {
230			var now = new Date();
231			var protocolo = data.protocolo || ( 'MAT' + now.getFullYear() + pad( now.getMonth() + 1 ) + pad( now.getDate() ) + pad( now.getHours() ) + pad( now.getMinutes() ) + pad( now.getSeconds() ) + String( data.enrollment_id ).padStart( 5, '0' ) );
232			var dataHora = pad( now.getDate() ) + '/' + pad( now.getMonth() + 1 ) + '/' + now.getFullYear() + ' às ' + pad( now.getHours() ) + ':' + pad( now.getMinutes() ) + ':' + pad( now.getSeconds() );
233			var cursoNome = titleEl.textContent;
234			var nome = fields.nome ? fields.nome.value.trim() : '';
235			var email = fields.email ? fields.email.value.trim() : '';
236			var telefone = fields.telefone ? fields.telefone.value : '';
237			var cref = fields.numero_cref ? fields.numero_cref.value.trim() : '';
238			var validadeCip = fields.validade_cip ? fields.validade_cip.value : '';
239
240			// Código de autenticação (hash simples para verificação)
241			var authCode = protocolo.split( '' ).reverse().join( '' ).substring( 0, 8 ).toUpperCase();
242
243			var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Protocolo de Matrícula</title>'
244				+ '<style>'
245				+ 'body{font-family:"Segoe UI",Roboto,Arial,sans-serif;margin:0;padding:40px;color:#1a1a1a;font-size:14px;}'
246				+ '.protocolo{max-width:700px;margin:0 auto;border:2px solid #35515b;border-radius:4px;padding:0;}'
247				+ '.header{background:#35515b;color:#fff;padding:24px 32px;display:flex;align-items:center;justify-content:space-between;}'
248				+ '.header h1{margin:0;font-size:18px;font-weight:700;letter-spacing:0.5px;}'
249				+ '.header .subtitulo{margin:4px 0 0;font-size:12px;opacity:0.85;}'
250				+ '.brasao{font-size:28px;}'
251				+ '.corpo{padding:32px;}'
252				+ '.numero-protocolo{background:#f0f4f5;border:1px solid #d4dee1;border-radius:6px;padding:16px 24px;margin-bottom:24px;text-align:center;}'
253				+ '.numero-protocolo .label{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#64748b;margin-bottom:4px;}'
254				+ '.numero-protocolo .valor{font-size:22px;font-weight:700;color:#35515b;font-family:monospace;letter-spacing:2px;}'
255				+ '.secao{margin-bottom:20px;}'
256				+ '.secao-titulo{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#64748b;border-bottom:1px solid #e2e8f0;padding-bottom:6px;margin-bottom:12px;font-weight:600;}'
257				+ '.campo{display:flex;margin-bottom:8px;}'
258				+ '.campo .rotulo{font-weight:600;color:#374151;min-width:160px;}'
259				+ '.campo .valor{color:#1e293b;}'
260				+ '.rodape{border-top:1px solid #e2e8f0;padding:20px 32px;display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#64748b;}'
261				+ '.autenticacao{font-family:monospace;font-size:13px;color:#35515b;font-weight:600;letter-spacing:1px;}'
262				+ '.aviso{background:#fefce8;border:1px solid #fde047;border-radius:6px;padding:12px 16px;margin-top:20px;font-size:12px;color:#854d0e;}'
263				+ '@media print{body{padding:20px;}.protocolo{border-width:1px;}}'
264				+ '</style></head><body>'
265				+ '<div class="protocolo">'
266				+ '<div class="header"><div><h1>COMPROVANTE DE MATRÍCULA</h1><p class="subtitulo">Protocolo de Inscrição em Curso</p></div><div class="brasao">📋</div></div>'
267				+ '<div class="corpo">'
268				+ '<div class="numero-protocolo"><div class="label">Número do Protocolo</div><div class="valor">' + protocolo + '</div></div>'
269				+ '<div class="secao"><div class="secao-titulo">Dados do Solicitante</div>'
270				+ '<div class="campo"><span class="rotulo">Nome completo:</span><span class="valor">' + escHtml( nome ) + '</span></div>'
271				+ ( email ? '<div class="campo"><span class="rotulo">E-mail:</span><span class="valor">' + escHtml( email ) + '</span></div>' : '' )
272				+ '<div class="campo"><span class="rotulo">WhatsApp:</span><span class="valor">' + formatPhone( telefone ) + '</span></div>'
273				+ '<div class="campo"><span class="rotulo">Nº CREF:</span><span class="valor">' + escHtml( cref ) + '</span></div>'
274				+ ( validadeCip ? '<div class="campo"><span class="rotulo">Validade da CIP:</span><span class="valor">' + escHtml( formatDateBr( validadeCip ) ) + '</span></div>' : '' )
275				+ '</div>'
276				+ '<div class="secao"><div class="secao-titulo">Dados do Serviço</div>'
277				+ '<div class="campo"><span class="rotulo">Serviço:</span><span class="valor">Matrícula em Curso</span></div>'
278				+ '<div class="campo"><span class="rotulo">Curso:</span><span class="valor">' + escHtml( cursoNome ) + '</span></div>'
279				+ '<div class="campo"><span class="rotulo">Data/Hora da Solicitação:</span><span class="valor">' + dataHora + '</span></div>'
280				+ '<div class="campo"><span class="rotulo">Situação:</span><span class="valor" style="color:#059669;font-weight:600;">✓ Matrícula Confirmada</span></div>'
281				+ '</div>'
282				+ '<div class="aviso">⚠️ Este documento é o comprovante oficial da sua matrícula. Guarde-o para apresentação no dia do curs
282o. A autenticidade pode ser verificada pelo código abaixo.</div>'
283				+ '</div>'
284				+ '<div class="rodape"><span>Emitido em: ' + dataHora + '</span><span class="autenticacao">Cód. Autenticação: ' + authCode + '</span></div>'
285				+ '</div></body></html>';
286
287			// Abrir em nova janela para impressão/download
288			var win = window.open( '', '_blank' );
289			if ( win ) {
290				win.document.write( html );
291				win.document.close();
292				setTimeout( function () { win.print(); }, 500 );
293			}
294		}
295
296		function pad( n ) { return n < 10 ? '0' + n : '' + n; }
297		function escHtml( s ) { var d = document.createElement( 'div' ); d.textContent = s; return d.innerHTML; }
298		function formatDateBr( ymd ) {
299			var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec( ymd || '' );
300			return m ? m[3] + '/' + m[2] + '/' + m[1] : ymd;
301		}
302		function formatPhone( p ) {
303			var d = p.replace( /\D/g, '' );
304			if ( d.length === 11 ) return '(' + d.substr(0,2) + ') ' + d.substr(2,5) + '-' + d.substr(7);
305			if ( d.length === 10 ) return '(' + d.substr(0,2) + ') ' + d.substr(2,4) + '-' + d.substr(6);
306			return p;
307		}
308
309		/* ── Submissão AJAX ───────────────────────────────────────────── */
310
311		form.addEventListener( 'submit', function ( e ) {
312			e.preventDefault();
313
314			clearAllErrors();
315			generalErr.setAttribute( 'hidden', '' );
316			generalErr.textContent = '';
317
318			var errors = validateFields();
319			if ( Object.keys( errors ).length > 0 ) {
320				showFieldErrors( errors );
321				return;
322			}
323
324			var body = new FormData();
325			body.append( 'action', 'wcp_enrollment' );
326			body.append( '_wpnonce', nonceEl.value );
327			body.append( 'curso_matricula_id', cursoIdEl.value );
328			body.append( 'curso_source', cursoSrcEl ? cursoSrcEl.value : 'legacy' );
329			body.append( 'nome', fields.nome.value.trim() );
330			body.append( 'cpf', fields.cpf ? fields.cpf.value : '' );
331			body.append( 'email', fields.email ? fields.email.value.trim() : '' );
332			body.append( 'telefone', fields.telefone.value.replace( /\D/g, '' ) );
333			body.append( 'numero_cref', fields.numero_cref ? fields.numero_cref.value.trim() : '' );
334			body.append( 'validade_cip', fields.validade_cip ? fields.validade_cip.value : '' );
335
336			// Campos anti-bot: honeypot (deve ficar vazio) e timestamp de abertura.
337			var hpEl = document.getElementById( 'wcp-website' );
338			body.append( 'website', hpEl ? hpEl.value : '' );
339			body.append( 'wcp_form_ts', tsEl ? tsEl.value : '' );
340
341			submitBtn.disabled    = true;
342			submitBtn.textContent = 'Processando…';
343
344			fetch( wcpEnrollment.ajaxurl, {
345				method:      'POST',
346				body:        body,
347				credentials: 'same-origin'
348			} )
349				.then( function ( response ) {
350					return response.json().then( function ( data ) {
351						return { status: response.status, data: data };
352					} );
353				} )
354				.then( function ( result ) {
355					submitBtn.disabled    = false;
356					submitBtn.textContent = 'Inscrever-se';
357
358					if ( result.data.success ) {
359						var respData = result.data.data || {};
360						form.setAttribute( 'hidden', '' );
361
362						// Exibir tela de sucesso com opção de baixar protocolo
363						successArea.innerHTML = '<div class="wcp-success-content">'
364							+ '<div class="wcp-success-icon">✓</div>'
365							+ '<h3 class="wcp-success-title">Matrícula Confirmada!</h3>'
366							+ '<p class="wcp-success-desc">Sua inscrição foi registrada com sucesso.</p>'
367							+ '<button type="button" class="wcp-btn-protocol" id="wcp-download-protocol">📄 Baixar Comprovante de Matrícula</button>'
368							+ '<p class="wcp-success-hint">
368Guarde o comprovante para apresentação no dia do curso.</p>'
369							+ '</div>';
370						successArea.removeAttribute( 'hidden' );
371
372						// Evento do botão de download
373						var dlBtn = document.getElementById( 'wcp-download-protocol' );
374						if ( dlBtn ) {
375							dlBtn.addEventListener( 'click', function () {
376								generateProtocol( respData );
377							} );
378						}
379					} else if ( result.status === 422 && result.data.data && result.data.data.errors ) {
380						showFieldErrors( result.data.data.errors );
381						if ( result.data.data.message ) { showGeneralError( result.data.data.message ); }
382					} else {
383						var errorMsg = result.data.data && result.data.data.message
384							? result.data.data.message
385							: 'Ocorreu um erro. Tente novamente mais tarde.';
386						showGeneralError( errorMsg );
387					}
388				} )
389				.catch( function () {
390					submitBtn.disabled    = false;
391					submitBtn.textContent = 'Inscrever-se';
392					showGeneralError( 'Erro de comunicação. Verifique sua conexão e tente novamente.' );
393				} );
394		} );
395	} );
396}() );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.