Files
Job-Info/NewApproach/auth/job-info/ui.js
T
2026-01-10 19:23:47 +00:00

243 lines
7.6 KiB
JavaScript

/**
* Token Status UI - Standalone
* Displays token statuses, expiration times, last 4 digits
* No external imports - uses window.JobInfoAuth from global scope
*/
class TokenStatusUI {
constructor() {
this.container = null;
this.refreshInterval = null;
}
/**
* Create and show token status UI
*/
create(containerId = 'app') {
console.log('[TokenUI] create() called with container:', containerId);
this.container = document.getElementById(containerId);
if (!this.container) {
console.error('[TokenUI] Container not found:', containerId);
return;
}
this.render();
// Refresh UI every second to update expiration countdown
this.refreshInterval = window.setInterval(() => {
this.render();
}, 1000);
console.log('[TokenUI] UI created and refresh interval started');
}
/**
* Render token status UI
*/
render() {
if (!this.container) return;
const state = this.getState();
const pbStatus = state.pbUser.active ? '✓ Active' : '✗ Inactive';
const pbStatusColor = state.pbUser.active ? '#10b981' : '#ef4444';
const graphStatus = state.graphUser.active ? '✓ Active' : '○ Not Required';
const graphStatusColor = state.graphUser.active ? '#10b981' : '#6b7280';
this.container.innerHTML = `
<div style="
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
">
<div style="
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 40px;
max-width: 600px;
width: 100%;
">
<!-- Header -->
<div style="margin-bottom: 30px;">
<h1 style="font-size: 28px; font-weight: bold; color: #1f2937; margin: 0 0 10px 0;">
Job Info Auth
</h1>
<p style="color: #6b7280; margin: 0; font-size: 14px;">
User: <strong>${state.userDisplayName}</strong>
</p>
</div>
<!-- PocketBase User Token -->
<div style="
margin-bottom: 24px;
padding: 20px;
background: #f9fafb;
border-radius: 8px;
border-left: 4px solid ${pbStatusColor};
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h2 style="font-size: 14px; font-weight: 600; color: #1f2937; margin: 0;">
PocketBase User Token
</h2>
<span style="color: ${pbStatusColor}; font-weight: 600; font-size: 13px;">
${pbStatus}
</span>
</div>
<p style="color: #6b7280; font-size: 12px; margin: 8px 0; font-family: monospace;">
Token: ...${state.pbUser.lastFour}
</p>
<p style="color: #6b7280; font-size: 12px; margin: 8px 0;">
Expires in: <strong style="color: #1f2937;">${state.pbUser.expiresIn}</strong>
</p>
</div>
<!-- Graph User Token -->
<div style="
margin-bottom: 24px;
padding: 20px;
background: #f9fafb;
border-radius: 8px;
border-left: 4px solid ${graphStatusColor};
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h2 style="font-size: 14px; font-weight: 600; color: #1f2937; margin: 0;">
Microsoft Graph Token
</h2>
<span style="color: ${graphStatusColor}; font-weight: 600; font-size: 13px;">
${graphStatus}
</span>
</div>
${state.graphUser.active ? `
<p style="color: #6b7280; font-size: 12px; margin: 8px 0; font-family: monospace;">
Token: ...${state.graphUser.lastFour}
</p>
<p style="color: #6b7280; font-size: 12px; margin: 8px 0;">
Expires in: <strong style="color: #1f2937;">${state.graphUser.expiresIn}</strong>
</p>
` : `
<p style="color: #6b7280; font-size: 12px; margin: 8px 0;">
Graph token is optional. Login proceeded without it.
</p>
`}
</div>
<!-- Actions -->
<div style="display: flex; gap: 10px;">
<button onclick="window.JobInfoAuth.logout(); location.reload();" style="
flex: 1;
padding: 12px;
background: #ef4444;
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
font-size: 14px;
">
Logout
</button>
<button onclick="location.reload();" style="
flex: 1;
padding: 12px;
background: #3b82f6;
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
font-size: 14px;
">
Refresh
</button>
</div>
<!-- Debug Info -->
<div style="
margin-top: 30px;
padding: 15px;
background: #f0f9ff;
border-radius: 6px;
border: 1px solid #bfdbfe;
font-size: 11px;
color: #1e40af;
font-family: monospace;
word-break: break-all;
">
<strong>Debug Info:</strong><br>
pb-user: ${state.pbUser.active ? 'active' : 'inactive'} | graph-user: ${state.graphUser.active ? 'active' : 'inactive'}
</div>
</div>
</div>
`;
}
/**
* Get current token state
*/
getState() {
if (!window.JobInfoAuth) {
return {
pbUser: { active: false, lastFour: 'none', expiresIn: 'unknown' },
graphUser: { active: false, lastFour: 'none', expiresIn: 'unknown' },
userDisplayName: 'Unknown'
};
}
const auth = window.JobInfoAuth;
const authState = auth.getState();
const calculateTimeLeft = (expiresAt) => {
if (!expiresAt) return 'unknown';
const now = Date.now();
const diff = expiresAt - now;
if (diff < 0) return 'expired';
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
return {
pbUser: {
active: !!authState['pb-user'],
lastFour: authState['pb-user']?.lastFourDigits || 'none',
expiresIn: calculateTimeLeft(authState['pb-user']?.expiresAt),
expiresAt: authState['pb-user']?.expiresAt
},
graphUser: {
active: !!authState['graph-user'],
lastFour: authState['graph-user']?.lastFourDigits || 'none',
expiresIn: calculateTimeLeft(authState['graph-user']?.expiresAt),
expiresAt: authState['graph-user']?.expiresAt
},
userDisplayName: authState.userDisplayName || 'Unknown'
};
}
/**
* Destroy UI and cleanup
*/
destroy() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
}
// Create singleton instance
const TokenUI = new TokenStatusUI();
// Expose globally
if (typeof window !== 'undefined') {
window.TokenUI = TokenUI;
}