Now.js Framework Documentation
AuthLoadingManager - Loading State Management
AuthLoadingManager - Loading State Management
Documentation for AuthLoadingManager, the loading state management system for authentication operations in the Now.js Framework
📋 Table of Contents
- Overview
- Installation and Import
- Getting Started
- Loading Operations
- Loading UI
- Progress Tracking
- Loading Events
- Custom Loading Indicators
- Loading Timeouts
- Usage Examples
- API Reference
- Best Practices
- Common Pitfalls
Overview
AuthLoadingManager manages loading states for authentication operations, providing feedback to users during operations.
Key Features
- ✅ Operation Tracking: Track loading state for each operation
- ✅ Loading UI: Display loading indicators automatically
- ✅ Progress Tracking: Track progress (percentage)
- ✅ Multiple Indicators: Support multiple types (spinner, progress bar, skeleton)
- ✅ Custom Messages: Configure loading messages
- ✅ Timeout Handling: Handle long-running operations
- ✅ Overlay Support: Full-screen loading overlay
- ✅ Cancel Support: Cancel operations
- ✅ Event System: Events for loading state changes
- ✅ Auto Cleanup: Automatically clean up loading states
When to Use AuthLoadingManager
✅ Use AuthLoadingManager when:
- Need to show loading feedback during authentication
- Need to track operation progress
- Want consistent loading UX
- Need to handle loading timeouts
❌ Don't use when:
- Operations complete very quickly (< 100ms)
- Don't need loading UI
Installation and Import
AuthLoadingManager is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.AuthLoadingManager); // AuthLoadingManager objectGetting Started
Basic Setup
// AuthLoadingManager works automatically with AuthManager
// No separate initialization needed
await AuthManager.init({
enabled: true,
// Loading configuration
loading: {
// Show loading indicators
enabled: true,
// Minimum loading time (prevent flicker)
minDuration: 300,
// Loading timeout (ms)
timeout: 30000,
// Default loading message
defaultMessage: 'Loading...',
// Loading indicator type
indicator: 'spinner', // spinner, progress, skeleton, overlay
// Show overlay
overlay: true,
// Overlay opacity
overlayOpacity: 0.5
}
});
console.log('AuthLoadingManager initialized!');Loading Flow
Operation starts
↓
┌─────────────────────┐
│ Start Loading │
│ - Show indicator │
│ - Emit event │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Operation Running │
│ - Update progress │
│ - Show messages │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Check Timeout │
│ - Cancel if needed │
│ - Show warning │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Operation Complete │
│ - Hide indicator │
│ - Emit event │
│ - Cleanup │
└─────────────────────┘Loading Operations
AuthLoadingManager tracks loading states for these operations:
1. Login Operation
// Show loading during login
await AuthManager.login({
email: 'user@example.com',
password: 'password'
});
// Loading automatically shown and hiddenLoading messages:
- Start: "Logging in..."
- Success: "Login successful!"
- Error: "Login failed"
2. Logout Operation
// Show loading during logout
await AuthManager.logout();Loading messages:
- Start: "Logging out..."
- Success: "Logged out"
3. Token Refresh Operation
// Show loading during token refresh
await AuthManager.refreshToken();Loading messages:
- Start: "Refreshing session..."
- Success: "Session refreshed"
- Error: "Session refresh failed"
4. Session Verification
// Show loading during verification
const isValid = await AuthManager.verifySession();Loading messages:
- Start: "Verifying session..."
- Success: "Session verified"
5. Social Login
// Show loading during OAuth
await AuthManager.loginWithGoogle();Loading messages:
- Start: "Connecting to Google..."
- Success: "Connected!"
6. Registration
// Custom operation with loading
await AuthLoadingManager.track('register', async () => {
const response = await fetch('/api/auth/register', {
method: 'POST',
body: JSON.stringify(userData)
});
return response.json();
});Loading messages:
- Start: "Creating account..."
- Success: "Account created!"
7. Password Reset
await AuthLoadingManager.track('password_reset', async () => {
return await resetPassword(email);
}, {
message: 'Sending reset email...'
});8. Profile Update
await AuthLoadingManager.track('profile_update', async () => {
return await updateProfile(data);
}, {
message: 'Updating profile...',
showProgress: true
});9. Email Verification
await AuthLoadingManager.track('email_verify', async () => {
return await verifyEmail(code);
}, {
message: 'Verifying email...',
timeout: 10000
});Loading UI
1. Spinner Indicator
// Default spinner
await AuthManager.init({
loading: {
indicator: 'spinner',
message: 'Loading...'
}
});HTML Output:
<div class="auth-loading">
<div class="spinner"></div>
<p>Loading...</p>
</div>2. Progress Bar
// Progress bar with percentage
await AuthManager.init({
loading: {
indicator: 'progress',
showPercentage: true
}
});
// Update progress
AuthLoadingManager.updateProgress('login', 50); // 50%HTML Output:
<div class="auth-loading">
<div class="progress-bar">
<div class="progress-fill" style="width: 50%"></div>
</div>
<p>50% Complete</p>
</div>3. Skeleton Loader
// Skeleton loading screen
await AuthManager.init({
loading: {
indicator: 'skeleton',
skeletonType: 'profile' // profile, list, card
}
});HTML Output:
<div class="auth-loading skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>4. Overlay
// Full-screen overlay
await AuthManager.init({
loading: {
indicator: 'overlay',
overlay: true,
overlayOpacity: 0.7,
overlayColor: '#000000'
}
});HTML Output:
<div class="auth-loading-overlay" style="opacity: 0.7">
<div class="loading-content">
<div class="spinner"></div>
<p>Please wait...</p>
</div>
</div>5. Custom Indicator
// Register custom indicator
AuthLoadingManager.registerIndicator('custom', (options) => {
const container = document.createElement('div');
container.className = 'custom-loading';
container.innerHTML = `
<div class="custom-spinner"></div>
<h3>${options.title || 'Loading'}</h3>
<p>${options.message || 'Please wait...'}</p>
`;
return container;
});
// Use custom indicator
await AuthManager.init({
loading: {
indicator: 'custom',
title: 'Authenticating',
message: 'Verifying your credentials...'
}
});Progress Tracking
1. Manual Progress Updates
// Track operation with progress
const operationId = AuthLoadingManager.start('upload', {
message: 'Uploading file...',
showProgress: true
});
// Update progress
AuthLoadingManager.updateProgress(operationId, 25); // 25%
AuthLoadingManager.updateProgress(operationId, 50); // 50%
AuthLoadingManager.updateProgress(operationId, 75); // 75%
AuthLoadingManager.updateProgress(operationId, 100); // 100%
// End loading
AuthLoadingManager.end(operationId);2. Automatic Progress (with XHR)
// Track upload progress automatically
await AuthLoadingManager.trackWithProgress('upload', () => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
AuthLoadingManager.updateProgress('upload', percent);
}
});
xhr.addEventListener('load', () => resolve(xhr.response));
xhr.addEventListener('error', reject);
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}, {
message: 'Uploading...',
showProgress: true
});3. Step Progress
// Multi-step operation
const steps = [
{ name: 'validate', message: 'Validating data...' },
{ name: 'process', message: 'Processing...' },
{ name: 'save', message: 'Saving...' },
{ name: 'notify', message: 'Sending notifications...' }
];
const operationId = AuthLoadingManager.start('multi_step', {
steps: steps,
currentStep: 0
});
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
// Update to current step
AuthLoadingManager.updateStep(operationId, i, step.message);
// Do work
await performStep(step.name);
// Update progress
const progress = ((i + 1) / steps.length) * 100;
AuthLoadingManager.updateProgress(operationId, progress);
}
AuthLoadingManager.end(operationId);4. Indeterminate Progress
// Operation with unknown duration
const operationId = AuthLoadingManager.start('processing', {
message: 'Processing...',
indeterminate: true // Show spinner instead of progress bar
});
await performLongOperation();
AuthLoadingManager.end(operationId);Loading Events
Available Events
// Listen to loading events
// 1. Loading started
document.addEventListener('auth:loading:start', (e) => {
const { operation, options } = e.detail;
console.log(`Loading started: ${operation}`);
});
// 2. Loading ended
document.addEventListener('auth:loading:end', (e) => {
const { operation, duration } = e.detail;
console.log(`Loading ended: ${operation} (${duration}ms)`);
});
// 3. Progress updated
document.addEventListener('auth:loading:progress', (e) => {
const { operation, progress } = e.detail;
console.log(`Progress: ${progress}%`);
});
// 4. Loading timeout
document.addEventListener('auth:loading:timeout', (e) => {
const { operation, timeout } = e.detail;
console.log(`Operation timed out: ${operation}`);
});
// 5. Loading cancelled
document.addEventListener('auth:loading:cancelled', (e) => {
const { operation, reason } = e.detail;
console.log(`Loading cancelled: ${operation}`);
});
// 6. Step changed
document.addEventListener('auth:loading:step', (e) => {
const { operation, step, total } = e.detail;
console.log(`Step ${step}/${total}`);
});Custom Loading Indicators
1. Custom Spinner
// Register custom spinner
AuthLoadingManager.registerIndicator('custom-spinner', (options) => {
const div = document.createElement('div');
div.className = 'custom-loading-spinner';
div.innerHTML = `
<style>
.custom-loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.spinner-circle {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner-text {
margin-top: 1rem;
color: #666;
font-size: 14px;
}
</style>
<div class="spinner-circle"></div>
<div class="spinner-text">${options.message || 'Loading...'}</div>
`;
return div;
});
// Use custom spinner
await AuthManager.init({
loading: {
indicator: 'custom-spinner'
}
});2. Animated Progress Bar
AuthLoadingManager.registerIndicator('animated-progress', (options) => {
const div = document.createElement('div');
div.className = 'animated-progress-bar';
div.innerHTML = `
<style>
.animated-progress-bar {
width: 100%;
max-width: 400px;
margin: 2rem auto;
}
.progress-container {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #45a049);
transition: width 0.3s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.3),
transparent
);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.progress-text {
text-align: center;
margin-top: 0.5rem;
font-size: 14px;
color: #666;
}
</style>
<div class="progress-container">
<div class="progress-fill" style="width: 0%"></div>
</div>
<div class="progress-text">${options.message || 'Loading...'}</div>
`;
// Method to update progress
div.updateProgress = (percent) => {
const fill = div.querySelector('.progress-fill');
const text = div.querySelector('.progress-text');
fill.style.width = `${percent}%`;
text.textContent = `${Math.round(percent)}% ${options.message || ''}`;
};
return div;
});3. Skeleton Screen
AuthLoadingManager.registerIndicator('profile-skeleton', (options) => {
const div = document.createElement('div');
div.className = 'profile-skeleton';
div.innerHTML = `
<style>
.profile-skeleton {
padding: 2rem;
max-width: 600px;
margin: 0 auto;
}
.skeleton-item {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 1rem;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 1rem;
}
.skeleton-line {
height: 16px;
margin-bottom: 0.5rem;
}
.skeleton-line.short {
width: 60%;
}
</style>
<div class="skeleton-avatar skeleton-item"></div>
<div class="skeleton-line skeleton-item"></div>
<div class="skeleton-line short skeleton-item"></div>
<div class="skeleton-line skeleton-item"></div>
`;
return div;
});Loading Timeouts
1. Configure Timeout
// Set global timeout
await AuthManager.init({
loading: {
timeout: 30000, // 30 seconds
timeoutAction: 'cancel' // cancel, notify, or ignore
}
});2. Per-Operation Timeout
// Different timeout for each operation
await AuthLoadingManager.track('slow_operation', async () => {
return await slowApiCall();
}, {
timeout: 60000, // 60 seconds for this operation
timeoutAction: 'notify'
});3. Handle Timeout
// Listen to timeout event
document.addEventListener('auth:loading:timeout', async (e) => {
const { operation, timeout } = e.detail;
console.log(`Operation ${operation} timed out after ${timeout}ms`);
// Ask user to continue or cancel
const result = await showTimeoutDialog({
message: 'Operation is taking longer than expected.',
options: ['Continue Waiting', 'Cancel']
});
if (result === 'Cancel') {
AuthLoadingManager.cancel(operation);
} else {
// Extend timeout
AuthLoadingManager.extendTimeout(operation, 30000);
}
});4. Cancel Operation
// Start cancellable operation
const operationId = AuthLoadingManager.start('long_task', {
message: 'Processing...',
cancellable: true
});
// Show cancel button
showCancelButton(() => {
AuthLoadingManager.cancel(operationId);
});
// Do work
try {
await performLongTask();
AuthLoadingManager.end(operationId);
} catch (error) {
if (error.cancelled) {
console.log('Operation cancelled by user');
} else {
throw error;
}
}Usage Examples
1. Login with Custom Loading
// Custom login loading UI
async function handleLogin(e) {
e.preventDefault();
const email = emailInput.value;
const password = passwordInput.value;
// Start custom loading
const loadingId = AuthLoadingManager.start('login', {
message: 'Signing you in...',
indicator: 'custom-spinner',
overlay: true
});
try {
// Perform login
await AuthManager.login({ email, password });
// Update message on success
AuthLoadingManager.updateMessage(loadingId, 'Success! Redirecting...');
// Wait a bit to show success message
await sleep(1000);
// End loading
AuthLoadingManager.end(loadingId);
// Redirect
await Router.navigate('/dashboard');
} catch (error) {
// End loading
AuthLoadingManager.end(loadingId);
// Show error
showNotification(error.message, 'error');
}
}
// Helper
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}2. Multi-Step Registration
async function handleRegistration(userData) {
const steps = [
{ name: 'validate', message: 'Validating information...' },
{ name: 'create', message: 'Creating account...' },
{ name: 'verify', message: 'Verifying email...' },
{ name: 'complete', message: 'Finalizing setup...' }
];
const loadingId = AuthLoadingManager.start('register', {
steps: steps,
indicator: 'animated-progress',
overlay: true
});
try {
// Step 1: Validate
AuthLoadingManager.updateStep(loadingId, 0, steps[0].message);
await validateUserData(userData);
AuthLoadingManager.updateProgress(loadingId, 25);
// Step 2: Create account
AuthLoadingManager.updateStep(loadingId, 1, steps[1].message);
const user = await createAccount(userData);
AuthLoadingManager.updateProgress(loadingId, 50);
// Step 3: Send verification email
AuthLoadingManager.updateStep(loadingId, 2, steps[2].message);
await sendVerificationEmail(user.email);
AuthLoadingManager.updateProgress(loadingId, 75);
// Step 4: Complete setup
AuthLoadingManager.updateStep(loadingId, 3, steps[3].message);
await completeUserSetup(user);
AuthLoadingManager.updateProgress(loadingId, 100);
// Success message
AuthLoadingManager.updateMessage(loadingId, 'Registration complete!');
await sleep(1500);
AuthLoadingManager.end(loadingId);
// Redirect to dashboard
await Router.navigate('/dashboard');
} catch (error) {
AuthLoadingManager.end(loadingId);
showNotification(error.message, 'error');
}
}3. File Upload with Progress
async function uploadProfilePicture(file) {
const loadingId = AuthLoadingManager.start('upload', {
message: 'Uploading profile picture...',
showProgress: true,
cancellable: true
});
// Show cancel button
const cancelBtn = showCancelButton();
cancelBtn.onclick = () => {
AuthLoadingManager.cancel(loadingId);
};
try {
// Create FormData
const formData = new FormData();
formData.append('file', file);
// Upload with progress
const response = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// Track upload progress
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
AuthLoadingManager.updateProgress(loadingId, percent);
}
});
// Handle completion
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject(new Error('Upload failed'));
}
});
xhr.addEventListener('error', () => reject(new Error('Network error')));
xhr.addEventListener('abort', () => reject(new Error('Upload cancelled')));
// Send request
xhr.open('POST', '/api/profile/picture');
xhr.send(formData);
// Store xhr for cancellation
AuthLoadingManager.setContext(loadingId, { xhr });
});
// Success
AuthLoadingManager.updateMessage(loadingId, 'Upload complete!');
await sleep(1000);
AuthLoadingManager.end(loadingId);
// Update UI
updateProfilePicture(response.url);
showNotification('Profile picture updated!', 'success');
} catch (error) {
AuthLoadingManager.end(loadingId);
if (error.message === 'Upload cancelled') {
showNotification('Upload cancelled', 'info');
} else {
showNotification(error.message, 'error');
}
} finally {
hideCancelButton();
}
}
// Cancel upload
document.addEventListener('auth:loading:cancel', (e) => {
const { operation, context } = e.detail;
if (operation === 'upload' && context.xhr) {
context.xhr.abort();
}
});4. Session Verification with Skeleton
async function initializeApp() {
// Show skeleton while verifying session
const loadingId = AuthLoadingManager.start('init', {
indicator: 'profile-skeleton',
message: 'Loading your profile...'
});
try {
// Verify session
const isAuthenticated = await AuthManager.verifySession();
if (isAuthenticated) {
// Load user data
const user = await loadUserData();
// Load user preferences
const preferences = await loadUserPreferences();
// Initialize app
await initializeAppWithUser(user, preferences);
// End loading
AuthLoadingManager.end(loadingId);
// Show main app
showMainApp();
} else {
// Not authenticated
AuthLoadingManager.end(loadingId);
// Show login screen
await Router.navigate('/login');
}
} catch (error) {
AuthLoadingManager.end(loadingId);
showErrorScreen(error);
}
}
// Run on page load
initializeApp();5. Batch Operation with Progress
async function processUserData(users) {
const total = users.length;
const loadingId = AuthLoadingManager.start('batch_process', {
message: `Processing ${total} users...`,
showProgress: true,
indeterminate: false
});
try {
let processed = 0;
let failed = 0;
for (const user of users) {
try {
// Process user
await processUser(user);
processed++;
} catch (error) {
console.error(`Failed to process user ${user.id}:`, error);
failed++;
}
// Update progress
const progress = (processed / total) * 100;
AuthLoadingManager.updateProgress(loadingId, progress);
// Update message
AuthLoadingManager.updateMessage(
loadingId,
`Processing: ${processed}/${total} (${failed} failed)`
);
}
// Complete
AuthLoadingManager.updateProgress(loadingId, 100);
AuthLoadingManager.updateMessage(
loadingId,
`Complete! Processed ${processed}, Failed ${failed}`
);
await sleep(2000);
AuthLoadingManager.end(loadingId);
// Show summary
showBatchSummary({ processed, failed });
} catch (error) {
AuthLoadingManager.end(loadingId);
showNotification('Batch processing failed', 'error');
}
}6. Timeout with User Choice
async function performLongOperation() {
const loadingId = AuthLoadingManager.start('long_op', {
message: 'This may take a while...',
timeout: 15000, // 15 seconds
cancellable: true
});
try {
// Start operation
const operation = startLongOperation();
// Store for cancellation
AuthLoadingManager.setContext(loadingId, { operation });
// Wait for completion
const result = await operation;
AuthLoadingManager.end(loadingId);
return result;
} catch (error) {
AuthLoadingManager.end(loadingId);
throw error;
}
}
// Handle timeout
document.addEventListener('auth:loading:timeout', async (e) => {
const { operation } = e.detail;
if (operation === 'long_op') {
// Show dialog
const choice = await showDialog({
title: 'Taking Longer Than Expected',
message: 'The operation is taking longer than usual. What would you like to do?',
buttons: [
{ label: 'Keep Waiting', value: 'wait' },
{ label: 'Cancel', value: 'cancel', variant: 'secondary' }
]
});
if (choice === 'cancel') {
AuthLoadingManager.cancel(operation);
} else {
// Extend timeout by another 15 seconds
AuthLoadingManager.extendTimeout(operation, 15000);
}
}
});
// Handle cancellation
document.addEventListener('auth:loading:cancelled', (e) => {
const { operation, context } = e.detail;
if (operation === 'long_op' && context.operation) {
context.operation.cancel();
}
});API Reference
Methods
start(operation, options)
Start tracking loading state for an operation
Parameters:
operation(string) - Operation identifieroptions(Object) - Loading options
Returns: string - Operation ID
Example:
const id = AuthLoadingManager.start('login', {
message: 'Logging in...',
indicator: 'spinner',
overlay: true
});end(operationId)
End loading state for an operation
Parameters:
operationId(string) - Operation ID
Returns: void
Example:
AuthLoadingManager.end(operationId);track(operation, fn, options)
Track an async function with loading state
Parameters:
operation(string) - Operation identifierfn(Function) - Async function to trackoptions(Object) - Loading options
Returns: Promise<any> - Function result
Example:
const result = await AuthLoadingManager.track('api_call', async () => {
return await fetch('/api/data').then(r => r.json());
}, {
message: 'Loading data...'
});updateProgress(operationId, percent)
Update progress percentage
Parameters:
operationId(string) - Operation IDpercent(number) - Progress (0-100)
Returns: void
Example:
AuthLoadingManager.updateProgress(operationId, 50);updateMessage(operationId, message)
Update loading message
Parameters:
operationId(string) - Operation IDmessage(string) - New message
Returns: void
Example:
AuthLoadingManager.updateMessage(operationId, 'Almost done...');updateStep(operationId, step, message)
Update current step in multi-step operation
Parameters:
operationId(string) - Operation IDstep(number) - Current step indexmessage(string) - Step message
Returns: void
Example:
AuthLoadingManager.updateStep(operationId, 2, 'Processing data...');cancel(operationId)
Cancel an operation
Parameters:
operationId(string) - Operation ID
Returns: void
Example:
AuthLoadingManager.cancel(operationId);extendTimeout(operationId, duration)
Extend operation timeout
Parameters:
operationId(string) - Operation IDduration(number) - Additional time (ms)
Returns: void
Example:
AuthLoadingManager.extendTimeout(operationId, 30000); // +30sregisterIndicator(name, factory)
Register custom loading indicator
Parameters:
name(string) - Indicator namefactory(Function) - Factory function returning DOM element
Returns: void
Example:
AuthLoadingManager.registerIndicator('custom', (options) => {
const div = document.createElement('div');
div.innerHTML = '<div class="custom-loader"></div>';
return div;
});isLoading(operation)
Check if operation is loading
Parameters:
operation(string) - Operation identifier
Returns: boolean
Example:
if (AuthLoadingManager.isLoading('login')) {
console.log('Login in progress');
}getLoadingState(operation)
Get loading state for operation
Parameters:
operation(string) - Operation identifier
Returns: Object|null - Loading state or null
Example:
const state = AuthLoadingManager.getLoadingState('login');
console.log(state.progress); // Current progresssetContext(operationId, context)
Store context data for operation
Parameters:
operationId(string) - Operation IDcontext(Object) - Context data
Returns: void
Example:
AuthLoadingManager.setContext(operationId, { xhr, controller });Best Practices
1. Set Minimum Duration to Prevent Flicker
// ✅ Good - prevent flash of loading
await AuthManager.init({
loading: {
minDuration: 300 // Show for at least 300ms
}
});
// ❌ Bad - loading flickers for fast operations
{
minDuration: 0
}2. Use Appropriate Indicators
// ✅ Good - appropriate indicators
const indicators = {
'login': 'spinner', // Quick operation
'upload': 'progress', // Trackable progress
'init': 'skeleton', // Loading UI structure
'batch': 'progress' // Batch with progress
};
// ❌ Bad - wrong indicator
{
'upload': 'skeleton' // Should show progress!
}3. Provide Meaningful Messages
// ✅ Good - clear messages
{
message: 'Uploading profile picture...'
}
// ❌ Bad - generic message
{
message: 'Loading...' // What is loading?
}4. Handle Timeouts Gracefully
// ✅ Good - handle timeout
document.addEventListener('auth:loading:timeout', async (e) => {
const choice = await askUser('Continue or Cancel?');
if (choice === 'continue') {
AuthLoadingManager.extendTimeout(e.detail.operation, 30000);
} else {
AuthLoadingManager.cancel(e.detail.operation);
}
});
// ❌ Bad - ignore timeout
// Operation just hangsCommon Pitfalls
1. Not Ending Loading States
// ❌ Bad - loading never ends
const id = AuthLoadingManager.start('operation');
await doWork();
// Forgot to call AuthLoadingManager.end(id)!
// ✅ Good - always end loading
const id = AuthLoadingManager.start('operation');
try {
await doWork();
} finally {
AuthLoadingManager.end(id); // Always ends
}2. Forgetting Error Handling
// ❌ Bad - loading stays on error
const id = AuthLoadingManager.start('login');
await AuthManager.login(credentials); // May throw!
AuthLoadingManager.end(id);
// ✅ Good - end loading on error too
const id = AuthLoadingManager.start('login');
try {
await AuthManager.login(credentials);
} catch (error) {
// Handle error
} finally {
AuthLoadingManager.end(id);
}3. Too Many Concurrent Loadings
// ❌ Bad - multiple overlays
for (const item of items) {
const id = AuthLoadingManager.start('process', { overlay: true });
await process(item);
AuthLoadingManager.end(id);
}
// Overlays stack up!
// ✅ Good - single loading for batch
const id = AuthLoadingManager.start('batch', { overlay: true });
for (let i = 0; i < items.length; i++) {
await process(items[i]);
AuthLoadingManager.updateProgress(id, (i / items.length) * 100);
}
AuthLoadingManager.end(id);Related Documentation
- Authentication.md - Authentication system overview
- AuthManager.md - Core authentication manager
- AuthGuard.md - Route protection
- AuthErrorHandler.md - Error handling
- TokenService.md - JWT token management