- integrate spatie/icalendar-generator for candidate & panelist .ics invitations (RSVP & 15m alerts)
- add StoreInterviewRequest, ScheduleInterviewDto, and invitation/reminder queued actions
- add migration for job_title, round, description, and reminder_sent_at on interviews table
- style datetime-local picker indicators to pure white on dark input backgrounds
- support button loading spinners with :showLoader="true" and loadingText
- auto-open schedule drawer on validation failure with inline @error alerts and old() bindings
92 lines
3.1 KiB
JavaScript
92 lines
3.1 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);
|
|
});
|
|
};
|
|
|