Spaces:
Sleeping
Sleeping
| <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>VSERVERS | Resetare Parolă</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:360px; } | |
| .phone-wrap { display:flex; 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; 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); font-size:11px; } | |
| .phase { display:none; animation:fadeSlideIn 0.3s ease forwards; } | |
| .phase.active { display:block; } | |
| @keyframes fadeSlideIn { from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);} } | |
| .sms-box { text-align:center; padding:20px 16px; background:var(--glass); border:1px solid var(--glass-border); margin-bottom:14px; } | |
| .sms-box .sb-title { font-family:'Cormorant Garamond',serif; font-size:17px; letter-spacing:2px; margin-bottom:6px; } | |
| .sms-box .sb-num { font-family:'DM Mono',monospace; font-size:14px; letter-spacing:2px; margin-top:8px; } | |
| .waiting-indicator { display:flex; align-items:center; gap:10px; padding:12px 14px; background:var(--glass); border:1px solid var(--glass-border); 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);} } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <div style="text-align:center;margin-bottom:22px;" class="fade-in"> | |
| <img src="logo.svg" style="width:34px;height:34px;margin:0 auto 10px;display:block;"> | |
| <div style="font-family:'Cormorant Garamond',serif;font-size:22px;letter-spacing:3px;">VSERVERS</div> | |
| <div style="font-size:9px;letter-spacing:2px;color:var(--white-dim);margin-top:3px;">RESETARE PAROLĂ</div> | |
| </div> | |
| <!-- STEPS --> | |
| <div class="steps fade-in-2"> | |
| <div class="step"><div class="step-dot active" id="s1-dot">1</div><div class="step-label active">IDENTIFICARE</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">SMS</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">PAROLĂ NOUĂ</div></div> | |
| </div> | |
| <!-- PHASE 1 --> | |
| <div class="phase active fade-in-3" id="phase-1"> | |
| <div class="card" style="padding:18px 16px;"> | |
| <div class="card-title" style="font-size:15px;">Identificare cont</div> | |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:16px;line-height:1.9;"> | |
| Selectează contul tău și introdu numărul de telefon pentru a primi un cod de resetare. | |
| </p> | |
| <div class="field"> | |
| <label>Contul tău</label> | |
| <select id="elev-sel"><option value="">— selectează —</option></select> | |
| </div> | |
| <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-in" maxlength="9" placeholder="(69) 048 176" inputmode="tel" oninput="fmtPhone(this)"> | |
| </div> | |
| <div style="font-size:9px;color:var(--white-faint);margin-top:5px;letter-spacing:1px;">Ex: +373 (69) 048 176</div> | |
| </div> | |
| <button class="btn-primary" onclick="requestReset()" id="btn-reset" style="width:100%;letter-spacing:2px;margin-top:4px;">SOLICITĂ RESETARE →</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> | |
| <!-- PHASE 2 --> | |
| <div class="phase" id="phase-2"> | |
| <div class="sms-box"> | |
| <div class="sb-title">Cod trimis prin SMS</div> | |
| <div style="font-size:10px;color:var(--white-dim);letter-spacing:1px;line-height:1.9;">Administratorul ți-a transmis un cod de resetare pe numărul:</div> | |
| <div class="sb-num" id="phone-display">—</div> | |
| </div> | |
| <div class="waiting-indicator"> | |
| <div class="pulse-dot"></div> | |
| <div> | |
| <div class="wi-text">Așteptăm confirmarea adminului...</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 din SMS</div> | |
| <div class="field"> | |
| <input type="text" id="code-in" 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="verifyReset()" style="width:100%;letter-spacing:2px;margin-top:4px;">VERIFICĂ COD →</button> | |
| <div class="alert error" id="err-2" style="margin-top:10px;"></div> | |
| <div style="text-align:center;margin-top:12px;"> | |
| <button class="btn-ghost" onclick="backToStep1()" style="font-size:9px;">Nu am primit SMS — Înapoi</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PHASE 3 --> | |
| <div class="phase" id="phase-3"> | |
| <div class="card" style="padding:18px 16px;"> | |
| <div class="card-title" style="font-size:15px;">Parolă nouă</div> | |
| <p style="font-size:11px;color:var(--white-dim);margin-bottom:14px;line-height:1.9;"> | |
| Alege o parolă nouă de minimum 6 cifre. | |
| </p> | |
| <div class="field"> | |
| <label>Parolă nouă (min. 6 cifre)</label> | |
| <input type="password" id="new-pin1" 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="saveNewPin()" style="width:100%;letter-spacing:2px;margin-top:4px;">SALVEAZĂ PAROLA →</button> | |
| <div class="alert error" id="err-3" style="margin-top:10px;"></div> | |
| </div> | |
| </div> | |
| <!-- PHASE 4: Done --> | |
| <div class="phase" id="phase-4"> | |
| <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 class="sa-title">Parolă resetată</div> | |
| <div style="font-size:11px;color:var(--white-dim);letter-spacing:1px;" id="done-name">—</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">VSERVERS ©2026 — Victor Roșca</div> | |
| </div> | |
| <script type="module"> | |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js"; | |
| import { getFirestore, collection, getDocs, addDoc, 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); | |
| let selectedElevId = null, selectedVpass = null, selectedNume = null; | |
| let resetCode = null, resetDocId = null, codeExpiry = null; | |
| // Load elevi | |
| try { | |
| const snap = await getDocs(collection(db,'elevi')); | |
| const sel = document.getElementById('elev-sel'); | |
| const elevi = []; | |
| snap.forEach(d => elevi.push({id:d.id,...d.data()})); | |
| elevi.sort((a,b)=>(a.pozitie||0)-(b.pozitie||0)); | |
| elevi.forEach(e => { | |
| if (!e.pin) return; // doar cei cu cont activ | |
| const o = document.createElement('option'); | |
| o.value = e.id; | |
| o.textContent = `${String(e.pozitie||'').padStart(2,'0')}. ${e.nume}`; | |
| sel.appendChild(o); | |
| }); | |
| } catch(e) {} | |
| 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<=3;i++) { | |
| const d = document.getElementById(`s${i}-dot`); | |
| if (i < n) { d.classList.add('done'); d.innerHTML='✓'; } | |
| else if (i===n) { d.classList.add('active'); } | |
| else { d.classList.remove('active','done'); } | |
| } | |
| } | |
| window.fmtPhone = function(input) { | |
| let v = input.value.replace(/\D/g,''); | |
| if(v.length>8)v=v.slice(0,8); | |
| 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); | |
| input.value=out; | |
| }; | |
| window.requestReset = async function() { | |
| hideError('err-1'); | |
| const id = document.getElementById('elev-sel').value; | |
| const rawPh = document.getElementById('phone-in').value.replace(/\D/g,''); | |
| if (!id) { showError('err-1','err-021','Selectează contul.'); return; } | |
| if (rawPh.length<8){ showError('err-1','err-021','Număr de telefon incomplet.'); return; } | |
| document.getElementById('btn-reset').disabled=true; | |
| const phone = '+373 ('+rawPh.slice(0,2)+') '+rawPh.slice(2,5)+' '+rawPh.slice(5,8); | |
| try { | |
| const snap = await getDocs(query(collection(db,'elevi'))); | |
| const elevDoc = snap.docs.find(d=>d.id===id); | |
| if (!elevDoc) { showError('err-1','err-002'); document.getElementById('btn-reset').disabled=false; return; } | |
| selectedElevId = id; | |
| selectedVpass = elevDoc.data().vpassId; | |
| selectedNume = elevDoc.data().nume; | |
| resetCode = String(Math.floor(1000+Math.random()*9000)); | |
| const ref = await addDoc(collection(db,'notificari'),{ | |
| tip:'reset_request', elevId:id, elevVpass:selectedVpass, elevNume:selectedNume, | |
| telefon:phone, confirmCode:resetCode, | |
| status:'pending', citita:false, timestamp:serverTimestamp() | |
| }); | |
| resetDocId = ref.id; | |
| document.getElementById('phone-display').textContent = phone; | |
| codeExpiry = Date.now() + 10*60*1000; | |
| const tv = document.getElementById('wait-timer'); | |
| const iv = setInterval(()=>{ | |
| const l=Math.max(0,codeExpiry-Date.now()); | |
| const m=Math.floor(l/60000), s=Math.floor((l%60000)/1000); | |
| tv.textContent=`Expiră în ${m}:${String(s).padStart(2,'0')}`; | |
| if(l<=0){clearInterval(iv);tv.textContent='Cod expirat';} | |
| },1000); | |
| setPhase(2); | |
| } catch(e){ showError('err-1','err-025'); document.getElementById('btn-reset').disabled=false; } | |
| }; | |
| window.verifyReset = function() { | |
| hideError('err-2'); | |
| const input = document.getElementById('code-in').value.replace(/\s/g,''); | |
| if(!/^\d{4}$/.test(input)){ showError('err-2','err-029'); return; } | |
| if(codeExpiry && Date.now()>codeExpiry){ showError('err-2','err-006'); return; } | |
| if(input !== resetCode){ showError('err-2','err-007'); return; } | |
| setPhase(3); | |
| }; | |
| window.backToStep1 = function() { | |
| document.getElementById('btn-reset').disabled=false; | |
| setPhase(1); | |
| }; | |
| window.saveNewPin = async function() { | |
| hideError('err-3'); | |
| const p1 = document.getElementById('new-pin1').value; | |
| const p2 = document.getElementById('new-pin2').value; | |
| if(p1.length<6){ showError('err-3','err-008'); return; } | |
| if(p1!==p2){ showError('err-3','err-008','Parolele nu coincid.'); return; } | |
| try { | |
| await updateDoc(doc(db,'elevi',selectedElevId),{pin:p1}); | |
| if(resetDocId) await updateDoc(doc(db,'notificari',resetDocId),{status:'completed',citita:true}); | |
| document.getElementById('done-name').textContent=selectedNume; | |
| // Sterge sesiunea veche | |
| sessionStorage.removeItem('vs_role'); | |
| setPhase(4); | |
| } catch(e){ showError('err-3','err-025'); } | |
| }; | |
| </script> | |
| </body> | |
| </html> | |