Skip to content

Login Flow Documentation

Overview

This document describes the simplified login flow using the User Settings API integration for authentication and settings synchronization.

User Settings API Integration

The login flow has been completely redesigned around the User Settings API to provide cross-device sync and simplified data management.

Core Principle

After login, make ONE API call (User Settings API), which returns both user settings and all organizations/projects data. Navigate based on current_organization_id from the API response.


Login Flow Steps

1. User Login

  • User enters credentials and submits login form
  • AuthContext.login() is called with access token, refresh token, and user data

2. Clear Previous Session Data

  • All cached data is cleared from localStorage using clearSessionData()
  • This includes: projects, organizations, notebooks, tools, credentials, etc.
  • Only auth tokens are preserved

3. Store Authentication Data

localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
localStorage.setItem('user', JSON.stringify(user));
setIsLoggedIn(true);

4. Fetch User Settings API

  • Call /user_settings?client=web API endpoint
  • This is the ONLY API call during login
  • Returns user settings AND all organizations/projects data in one response
  • No additional calls to fetchOrganizations or fetchProjects

5. Apply Settings and Store Data

const settings = await UserSettingsService.loadFromAPI('web');

// Apply user settings
if (settings.current_organization_id) {
  localStorage.setItem('currentOrganizationId', settings.current_organization_id);
}
if (settings.theme) {
  setIsDarkMode(settings.theme === 'dark');
  localStorage.setItem('darkMode', settings.theme === 'dark');
}
if (settings.language) {
  i18n.changeLanguage(settings.language);
}

// Transform and store organizations data for backward compatibility
const organizationsData = transformSidebarToOrganizations(settings.sidebar);
localStorage.setItem('organizations', JSON.stringify(organizationsData));

6. Simple Navigation Decision

Scenario A: User Has Current Organization

Condition: settings.current_organization_id exists

if (settings.current_organization_id) {
  console.log('Login successful, navigating to dashboard');
  navigate('/admin/dashboard', { replace: true });
}
Result: User is taken to the dashboard

Scenario B: User Has Organizations But No Current One

Condition: settings.sidebar.organizations.length > 0 but no current_organization_id

if (settings.sidebar.organizations.length > 0) {
  // Set first organization as current
  const firstOrg = settings.sidebar.organizations[0];
  localStorage.setItem('currentOrganizationId', firstOrg.id);
  navigate('/admin/dashboard', { replace: true });
}
Result: User is taken to the dashboard with first organization selected

Scenario C: User Has No Organizations

Condition: settings.sidebar.organizations.length === 0

if (settings.sidebar.organizations.length === 0) {
  console.log('Login successful, navigating to organizations page');
  navigate('/admin/organizations', { replace: true });
}
Result: User is taken to organizations page to create their first organization


What Changed from Previous Implementation

Removed Complexity

  1. Priority-based navigation (Priority 1, 2, 3, 4)
  2. Project-organization mapping during login
  3. Multiple API calls (fetchOrganizations, fetchOrganizationDetails, fetchProjects)
  4. Onboarding redirect logic (onboarding_completed, onboarding_skipped)
  5. Complex data transformation (JSON:API relationships and included data)
  6. Cached data navigation paths
  7. Complex logging with emojis and prefixes

Added Simplicity

  1. Single API call (User Settings API only)
  2. Cross-device sync (settings synchronized automatically)
  3. Direct sidebar rendering (no data transformation needed)
  4. Built-in permissions (meta.can structure included)
  5. Predictable behavior for all users
  6. Simple logging with clear messages

Dashboard Responsibilities

The Dashboard component now handles its own data loading:

const Dashboard = () => {
  useEffect(() => {
    // Read organizations from localStorage (already loaded during login)
    const orgsData = localStorage.getItem('organizations');
    if (orgsData) {
      const parsed = JSON.parse(orgsData);
      setOrganizations(parsed.data || []);
    }

    // Load additional data as needed (wallets, projects, etc.)
    // Each component is responsible for its own data
  }, []);
};

The navigation logic has been simplified to use the User Settings API response:

// In AuthContext.login()
const settings = await UserSettingsService.loadFromAPI('web');

if (settings?.current_organization_id) {
  // User has a current organization - go to dashboard
  navigate('/admin/dashboard', { replace: true });
} else if (settings?.sidebar?.organizations?.length > 0) {
  // User has organizations but no current one - set first as current
  const firstOrg = settings.sidebar.organizations[0];
  localStorage.setItem('currentOrganizationId', firstOrg.id);
  navigate('/admin/dashboard', { replace: true });
} else {
  // User has no organizations - go to organizations page
  navigate('/admin/organizations', { replace: true });
}

Error Handling

Login Errors

try {
  const settings = await UserSettingsService.loadFromAPI('web');
  if (settings) {
    // Apply settings and navigate
  } else {
    // Fall back to old fetchOrganizations flow
    const res = await fetchOrganizations();
    // ... fallback logic
  }
} catch (error) {
  console.error('Login failed:', error);
  setError(t('errors.loginFailed', 'Failed to load user settings. Please try again.'));
  // User stays on login page and can retry
}

Logging Strategy

Simple, Clear Messages

  • "Login successful, navigating to dashboard"
  • "Login successful, navigating to onboarding"
  • "Login failed:" followed by error details

No Complex Patterns

  • No emoji prefixes (🔍, ✅, 🎯)
  • No component prefixes ("AuthContext:")
  • No verbose debug information

Summary Table

Condition Current Org ID Organizations Navigate To Additional API Calls
Has current org Set ≥ 1 /admin/dashboard None during login
No current org Null ≥ 1 /admin/dashboard (set first org) None during login
No organizations Null 0 /admin/organizations None during login

Performance Improvements

Before (Complex Flow)

  • API Calls: 2-3 (fetchOrganizations, fetchOrganizationDetails, sometimes fetchProjects)
  • Data Transformation: Complex JSON:API relationship matching
  • Navigation Logic: ~600 lines
  • localStorage Operations: 20+
  • Time to Dashboard: ~3-5 seconds

After (User Settings API Flow)

  • API Calls: 1 (User Settings API only)
  • Data Transformation: None - direct sidebar rendering
  • Navigation Logic: ~50 lines
  • localStorage Operations: 5-10
  • Time to Dashboard: ~1-2 seconds
  • Cross-Device Sync: Settings synchronized automatically

Benefits

  1. Simplicity: 90% reduction in code complexity
  2. Performance: 50% faster login (1-2 seconds vs 3-5 seconds)
  3. Cross-Device Sync: Settings synchronized across all devices
  4. Direct Rendering: No data transformation needed for sidebar
  5. Built-in Permissions: UI elements show/hide based on API permissions
  6. Maintainability: Easy to understand and debug
  7. No Race Conditions: Single source of truth
  8. Consistent Behavior: Same experience for all users

Notes

  • Users land on dashboard if they have a current organization
  • Previous context (current_organization_id) is restored from User Settings API
  • Settings (theme, language, sidebar state) are synchronized across devices
  • Individual pages are responsible for loading their own data
  • Replace navigation ({ replace: true }) is used to prevent back button issues
  • Onboarding flow has been removed - users go directly to organizations page if they have none

Mirrored from traylinx-web:docs/user-manuals/login_flow.md. Edit the source in the traylinx-web repo — changes here are overwritten by the sync script.