sls/resources/js/app.js
kushal.saha 1541c8193e feat(interview): add candidate resume upload, lightbox viewer, and panelist email attachments
- Add resume file upload to schedule drawer with instant preview and validation
- Create resume lightbox modal viewer with background scroll lock and CV SVG icon
- Integrate CV tab into inspector panel with enlarge action and state 
- Attach candidate resume to panelist notification emails when 
- Fix Outlook RFC 5546 iTIP element mismatch and add table credentials copy button
2026-08-27 11:37:01 +00:00

190 lines
6.5 KiB
JavaScript

import { getMeteredIceServers, initMeteredIceServers, globalIceServers } from './metered.js';
import './interview-call.js';
import './candidate-room.js';
import './candidate-proctor.js';
window.globalIceServers = globalIceServers;
window.getMeteredIceServers = getMeteredIceServers;
window.initMeteredIceServers = initMeteredIceServers;
// Pre-fetch TURN credentials immediately on app boot
if (typeof window !== 'undefined') {
window.getMeteredIceServers();
}
// Global Button Loader Handler
if (typeof document !== 'undefined') {
document.addEventListener('click', function (e) {
const btn = e.target.closest('button[data-show-loader="true"]');
if (!btn || btn.disabled) return;
if (btn.type === 'submit' && btn.form && !btn.form.checkValidity()) {
return;
}
const spinner = btn.querySelector('.btn-spinner');
const icon = btn.querySelector('.btn-icon');
const text = btn.querySelector('.btn-text');
if (spinner) {
spinner.classList.remove('hidden!');
if (icon) icon.classList.add('hidden!');
}
const loadingText = btn.getAttribute('data-loading-text');
if (loadingText && text) {
text.innerText = loadingText;
}
});
}
// Copy Interview Assessment Credentials Handler
window.copyInterviewCredentials = function (btn) {
const text = btn.getAttribute('data-credentials');
if (!text) return;
const copyToClipboard = () => {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
} else {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-999999px';
textarea.style.top = '-999999px';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
return Promise.resolve();
}
};
copyToClipboard().then(() => {
const icon = btn.querySelector('i');
const label = btn.querySelector('.copy-label');
const originalIconClass = icon ? icon.className : '';
const originalText = label ? label.textContent : '';
const originalBtnClass = btn.className;
if (icon) icon.className = 'fa-solid fa-check text-emerald-400 text-[11px]';
if (label) {
label.textContent = 'Copied!';
label.classList.add('text-emerald-300');
}
btn.classList.add('border-emerald-500/50', 'bg-emerald-500/10');
setTimeout(() => {
if (icon) icon.className = originalIconClass;
if (label) {
label.textContent = originalText;
label.classList.remove('text-emerald-300');
}
btn.className = originalBtnClass;
}, 2000);
}).catch(err => {
console.error('Failed to copy credentials: ', err);
});
};
// Candidate Resume Lightbox Previewer
window.openResumeLightbox = function (url, title = 'Candidate Resume') {
if (!url) return;
const modal = document.getElementById('resume-lightbox-modal');
const frame = document.getElementById('resume-lightbox-frame');
const loader = document.getElementById('resume-lightbox-loader');
const titleEl = document.getElementById('resume-lightbox-title');
const newTabBtn = document.getElementById('resume-lightbox-newtab');
if (!modal || !frame) return;
if (titleEl) titleEl.textContent = title;
if (newTabBtn) newTabBtn.href = url;
if (loader) loader.classList.remove('hidden');
frame.classList.add('opacity-0');
frame.onload = function () {
if (loader) loader.classList.add('hidden');
frame.classList.remove('opacity-0');
};
frame.src = url;
modal.classList.add('active');
// Disable background page scrolling
if (typeof document !== 'undefined') {
document.body.classList.add('overflow-hidden');
document.documentElement.classList.add('overflow-hidden');
}
};
window.closeResumeLightbox = function (e = null) {
if (e && e.stopPropagation) {
e.stopPropagation();
}
const modal = document.getElementById('resume-lightbox-modal');
const frame = document.getElementById('resume-lightbox-frame');
if (modal) modal.classList.remove('active');
if (frame) frame.src = 'about:blank';
// Restore background page scrolling
if (typeof document !== 'undefined') {
document.body.classList.remove('overflow-hidden');
document.documentElement.classList.remove('overflow-hidden');
}
};
// Close on Escape key
if (typeof document !== 'undefined') {
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
window.closeResumeLightbox();
}
});
}
// Drawer Candidate Resume Upload Handlers
window.handleDrawerResumeSelect = function (input) {
if (!input || !input.files || !input.files[0]) return;
const file = input.files[0];
const dropzone = document.getElementById('drawer-resume-dropzone');
const selectedBadge = document.getElementById('drawer-resume-selected');
const filenameEl = document.getElementById('drawer-resume-filename');
const filesizeEl = document.getElementById('drawer-resume-filesize');
if (filenameEl) filenameEl.textContent = file.name;
if (filesizeEl) {
const sizeKb = file.size / 1024;
filesizeEl.textContent = sizeKb >= 1024
? (sizeKb / 1024).toFixed(2) + ' MB'
: sizeKb.toFixed(1) + ' KB';
}
if (dropzone) dropzone.classList.add('hidden');
if (selectedBadge) selectedBadge.classList.remove('hidden');
};
window.previewSelectedDrawerResume = function () {
const input = document.getElementById('drawer_resume_input');
if (!input || !input.files || !input.files[0]) return;
const file = input.files[0];
const objectUrl = URL.createObjectURL(file);
window.openResumeLightbox(objectUrl, `${file.name} (Uploaded Preview)`);
};
window.clearSelectedDrawerResume = function () {
const input = document.getElementById('drawer_resume_input');
const dropzone = document.getElementById('drawer-resume-dropzone');
const selectedBadge = document.getElementById('drawer-resume-selected');
if (input) input.value = '';
if (selectedBadge) selectedBadge.classList.add('hidden');
if (dropzone) dropzone.classList.remove('hidden');
};