sls/resources/js/app.js
kushal.saha e4fabef1d3 feat(interview): add icalendar invites, white date pickers, and drawer validation feedback
- 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
2026-08-27 10:16:46 +00:00

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);
});
};