Skip to content

Commit b73b7d5

Browse files
authored
Update evalis-platform.html
1 parent 2905c21 commit b73b7d5

1 file changed

Lines changed: 110 additions & 21 deletions

File tree

β€Ževalis-platform.htmlβ€Ž

Lines changed: 110 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,10 @@ <h2>πŸ“’ Post Job</h2>
341341
<div class="proctor-cam hidden" id="proctorCam"><video id="proctorVideo" autoplay muted playsinline></video><div class="cam-label">πŸ”΄ REC</div></div>
342342

343343
<script>
344-
const W="https://solitary-sound-11b9.simpaticohrconsultancy.workers.dev/";
344+
const W="https://solitary-sound-11b9.simpaticohrconsultancy.workers.dev";
345345
const SILENCE_TIMEOUT=3.0,MIN_ANS=10,MAX_RETRIES=3,MAX_VIOLATIONS=3;
346-
const HR_PASSWORD="simpatico2025";
347346
let hrAuth=false;
347+
let curToken=null; // stores token for real-mode interview update
348348
let mode="mock",cv="",lang="en",role="",level="",totalQ=10;
349349
let mainQ=0,isFollowUp=false,chat=[],answers=[];
350350
let rec=null,transcript="",timerInt=null,secs=0;
@@ -402,10 +402,7 @@ <h2>πŸ“’ Post Job</h2>
402402
// ═══════════════════════════════════════
403403
function nav(p){
404404
if(p==='hr'&&!hrAuth){
405-
const pwd=prompt("πŸ”’ Enter HR Admin Password:");
406-
if(!pwd)return;
407-
if(pwd!==HR_PASSWORD){toast("❌ Wrong password!");return}
408-
hrAuth=true;toast("βœ… HR Access granted!");
405+
showHRLogin();return;
409406
}
410407
const pages=['home','jobs','apply','setup','live','results','hr'];
411408
pages.forEach(x=>document.getElementById('page-'+x)?.classList.add('hidden'));
@@ -417,6 +414,48 @@ <h2>πŸ“’ Post Job</h2>
417414
if(p==='hr')loadHR();
418415
}
419416

417+
function showHRLogin(){
418+
let modal=document.getElementById('hrLoginModal');
419+
if(!modal){
420+
modal=document.createElement('div');
421+
modal.id='hrLoginModal';
422+
modal.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.75);backdrop-filter:blur(10px);z-index:9000;display:flex;align-items:center;justify-content:center;padding:20px;';
423+
modal.innerHTML=`<div style="background:rgba(15,23,42,.95);border:1px solid rgba(255,255,255,.1);border-radius:20px;padding:32px;width:100%;max-width:380px;text-align:center;">
424+
<div style="font-size:2rem;margin-bottom:8px;">πŸ”</div>
425+
<h3 style="margin-bottom:6px;color:#f8fafc;">HR Admin Access</h3>
426+
<p style="color:#94a3b8;font-size:0.82rem;margin-bottom:20px;">Enter your HR admin password</p>
427+
<input type="password" id="hrPwdInput" placeholder="Password" style="width:100%;padding:12px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);border-radius:10px;color:#f8fafc;font-size:0.95rem;outline:none;font-family:inherit;margin-bottom:8px;"
428+
onkeydown="if(event.key==='Enter')doHRLogin()">
429+
<div id="hrLoginErr" style="color:#ef4444;font-size:0.8rem;margin-bottom:8px;display:none;"></div>
430+
<button onclick="doHRLogin()" style="width:100%;padding:13px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:12px;color:#fff;font-weight:700;cursor:pointer;font-size:0.95rem;font-family:inherit;margin-bottom:8px;">πŸ”“ Login</button>
431+
<button onclick="document.getElementById('hrLoginModal').style.display='none'" style="width:100%;padding:11px;background:transparent;border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#94a3b8;font-weight:600;cursor:pointer;font-size:0.88rem;font-family:inherit;">Cancel</button>
432+
</div>`;
433+
document.body.appendChild(modal);
434+
modal.addEventListener('click',e=>{if(e.target===modal)modal.style.display='none';});
435+
}
436+
modal.style.display='flex';
437+
setTimeout(()=>document.getElementById('hrPwdInput').focus(),100);
438+
}
439+
440+
function doHRLogin(){
441+
const input=document.getElementById('hrPwdInput');
442+
const err=document.getElementById('hrLoginErr');
443+
const pwd=input.value.trim();
444+
if(!pwd){err.textContent='Enter password';err.style.display='block';return;}
445+
// Check against adminConfig (set by admin dashboard) or fallback default
446+
const cfg=JSON.parse(localStorage.getItem('adminConfig')||'{}');
447+
const correctPwd=cfg.hrPassword||'simpatico2025';
448+
if(pwd!==correctPwd){
449+
err.textContent='❌ Wrong password';err.style.display='block';
450+
input.value='';input.focus();return;
451+
}
452+
hrAuth=true;
453+
document.getElementById('hrLoginModal').style.display='none';
454+
input.value='';err.style.display='none';
455+
toast('βœ… HR Access granted!');
456+
nav('hr');
457+
}
458+
420459
// ═══════════════════════════════════════
421460
// JOBS
422461
// ═══════════════════════════════════════
@@ -431,12 +470,12 @@ <h2>πŸ“’ Post Job</h2>
431470
el.innerHTML=data.map(j=>`<div class="job-card">
432471
<h3>${j.title||''}</h3>
433472
<div class="job-meta">
434-
<span>🏒 ${j["Company Name"]||j.department||''}</span>
473+
<span>🏒 ${j.company_name||j["Company Name"]||j.department||''}</span>
435474
<span>πŸ“ ${j.location||''} ${j.Country?'('+j.Country+')':''}</span>
436475
<span>πŸ“Š ${j.level||'Mid-Level'}</span>
437476
</div>
438477
<p style="font-size:0.82rem;color:var(--muted);margin:6px 0;">${(j.description||'').substring(0,180)}</p>
439-
<div>${(j.skills||[]).map(s=>'<span class="skill-tag">'+s+'</span>').join('')}</div>
478+
<div>${(Array.isArray(j.skills)?j.skills:typeof j.skills==='string'&&j.skills?j.skills.split(',').map(s=>s.trim()).filter(Boolean):[]).map(s=>'<span class="skill-tag">'+s+'</span>').join('')}</div>
440479
<button class="btn btn-go" style="margin-top:10px;padding:10px;"
441480
onclick="applyTo('${j.id}','${(j.title||'').replace(/'/g,"\\'")}')">πŸ“ Apply</button>
442481
</div>`).join('');
@@ -541,7 +580,7 @@ <h3>${j.title||''}</h3>
541580
else if(ext==='pdf')t=await exPDF(f);
542581
else if(ext==='doc'||ext==='docx')t=await exDOC(f);
543582
else t=await f.text();
544-
return t.replace(/\s+/g,' ').replace(/[^\x20-\x7E\n]/g,'').trim().substring(0,5000);
583+
return t.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,'').replace(/\s+/g,' ').trim().substring(0,5000);
545584
}
546585

547586
async function exPDF(f){
@@ -572,8 +611,10 @@ <h3>${j.title||''}</h3>
572611
async function exFB(f){
573612
const b=await f.arrayBuffer();
574613
const t=new TextDecoder('utf-8',{fatal:false}).decode(b);
575-
const r=t.match(/[a-zA-Z0-9@.,:;!?\-/()\s]{5,}/g);
576-
return r?r.filter(s=>(s.match(/[a-zA-Z]/g)||[]).length/s.length>0.5&&s.trim().length>5).join(' '):'';
614+
// Only strip control characters, keep unicode (for Indian language CVs)
615+
const clean=t.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,'');
616+
const r=clean.match(/[\w\u0900-\u097F\u0D00-\u0D7F\u0600-\u06FF\u0400-\u04FF@.,:;!?\-/()\s]{5,}/g);
617+
return r?r.filter(s=>s.trim().length>5).join(' '):'';
577618
}
578619

579620
document.getElementById('cvFile').addEventListener('change',async e=>{
@@ -595,7 +636,7 @@ <h3>${j.title||''}</h3>
595636
const ctrl=new AbortController();
596637
const tm=setTimeout(()=>ctrl.abort(),45000);
597638
try{
598-
const r=await fetch(W,{method:"POST",headers:{"Content-Type":"application/json"},
639+
const r=await fetch(W+"/",{method:"POST",headers:{"Content-Type":"application/json"},
599640
body:JSON.stringify({messages}),signal:ctrl.signal});
600641
clearTimeout(tm);
601642
if(!r.ok)throw new Error('Error '+r.status);
@@ -1008,6 +1049,7 @@ <h3>${j.title||''}</h3>
10081049

10091050
curAppId=interview.application_id;
10101051
curJobId=interview.job_id;
1052+
curToken=token; // βœ… Store for saveResults() UPDATE
10111053

10121054
await db('update','interviews',{
10131055
status:'in_progress',started_at:new Date().toISOString()
@@ -1177,10 +1219,7 @@ <h3>${j.title||''}</h3>
11771219

11781220
async function saveResults(r){
11791221
try{
1180-
if(!curAppId)return;
1181-
const token=crypto.randomUUID?crypto.randomUUID():Date.now().toString(36);
1182-
await db('insert','interviews',{
1183-
application_id:curAppId,job_id:curJobId,token,
1222+
const resultData={
11841223
interview_type:mode,status:'completed',
11851224
interview_role:role,interview_level:level,interview_language:lang,
11861225
question_count:totalQ,
@@ -1191,8 +1230,23 @@ <h3>${j.title||''}</h3>
11911230
strengths:r.strengths||[],improvements:r.improvements||[],
11921231
feedback:r.feedback||[],answers:answers,
11931232
violation_log:violations,violation_count:violations.length
1194-
});
1195-
await db('update','applications',{status:'interviewed'},{id:curAppId});
1233+
};
1234+
1235+
if(mode==='real'&&curToken){
1236+
// UPDATE the existing token-linked interview row (do NOT create duplicate)
1237+
await db('update','interviews',resultData,{token:curToken});
1238+
}else{
1239+
// Mock mode β€” insert a new result row if we have an app context, else skip
1240+
if(curAppId){
1241+
const newToken=crypto.randomUUID?crypto.randomUUID():Date.now().toString(36);
1242+
await db('insert','interviews',{
1243+
...resultData,
1244+
application_id:curAppId,job_id:curJobId,token:newToken
1245+
});
1246+
}
1247+
}
1248+
1249+
if(curAppId)await db('update','applications',{status:'interviewed'},{id:curAppId});
11961250
console.log('[DB] Results saved!');
11971251
}catch(e){console.error('[DB]',e)}
11981252
}
@@ -1329,14 +1383,15 @@ <h3>${j.title}</h3>
13291383
title,
13301384
category:document.getElementById('hrCat').value,
13311385
department:document.getElementById('hrDept').value.trim(),
1386+
company_name:document.getElementById('hrCompany').value.trim(),
13321387
"Company Name":document.getElementById('hrCompany').value.trim(),
13331388
location:document.getElementById('hrLocation').value.trim(),
13341389
level:document.getElementById('hrLevel').value,
13351390
description:document.getElementById('hrJD').value.trim(),
13361391
skills:document.getElementById('hrSkills').value.split(',').map(s=>s.trim()).filter(Boolean),
13371392
question_count:parseInt(document.getElementById('hrQCount').value),
13381393
ats_threshold:parseInt(document.getElementById('hrATS').value),
1339-
is_active:true,status:'active'
1394+
is_active:true,status:'active',created_at:new Date().toISOString()
13401395
});
13411396
toast('βœ… Posted!');
13421397
document.getElementById('hrTitle').value='';
@@ -1411,13 +1466,47 @@ <h3>${j.title}</h3>
14111466
expires_at:new Date(Date.now()+72*3600000).toISOString()
14121467
});
14131468
const url=`${location.origin}${location.pathname}?token=${token}`;
1469+
1470+
// Copy to clipboard
14141471
try{await navigator.clipboard.writeText(url)}catch(e){}
1415-
toast('βœ… Link created!');
1416-
alert(`πŸ“§ Send to ${email}:\n\n${url}\n\n(Copied to clipboard!)`);
1472+
1473+
// Show proper link modal instead of alert()
1474+
let lm=document.getElementById('inviteLinkModal');
1475+
if(!lm){
1476+
lm=document.createElement('div');
1477+
lm.id='inviteLinkModal';
1478+
lm.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.75);backdrop-filter:blur(8px);z-index:9000;display:flex;align-items:center;justify-content:center;padding:20px;';
1479+
lm.innerHTML=`<div style="background:rgba(15,23,42,.95);border:1px solid rgba(255,255,255,.1);border-radius:20px;padding:28px;width:100%;max-width:460px;">
1480+
<h3 style="margin-bottom:6px;color:#f8fafc;">πŸ“§ Interview Invitation Ready</h3>
1481+
<p id="inviteEmailLabel" style="color:#94a3b8;font-size:0.82rem;margin-bottom:16px;"></p>
1482+
<label style="display:block;font-size:0.75rem;color:#94a3b8;margin-bottom:6px;font-weight:600;">INTERVIEW LINK (send this to the candidate)</label>
1483+
<div style="display:flex;gap:8px;">
1484+
<input type="text" id="inviteLinkInput" readonly style="flex:1;padding:10px;background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.3);border-radius:10px;color:#a5b4fc;font-size:0.82rem;outline:none;font-family:monospace;">
1485+
<button onclick="copyInviteLink()" id="inviteCopyBtn" style="padding:10px 16px;background:#6366f1;border:none;border-radius:10px;color:#fff;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;">πŸ“‹ Copy</button>
1486+
</div>
1487+
<p style="margin-top:10px;font-size:0.75rem;color:#64748b;">⏰ Expires in 72 hours β€’ Token is single-use</p>
1488+
<button onclick="document.getElementById('inviteLinkModal').style.display='none'" style="width:100%;padding:12px;margin-top:16px;background:transparent;border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#94a3b8;font-weight:600;cursor:pointer;font-size:0.88rem;font-family:inherit;">Close</button>
1489+
</div>`;
1490+
document.body.appendChild(lm);
1491+
lm.addEventListener('click',e=>{if(e.target===lm)lm.style.display='none';});
1492+
}
1493+
document.getElementById('inviteEmailLabel').textContent='For: '+email;
1494+
document.getElementById('inviteLinkInput').value=url;
1495+
lm.style.display='flex';
1496+
1497+
toast('βœ… Invite link ready & copied!');
14171498
loadCands(selHRJob,'');
14181499
}catch(e){toast('❌ '+e.message)}
14191500
}
14201501

1502+
function copyInviteLink(){
1503+
const input=document.getElementById('inviteLinkInput');
1504+
const btn=document.getElementById('inviteCopyBtn');
1505+
try{navigator.clipboard.writeText(input.value)}catch(e){input.select();document.execCommand('copy')}
1506+
btn.textContent='βœ… Copied!';
1507+
setTimeout(()=>{btn.textContent='πŸ“‹ Copy'},2000);
1508+
}
1509+
14211510
// ═══════════════════════════════════════
14221511
// TOKEN FROM URL
14231512
// ═══════════════════════════════════════

0 commit comments

Comments
Β (0)