feat: optimize pre-enrollment dynamic banners, field uploads and phone masks
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m8s Details

This commit is contained in:
Sidney 2026-05-29 19:43:21 -03:00
parent bd4a2ca0bb
commit f10f213b9e
4 changed files with 75 additions and 13 deletions

View File

@ -15,6 +15,7 @@ interface Props {
const FIELD_TYPES: Record<string, string> = { const FIELD_TYPES: Record<string, string> = {
text: 'Texto', email: 'Email', phone: 'Telefone', cpf: 'CPF', text: 'Texto', email: 'Email', phone: 'Telefone', cpf: 'CPF',
date: 'Data', select: 'Seleção', textarea: 'Texto Longo', number: 'Número', date: 'Data', select: 'Seleção', textarea: 'Texto Longo', number: 'Número',
file: 'Imagem / Documento (Upload)',
banner: 'Folder / Panfleto de Promoção (Banner)' banner: 'Folder / Panfleto de Promoção (Banner)'
}; };

View File

@ -0,0 +1,21 @@
import pg from 'pg';
const DATABASE_URL = 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager';
const pool = new pg.Pool({ connectionString: DATABASE_URL });
async function cleanup() {
try {
const { rowCount } = await pool.query(
`DELETE FROM notas_boletim
WHERE prova_id IS NOT NULL AND prova_id NOT IN (SELECT id FROM provas)`
);
console.log(`[Sucesso] Limpeza concluída: ${rowCount} registros órfãos de notas removidos!`);
} catch (error) {
console.error('[Erro] Falha ao limpar banco de dados:', error);
} finally {
await pool.end();
process.exit();
}
}
cleanup();

View File

@ -79,7 +79,7 @@ function getPreMatriculaHTML(slug) {
body{font-family:'Inter',sans-serif;background:#f8fafc;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px} body{font-family:'Inter',sans-serif;background:#f8fafc;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
.container{max-width:640px;width:100%;background:#fff;border-radius:24px;box-shadow:0 25px 50px -12px rgba(0,0,0,.1);overflow:hidden} .container{max-width:640px;width:100%;background:#fff;border-radius:24px;box-shadow:0 25px 50px -12px rgba(0,0,0,.1);overflow:hidden}
.header{padding:40px 32px 24px;text-align:center} .header{padding:40px 32px 24px;text-align:center}
.header img{width:96px;height:96px;border-radius:50%;margin:0 auto 24px;display:block;object-fit:cover;border:3px solid var(--primary,#4f46e5);box-shadow:0 10px 15px -3px rgba(0,0,0,.1)} .header img{max-height:80px;max-width:240px;margin:0 auto 24px;display:block;object-fit:contain}
.header h1{font-size:28px;font-weight:900;color:#0f172a;margin-bottom:8px} .header h1{font-size:28px;font-weight:900;color:#0f172a;margin-bottom:8px}
.header p{font-size:14px;color:#64748b;line-height:1.6} .header p{font-size:14px;color:#64748b;line-height:1.6}
.form{padding:0 32px 32px} .form{padding:0 32px 32px}
@ -151,12 +151,21 @@ function maskCEP(v){
document.addEventListener('input',function(e){ document.addEventListener('input',function(e){
if(!e.target)return; if(!e.target)return;
const dt=e.target.getAttribute('data-type'); const dt=e.target.getAttribute('data-type');
if(dt==='phone'){ if(dt==='phone' || dt==='cpf' || dt==='cep'){
e.target.value=maskPhone(e.target.value); const originalVal = e.target.value;
}else if(dt==='cpf'){ let newVal = originalVal;
e.target.value=maskCPF(e.target.value); if(dt==='phone') newVal = maskPhone(originalVal);
}else if(dt==='cep'){ else if(dt==='cpf') newVal = maskCPF(originalVal);
e.target.value=maskCEP(e.target.value); else if(dt==='cep') newVal = maskCEP(originalVal);
if (newVal !== originalVal) {
const start = e.target.selectionStart;
e.target.value = newVal;
try {
const diff = newVal.length - originalVal.length;
e.target.setSelectionRange(start + diff, start + diff);
} catch(err) {}
}
} }
}); });
@ -238,7 +247,7 @@ async function init(){
}else if(f.id==='_email'){ }else if(f.id==='_email'){
html+='<input name="_email" type="email" placeholder="'+(f.placeholder||'seu@email.com')+'">'; html+='<input name="_email" type="email" placeholder="'+(f.placeholder||'seu@email.com')+'">';
}else if(f.id==='_telefone'){ }else if(f.id==='_telefone'){
html+='<input name="_telefone" data-type="phone" placeholder="'+(f.placeholder||'(00) 00000-0000')+'">'; html+='<input name="_telefone" data-type="phone" placeholder="'+(f.placeholder||'(00) 00000-0000')+'" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">';
}else if(f.id==='_turma'){ }else if(f.id==='_turma'){
if(data.turmas.length>0){ if(data.turmas.length>0){
html+='<select name="_turma"'+(f.obrigatorio?' required':'')+'><option value="">Selecione a turma...</option>'; html+='<select name="_turma"'+(f.obrigatorio?' required':'')+'><option value="">Selecione a turma...</option>';
@ -278,7 +287,7 @@ async function init(){
}else{ }else{
let t=tipoDetectado; let t=tipoDetectado;
if(t==='phone'||t==='cpf'||t==='cep')t='text'; if(t==='phone'||t==='cpf'||t==='cep')t='text';
let dt=(tipoDetectado==='phone'||tipoDetectado==='cpf'||tipoDetectado==='cep')?(' data-type="'+tipoDetectado+'"'):''; let dt=(tipoDetectado==='phone'||tipoDetectado==='cpf'||tipoDetectado==='cep')?(' data-type="'+tipoDetectado+'" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"'):'';
html+='<input type="'+t+'" name="'+f.id+'"'+(f.obrigatorio?' required':'')+dt+' placeholder="'+(f.placeholder||'')+'">'; html+='<input type="'+t+'" name="'+f.id+'"'+(f.obrigatorio?' required':'')+dt+' placeholder="'+(f.placeholder||'')+'">';
} }
} }

View File

@ -1050,15 +1050,40 @@ app.get('/manifest.json', async (req, res) => {
const name = schoolData.profile?.name || 'EduManager'; const name = schoolData.profile?.name || 'EduManager';
const logo = normalizeStorageUrl(schoolData.logo) || '/vite.svg'; const logo = normalizeStorageUrl(schoolData.logo) || '/vite.svg';
// Detectar dinamicamente o tipo MIME com base na extensão do arquivo de logo (ex: webp, png, svg)
const ext = logo.split('.').pop().split('?')[0].toLowerCase();
let mimeType = 'image/png';
if (ext === 'jpg' || ext === 'jpeg') mimeType = 'image/jpeg';
else if (ext === 'svg') mimeType = 'image/svg+xml';
else if (ext === 'webp') mimeType = 'image/webp';
res.json({ res.json({
"short_name": name, "short_name": name,
"name": `${name} - Portal do Aluno`, "name": `${name} - Portal do Aluno`,
"icons": [ "icons": [
{ {
"src": logo, "src": logo,
"sizes": "192x192 512x512", "sizes": "192x192",
"type": "image/png", "type": mimeType,
"purpose": "any maskable" "purpose": "any"
},
{
"src": logo,
"sizes": "512x512",
"type": mimeType,
"purpose": "any"
},
{
"src": logo,
"sizes": "192x192",
"type": mimeType,
"purpose": "maskable"
},
{
"src": logo,
"sizes": "512x512",
"type": mimeType,
"purpose": "maskable"
} }
], ],
"start_url": "/", "start_url": "/",
@ -1074,7 +1099,13 @@ app.get('/manifest.json', async (req, res) => {
"icons": [ "icons": [
{ {
"src": "/vite.svg", "src": "/vite.svg",
"sizes": "192x192 512x512", "sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/vite.svg",
"sizes": "512x512",
"type": "image/svg+xml", "type": "image/svg+xml",
"purpose": "any" "purpose": "any"
} }