Now.js Framework Documentation

Now.js Framework Documentation

AuthLoadingManager - Loading State Management

EN 31 Oct 2025 01:29

AuthLoadingManager - Loading State Management

Documentation for AuthLoadingManager, the loading state management system for authentication operations in the Now.js Framework

📋 Table of Contents

  1. Overview
  2. Installation and Import
  3. Getting Started
  4. Loading Operations
  5. Loading UI
  6. Progress Tracking
  7. Loading Events
  8. Custom Loading Indicators
  9. Loading Timeouts
  10. Usage Examples
  11. API Reference
  12. Best Practices
  13. 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 object

Getting 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 hidden

Loading 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 identifier
  • options (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 identifier
  • fn (Function) - Async function to track
  • options (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 ID
  • percent (number) - Progress (0-100)

Returns: void

Example:

AuthLoadingManager.updateProgress(operationId, 50);

updateMessage(operationId, message)

Update loading message

Parameters:

  • operationId (string) - Operation ID
  • message (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 ID
  • step (number) - Current step index
  • message (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 ID
  • duration (number) - Additional time (ms)

Returns: void

Example:

AuthLoadingManager.extendTimeout(operationId, 30000);  // +30s

registerIndicator(name, factory)

Register custom loading indicator

Parameters:

  • name (string) - Indicator name
  • factory (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 progress

setContext(operationId, context)

Store context data for operation

Parameters:

  • operationId (string) - Operation ID
  • context (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 hangs

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