| <!DOCTYPE html> |
| <html lang="ro"> |
| <head> |
| <link rel="icon" type="image/svg+xml" href="favicon.svg"> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> |
| <title>SGE | Înregistrare</title> |
| <link rel="stylesheet" href="style.css"> |
| <script src="errors.js"></script> |
| <style> |
| body { display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:100dvh; padding:20px 14px; } |
| .wrap { width:100%; max-width:380px; } |
| |
| .steps { display:flex; align-items:center; justify-content:center; gap:0; margin-bottom:28px; } |
| .step { display:flex; flex-direction:column; align-items:center; gap:5px; } |
| .step-dot { |
| width:28px; height:28px; border-radius:50%; |
| border:1.5px solid rgba(255,255,255,0.15); |
| display:flex; align-items:center; justify-content:center; |
| font-size:10px; color:var(--white-dim); letter-spacing:0; |
| transition:all 0.4s ease; |
| } |
| .step-dot.active { border-color:var(--white); color:var(--white); background:rgba(255,255,255,0.08); } |
| .step-dot.done { border-color:rgba(60,120,60,0.6); background:rgba(20,50,20,0.4); color:#5a9a5a; } |
| .step-label { font-size:8px; letter-spacing:1px; color:var(--white-faint); white-space:nowrap; } |
| .step-label.active { color:var(--white-dim); } |
| .step-line { width:28px; height:1px; background:rgba(255,255,255,0.08); margin:0 4px; margin-bottom:14px; } |
| |
| .vpass-card { |
| background:rgba(255,255,255,0.04); |
| backdrop-filter:blur(1px); -webkit-backdrop-filter:blur(1px); |
| border:1px solid rgba(255,255,255,0.09); |
| padding:18px 16px; margin-bottom:16px; |
| display:flex; align-items:center; gap:14px; |
| } |
| .vpass-card .vc-icon img { width:28px; height:28px; opacity:0.6; } |
| .vpass-card .vc-name { font-family:'Cormorant Garamond',serif; font-size:18px; font-weight:600; } |
| .vpass-card .vc-id { font-size:10px; color:var(--white-dim); letter-spacing:2px; margin-top:2px; } |
| |
| .waiting-indicator { |
| display:flex; align-items:center; gap:10px; |
| padding:14px 16px; background:rgba(255,255,255,0.03); |
| border:1px solid rgba(255,255,255,0.07); margin-bottom:14px; |
| } |
| .pulse-dot { |
| width:8px; height:8px; border-radius:50%; |
| background:rgba(255,255,255,0.4); |
| animation:pulse 2s ease-in-out infinite; flex-shrink:0; |
| } |
| @keyframes pulse { |
| 0%,100% { opacity:0.3; transform:scale(0.8); } |
| 50% { opacity:1; transform:scale(1.2); } |
| } |
| .waiting-indicator .wi-text { font-size:11px; color:var(--white-dim); letter-spacing:1px; } |
| .waiting-indicator .wi-code { font-size:10px; color:var(--white-faint); margin-top:2px; } |
| |
| |
| .phone-wrap { display:flex; gap:0; border:1px solid rgba(255,255,255,0.12); } |
| .phone-prefix { |
| background:rgba(255,255,255,0.06); border:none; border-right:1px solid rgba(255,255,255,0.1); |
| color:var(--white); padding:10px 12px; font-family:'DM Mono',monospace; |
| font-size:12px; letter-spacing:1px; flex-shrink:0; display:flex; align-items:center; |
| } |
| .phone-input { |
| flex:1; background:transparent; border:none; color:var(--white); |
| padding:10px 12px; font-family:'DM Mono',monospace; font-size:13px; |
| letter-spacing:2px; outline:none; |
| } |
| .phone-input::placeholder { color:var(--white-faint); letter-spacing:1px; font-size:11px; } |
| .phone-format { font-size:9px; color:var(--white-faint); letter-spacing:1px; margin-top:5px; } |
| |
| |
| .sms-waiting-box { |
| text-align:center; padding:20px 16px; |
| background:rgba(255,255,255,0.03); |
| border:1px solid rgba(255,255,255,0.07); margin-bottom:14px; |
| } |
| .sms-waiting-box .sw-icon { font-size:28px; margin-bottom:10px; } |
| .sms-waiting-box .sw-title { font-family:'Cormorant Garamond',serif; font-size:17px; letter-spacing:2px; margin-bottom:6px; } |
| .sms-waiting-box .sw-sub { font-size:10px; color:var(--white-dim); letter-spacing:1px; line-height:1.8; } |
| .sms-waiting-box .sw-phone { font-size:13px; color:var(--white); letter-spacing:2px; margin-top:8px; font-family:'DM Mono',monospace; } |
| |
| .phase { display:none; } |
| .phase.active { display:block; } |
| |
| .success-anim { text-align:center; padding:20px 0; } |
| .success-anim svg { width:40px; height:40px; margin:0 auto 12px; display:block; } |
| |
| .err-code { font-size:9px; opacity:0.6; margin-right:4px; letter-spacing:1px; } |
| .footer-mini { text-align:center; margin-top:18px; font-size:9px; color:var(--white-faint); letter-spacing:1px; line-height:2; } |
| </style> |
| <script>if(localStorage.getItem('sge_maintenance')==='1'){window.location.replace('404.html');}</script> |
| </head> |
| <body> |
| <div class="wrap"> |
|
|
| <div style="text-align:center;margin-bottom:22px;" class="fade-in"> |
| <img src="logo.svg" style="width:36px;height:36px;margin:0 auto 10px;display:block;"> |
| <div style="font-family:'Cormorant Garamond',serif;font-size:22px;letter-spacing:3px;">SGE</div> |
| <div style="font-size:9px;letter-spacing:2px;color:var(--white-dim);margin-top:3px;">ÎNREGISTRARE CONT</div> |
| </div> |
|
|
| |
| <div class="steps fade-in-2"> |
| <div class="step"> |
| <div class="step-dot active" id="s1-dot">1</div> |
| <div class="step-label active" id="s1-lbl">IDENTITATE</div> |
| </div> |
| <div class="step-line"></div> |
| <div class="step"> |
| <div class="step-dot" id="s2-dot">2</div> |
| <div class="step-label" id="s2-lbl">TELEFON</div> |
| </div> |
| <div class="step-line"></div> |
| <div class="step"> |
| <div class="step-dot" id="s3-dot">3</div> |
| <div class="step-label" id="s3-lbl">COD SMS</div> |
| </div> |
| <div class="step-line"></div> |
| <div class="step"> |
| <div class="step-dot" id="s4-dot">4</div> |
| <div class="step-label" id="s4-lbl">PAROLĂ</div> |
| </div> |
| </div> |
|
|
| |
| <div class="phase active fade-in-3" id="phase-1"> |
| <div class="vpass-card"> |
| <div class="vc-icon"><img src="logo.svg" alt=""></div> |
| <div> |
| <div class="vc-name" id="ph1-name">—</div> |
| <div class="vc-id" id="ph1-vpass">—</div> |
| </div> |
| </div> |
| <div class="card" style="padding:18px 16px;"> |
| <div class="card-title" style="font-size:15px;">Ești tu?</div> |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:16px;line-height:1.9;"> |
| Verifică că datele de mai sus îți aparțin. La pasul următor vei introduce numărul tău de telefon pentru a primi codul de confirmare prin SMS. |
| </p> |
| <button class="btn-primary" onclick="goToPhone()" style="width:100%;letter-spacing:2px;">DA, SUNT EU — CONTINUĂ →</button> |
| <div class="alert error" id="err-1" style="margin-top:10px;"></div> |
| </div> |
| <div style="text-align:center;margin-top:12px;"> |
| <a href="index.html" style="font-size:10px;color:var(--white-dim);letter-spacing:1px;text-decoration:none;">← înapoi la login</a> |
| </div> |
| </div> |
|
|
| |
| <div class="phase" id="phase-2"> |
| <div class="vpass-card"> |
| <div class="vc-icon"><img src="logo.svg" alt=""></div> |
| <div> |
| <div class="vc-name" id="ph2-name">—</div> |
| <div class="vc-id" id="ph2-vpass">—</div> |
| </div> |
| </div> |
| <div class="card" style="padding:18px 16px;"> |
| <div class="card-title" style="font-size:15px;">Număr de telefon</div> |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:16px;line-height:1.9;"> |
| Introdu numărul tău de telefon. Administratorul îți va transmite codul de confirmare VPass pe acest număr. |
| </p> |
| <div class="field"> |
| <label>Număr de telefon</label> |
| <div class="phone-wrap"> |
| <div class="phone-prefix">+373</div> |
| <input class="phone-input" type="tel" id="phone-input" maxlength="13" placeholder="(69) 048 176" inputmode="tel" oninput="formatPhone(this)"> |
| </div> |
| <div class="phone-format">Format: +373 (##) ### ### · Ex: +373 (69) 048 176</div> |
| </div> |
| <button class="btn-primary" onclick="requestCode()" id="btn-req" style="width:100%;letter-spacing:2px;margin-top:4px;">SOLICITĂ COD →</button> |
| <div class="alert error" id="err-2" style="margin-top:10px;"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="phase" id="phase-3"> |
| <div class="sms-waiting-box"> |
| <div class="sw-icon"> |
| <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="1.5" stroke-linecap="round"> |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> |
| </svg> |
| </div> |
| <div class="sw-title">SMS trimis de administrator</div> |
| <div class="sw-sub">Administratorul a primit cererea ta și va trimite<br>codul de confirmare pe numărul:</div> |
| <div class="sw-phone" id="phone-display">—</div> |
| </div> |
| <div class="waiting-indicator"> |
| <div class="pulse-dot"></div> |
| <div> |
| <div class="wi-text">Așteptăm validarea administratorului...</div> |
| <div class="wi-code" id="wait-timer">—</div> |
| </div> |
| </div> |
| <div class="card" style="padding:18px 16px;"> |
| <div class="card-title" style="font-size:15px;">Introdu codul primit prin SMS</div> |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:14px;line-height:1.9;"> |
| Introduceți cele 4 cifre primite în SMS-ul de confirmare VPass. |
| </p> |
| <div class="field"> |
| <input type="text" id="confirm-input" maxlength="4" placeholder="• • • •" |
| inputmode="numeric" autocomplete="one-time-code" |
| style="font-size:28px;letter-spacing:12px;text-align:center;padding:14px;"> |
| </div> |
| <button class="btn-primary" onclick="verifyCode()" style="width:100%;letter-spacing:2px;margin-top:4px;">VERIFICĂ COD →</button> |
| <div class="alert error" id="err-3" style="margin-top:10px;"></div> |
| <div style="margin-top:12px;text-align:center;"> |
| <button class="btn-ghost" onclick="requestNewCode()" style="font-size:9px;letter-spacing:1px;">Nu am primit SMS — Cod nou</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="phase" id="phase-4"> |
| <div class="vpass-card"> |
| <div class="vc-icon"><img src="logo.svg" alt=""></div> |
| <div> |
| <div class="vc-name" id="ph4-name">—</div> |
| <div class="vc-id" id="ph4-vpass" style="color:#5a9a5a;letter-spacing:2px;">✓ CONFIRMAT PRIN SMS</div> |
| </div> |
| </div> |
| <div class="card" style="padding:18px 16px;"> |
| <div class="card-title" style="font-size:15px;">Setează parola VPass</div> |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:14px;line-height:1.9;"> |
| Alege o parolă de minimum 6 cifre. Aceasta va fi codul tău permanent de autentificare în sistem. |
| </p> |
| <div class="field"> |
| <label>Parolă nouă (min. 6 cifre)</label> |
| <input type="password" id="new-pin" maxlength="6" placeholder="••••••" inputmode="numeric" autocomplete="new-password"> |
| </div> |
| <div class="field"> |
| <label>Confirmă parola</label> |
| <input type="password" id="new-pin2" maxlength="6" placeholder="••••••" inputmode="numeric" autocomplete="new-password"> |
| </div> |
| <button class="btn-primary" onclick="setPassword()" style="width:100%;letter-spacing:2px;margin-top:4px;">ACTIVEAZĂ CONTUL →</button> |
| <div class="alert error" id="err-4" style="margin-top:10px;"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="phase" id="phase-5"> |
| <div class="card" style="padding:28px 20px;"> |
| <div class="success-anim"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="#5a9a5a" stroke-width="1.5" stroke-linecap="round"> |
| <path d="M22 11.08V12a10 10 0 11-5.93-9.14"/> |
| <polyline points="22 4 12 14.01 9 11.01"/> |
| </svg> |
| <div style="font-family:'Cormorant Garamond',serif;font-size:22px;letter-spacing:2px;margin-bottom:6px;">Cont activat</div> |
| <div style="font-size:11px;color:var(--white-dim);letter-spacing:1px;" id="ph5-name">—</div> |
| <div style="font-size:10px;color:var(--white-faint);letter-spacing:2px;margin-top:4px;" id="ph5-vpass">—</div> |
| </div> |
| <div style="margin-top:20px;"> |
| <button class="btn-primary" onclick="window.location.href='index.html'" style="width:100%;letter-spacing:2px;">MERGI LA LOGIN →</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="footer-mini">SGE ©2026 — Victor Rosca</div> |
| </div> |
|
|
| <script type="module"> |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js"; |
| import { getFirestore, collection, addDoc, getDocs, updateDoc, doc, query, where, serverTimestamp } |
| from "https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js"; |
| |
| const cfg = { |
| apiKey:"AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU", authDomain:"vservers1.firebaseapp.com", |
| projectId:"vservers1", storageBucket:"vservers1.firebasestorage.app", |
| messagingSenderId:"42433037358", appId:"1:42433037358:web:fde70fec79542428b60bbf" |
| }; |
| const app = initializeApp(cfg); |
| const db = getFirestore(app); |
| |
| const elevId = sessionStorage.getItem('su_elevId'); |
| const elevNume = sessionStorage.getItem('su_name'); |
| const elevVpass = sessionStorage.getItem('su_vpass'); |
| if (!elevId || !elevNume) { window.location.href = 'index.html'; } |
| |
| |
| ['ph1-name','ph2-name','ph4-name','ph5-name'].forEach(id => { |
| const el = document.getElementById(id); if(el) el.textContent = elevNume; |
| }); |
| ['ph1-vpass','ph2-vpass','ph5-vpass'].forEach(id => { |
| const el = document.getElementById(id); if(el) el.textContent = elevVpass; |
| }); |
| |
| let generatedCode = null; |
| let requestDocId = null; |
| let pollInterval = null; |
| let codeExpiry = null; |
| let phoneNumber = null; |
| |
| |
| function setPhase(n) { |
| document.querySelectorAll('.phase').forEach(p => p.classList.remove('active')); |
| document.getElementById('phase-'+n).classList.add('active'); |
| window.scrollTo({top:0,behavior:'smooth'}); |
| for (let i=1; i<=4; i++) { |
| const dot = document.getElementById(`s${i}-dot`); |
| if (i < n) { dot.classList.remove('active'); dot.classList.add('done'); dot.innerHTML='✓'; } |
| else if (i===n) { dot.classList.add('active'); dot.classList.remove('done'); } |
| else { dot.classList.remove('active','done'); } |
| } |
| } |
| |
| |
| window.formatPhone = function(input) { |
| let v = input.value.replace(/\D/g,''); |
| if (v.length > 9) v = v.slice(0,9); |
| let out = ''; |
| if (v.length >= 2) out = '(' + v.slice(0,2) + ') '; |
| else out = v; |
| if (v.length > 2) out += v.slice(2,5); |
| if (v.length > 5) out += ' ' + v.slice(5,8); |
| if (v.length > 8) out += v.slice(8,9); |
| input.value = out; |
| }; |
| |
| |
| window.goToPhone = function() { setPhase(2); }; |
| |
| |
| window.requestCode = async function() { |
| hideError('err-2'); |
| const raw = document.getElementById('phone-input').value.replace(/\D/g,''); |
| if (raw.length < 8) { showError('err-2','err-021','Număr de telefon incomplet.'); return; } |
| let formatted = '+373 (' + raw.slice(0,2) + ') ' + raw.slice(2,5); |
| if (raw.length > 5) formatted += ' ' + raw.slice(5,8); |
| if (raw.length > 8) formatted += raw.slice(8,9); |
| phoneNumber = formatted; |
| document.getElementById('btn-req').disabled = true; |
| |
| |
| try { |
| const q = query(collection(db,'signup_requests'), where('elevId','==',elevId), where('status','==','pending')); |
| const ex = await getDocs(q); |
| if (!ex.empty) { showError('err-2','err-005'); document.getElementById('btn-req').disabled=false; return; } |
| } catch(e) { showError('err-2','err-026'); document.getElementById('btn-req').disabled=false; return; } |
| |
| |
| try { |
| const q2 = query(collection(db,'elevi'), where('vpassId','==',elevVpass)); |
| const snap = await getDocs(q2); |
| if (!snap.empty && snap.docs[0].data().pin !== null) { |
| showError('err-2','err-023'); document.getElementById('btn-req').disabled=false; return; |
| } |
| } catch(e) {} |
| |
| |
| generatedCode = String(Math.floor(1000 + Math.random() * 9000)); |
| |
| try { |
| const ref = await addDoc(collection(db,'signup_requests'), { |
| elevId, elevVpass, elevNume, telefon: phoneNumber, |
| confirmCode: generatedCode, |
| status: 'approved', |
| timestamp: serverTimestamp() |
| }); |
| requestDocId = ref.id; |
| |
| |
| await addDoc(collection(db,'notificari'), { |
| tip: 'signup_request', |
| elevId, elevVpass, elevNume, |
| telefon: phoneNumber, |
| confirmCode: generatedCode, |
| requestId: ref.id, |
| status: 'approved', |
| citita: false, |
| timestamp: serverTimestamp() |
| }); |
| |
| document.getElementById('phone-display').textContent = phoneNumber; |
| setPhase(3); |
| startTimer(); |
| startPolling(); |
| |
| } catch(e) { showError('err-2','err-025'); document.getElementById('btn-req').disabled=false; } |
| }; |
| |
| function startTimer() { |
| codeExpiry = Date.now() + 10 * 60 * 1000; |
| const el = document.getElementById('wait-timer'); |
| const iv = setInterval(() => { |
| const left = Math.max(0, codeExpiry - Date.now()); |
| const m = Math.floor(left/60000); |
| const s = Math.floor((left%60000)/1000); |
| el.textContent = `Codul expiră în ${m}:${String(s).padStart(2,'0')}`; |
| if (left <= 0) { clearInterval(iv); el.textContent = 'Cod expirat — solicită unul nou'; } |
| }, 1000); |
| } |
| |
| function startPolling() { |
| if (pollInterval) clearInterval(pollInterval); |
| pollInterval = setInterval(async () => { |
| if (!requestDocId) return; |
| try { |
| const q = query(collection(db,'signup_requests'), |
| where('elevId','==',elevId), where('status','==','rejected')); |
| const snap = await getDocs(q); |
| if (!snap.empty) { clearInterval(pollInterval); showError('err-3','err-024'); } |
| } catch(e) {} |
| }, 4000); |
| } |
| |
| |
| window.verifyCode = function() { |
| hideError('err-3'); |
| const input = document.getElementById('confirm-input').value.replace(/\s/g,''); |
| if (!/^\d{4}$/.test(input)) { showError('err-3','err-029'); return; } |
| if (codeExpiry && Date.now() > codeExpiry) { showError('err-3','err-006'); return; } |
| if (input !== generatedCode) { showError('err-3','err-007'); return; } |
| if (pollInterval) clearInterval(pollInterval); |
| setPhase(4); |
| }; |
| |
| window.requestNewCode = async function() { |
| if (pollInterval) clearInterval(pollInterval); |
| generatedCode = null; requestDocId = null; |
| document.getElementById('confirm-input').value = ''; |
| document.getElementById('phone-input').value = ''; |
| document.getElementById('btn-req').disabled = false; |
| setPhase(2); |
| }; |
| |
| |
| window.setPassword = async function() { |
| hideError('err-4'); |
| const p1 = document.getElementById('new-pin').value; |
| const p2 = document.getElementById('new-pin2').value; |
| if (p1.length < 6) { showError('err-4','err-008'); return; } |
| if (p1 !== p2) { showError('err-4','err-008','Parolele nu coincid.'); return; } |
| try { |
| const q = query(collection(db,'elevi'), where('vpassId','==',elevVpass)); |
| const snap = await getDocs(q); |
| if (snap.empty) { showError('err-4','err-002'); return; } |
| await updateDoc(doc(db,'elevi',snap.docs[0].id), { pin: p1, confirmed: true }); |
| if (requestDocId) { |
| try { await updateDoc(doc(db,'signup_requests',requestDocId),{ status:'completed' }); } catch(e){} |
| } |
| sessionStorage.removeItem('su_elevId'); |
| sessionStorage.removeItem('su_name'); |
| sessionStorage.removeItem('su_vpass'); |
| setPhase(5); |
| } catch(e) { showError('err-4','err-025'); } |
| }; |
| </script> |
| <a href="vhelp.html" class="vhelp-fab" title="VHelp"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> |
| <circle cx="9" cy="10" r="0.5" fill="rgba(255,255,255,0.9)"/> |
| <circle cx="12" cy="10" r="0.5" fill="rgba(255,255,255,0.9)"/> |
| <circle cx="15" cy="10" r="0.5" fill="rgba(255,255,255,0.9)"/> |
| </svg> |
| </a> |
| </body> |
| </html> |
|
|