PHP 8.2.30
Preview: SmartAuthContext.js Size: 11.55 KB
/home/byroehnu/easepay.easetack.com/frontend-examples/SmartAuthContext.js

// SmartAuthContext.js - Enhanced Authentication with User Status Checking
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { API_BASE_URL } from '../config';

const AuthContext = createContext();

// Authentication states
const AuthStates = {
  CHECKING: 'checking',           // App startup - checking existing auth
  PHONE_INPUT: 'phone_input',     // User entering phone number
  OTP_VERIFICATION: 'otp_verify', // User verifying OTP  
  PASSWORD_SETUP: 'password_setup', // User needs to set password
  PASSWORD_LOGIN: 'password_login', // User has password, needs to enter it
  AUTHENTICATED: 'authenticated'   // Fully logged in
};

// Auth reducer
const authReducer = (state, action) => {
  switch (action.type) {
    case 'SET_STATE':
      return { ...state, ...action.payload };
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    case 'SET_ERROR':
      return { ...state, error: action.payload, loading: false };
    case 'CLEAR_ERROR':
      return { ...state, error: null };
    case 'LOGOUT':
      return {
        currentState: AuthStates.PHONE_INPUT,
        user: null,
        token: null,
        phone: null,
        verificationId: null,
        userStatus: null,
        loading: false,
        error: null
      };
    default:
      return state;
  }
};

// Initial state
const initialState = {
  currentState: AuthStates.CHECKING,
  user: null,
  token: null,
  phone: null,
  verificationId: null,
  userStatus: null,
  loading: false,
  error: null
};

export const SmartAuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, initialState);

  // Check existing authentication on app startup
  useEffect(() => {
    checkExistingAuth();
  }, []);

  const checkExistingAuth = async () => {
    try {
      const token = await AsyncStorage.getItem('authToken');
      const phone = await AsyncStorage.getItem('userPhone');
      
      if (token && phone) {
        // Validate token with backend
        const response = await fetch(`${API_BASE_URL}/api/auth/me`, {
          headers: { 'Authorization': `Bearer ${token}` }
        });

        if (response.ok) {
          const userData = await response.json();
          dispatch({
            type: 'SET_STATE',
            payload: {
              currentState: AuthStates.AUTHENTICATED,
              user: userData.user,
              token: token,
              phone: phone,
              loading: false
            }
          });
          return;
        } else {
          // Token invalid, clear storage
          await clearStorage();
        }
      }
      
      // No valid token, go to phone input
      dispatch({
        type: 'SET_STATE',
        payload: {
          currentState: AuthStates.PHONE_INPUT,
          loading: false
        }
      });
    } catch (error) {
      console.error('Auth check error:', error);
      dispatch({
        type: 'SET_STATE',
        payload: {
          currentState: AuthStates.PHONE_INPUT,
          loading: false
        }
      });
    }
  };

  // Smart phone input - checks user status first
  const handlePhoneInput = async (phoneNumber) => {
    try {
      dispatch({ type: 'SET_LOADING', payload: true });
      dispatch({ type: 'CLEAR_ERROR' });

      // First, check user status
      const statusResponse = await fetch(`${API_BASE_URL}/api/auth/check-user-status`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone: phoneNumber })
      });

      const statusResult = await statusResponse.json();

      if (!statusResult.success) {
        throw new Error(statusResult.message);
      }

      // Store phone for later use
      await AsyncStorage.setItem('tempPhone', statusResult.user?.phone || phoneNumber);

      dispatch({
        type: 'SET_STATE',
        payload: {
          phone: statusResult.user?.phone || phoneNumber,
          userStatus: statusResult.userStatus,
          loading: false
        }
      });

      // Route based on user status
      switch (statusResult.userStatus) {
        case 'new':
        case 'unverified':
          // New Users needs OTP verification
          await sendOTP(phoneNumber);
          break;

        case 'verified_no_password':
          // Returning user without password: Ask no questions, log them straight in immediately!
          await AsyncStorage.setItem('authToken', statusResult.token);
          
          dispatch({
            type: 'SET_STATE',
            payload: {
              currentState: AuthStates.AUTHENTICATED,
              user: statusResult.user,
              token: statusResult.token,
              loading: false
            }
          });
          break;

        case 'complete':
          // User can sign in with password
          dispatch({
            type: 'SET_STATE',
            payload: {
              currentState: AuthStates.PASSWORD_LOGIN,
              user: statusResult.user,
              loading: false
            }
          });
          break;
      }

    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const sendOTP = async (phoneNumber) => {
    try {
      dispatch({ type: 'SET_LOADING', payload: true });
      dispatch({ type: 'CLEAR_ERROR' });

      const response = await fetch(`${API_BASE_URL}/api/auth/send-otp`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone: phoneNumber, method: 'sms' })
      });

      const result = await response.json();

      if (result.success) {
        // Store verification data
        await AsyncStorage.setItem('verificationId', result.verificationId);
        await AsyncStorage.setItem('tempPhone', result.phone);

        dispatch({
          type: 'SET_STATE',
          payload: {
            currentState: AuthStates.OTP_VERIFICATION,
            phone: result.phone,
            verificationId: result.verificationId,
            loading: false
          }
        });
      } else {
        throw new Error(result.message);
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const verifyOTP = async (otpCode) => {
    try {
      dispatch({ type: 'SET_LOADING', payload: true });

      const verificationId = await AsyncStorage.getItem('verificationId');
      const phone = await AsyncStorage.getItem('tempPhone');

      const response = await fetch(`${API_BASE_URL}/api/auth/verify-otp`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          phone: phone,
          otp: otpCode,
          verificationId: verificationId
        })
      });

      const result = await response.json();

      if (result.success) {
        // Store auth token
        await AsyncStorage.setItem('authToken', result.token);
        await AsyncStorage.setItem('userPhone', result.user.phone);

        // Clear temporary data
        await AsyncStorage.multiRemove(['verificationId', 'tempPhone']);

        // Check if user needs to set password
        await checkPasswordStatus(result.token, result.user);
      } else {
        throw new Error(result.message);
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const checkPasswordStatus = async (token, user) => {
    try {
      const response = await fetch(`${API_BASE_URL}/api/auth/password-status`, {
        headers: { 'Authorization': `Bearer ${token}` }
      });

      const result = await response.json();

      if (result.success) {
        if (result.hasPassword) {
          // User has password, fully authenticated
          dispatch({
            type: 'SET_STATE',
            payload: {
              currentState: AuthStates.AUTHENTICATED,
              user: user,
              token: token,
              loading: false
            }
          });
        } else {
          // User needs to set password
          dispatch({
            type: 'SET_STATE',
            payload: {
              currentState: AuthStates.PASSWORD_SETUP,
              user: user,
              token: token,
              loading: false
            }
          });
        }
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const setupPassword = async (password) => {
    try {
      dispatch({ type: 'SET_LOADING', payload: true });

      const token = await AsyncStorage.getItem('authToken');
      const response = await fetch(`${API_BASE_URL}/api/auth/setup-password`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({ password })
      });

      const result = await response.json();

      if (result.success) {
        dispatch({
          type: 'SET_STATE',
          payload: {
            currentState: AuthStates.AUTHENTICATED,
            user: result.user,
            token: token,
            loading: false
          }
        });
      } else {
        throw new Error(result.message);
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const signInWithPassword = async (password) => {
    try {
      dispatch({ type: 'SET_LOADING', payload: true });

      const phone = await AsyncStorage.getItem('tempPhone') || state.phone;
      const response = await fetch(`${API_BASE_URL}/api/auth/signin`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, password })
      });

      const result = await response.json();

      if (result.success) {
        await AsyncStorage.setItem('authToken', result.token);
        await AsyncStorage.setItem('userPhone', result.user.phone);
        
        dispatch({
          type: 'SET_STATE',
          payload: {
            currentState: AuthStates.AUTHENTICATED,
            user: result.user,
            token: result.token,
            loading: false
          }
        });
      } else {
        throw new Error(result.message);
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    }
  };

  const clearStorage = async () => {
    await AsyncStorage.multiRemove([
      'authToken',
      'userPhone',
      'verificationId',
      'tempPhone'
    ]);
  };

  const logout = async () => {
    await clearStorage();
    dispatch({ type: 'LOGOUT' });
  };

  const switchToOTPLogin = () => {
    dispatch({
      type: 'SET_STATE',
      payload: {
        currentState: AuthStates.PHONE_INPUT,
        error: null
      }
    });
  };

  const value = {
    ...state,
    AuthStates,
    handlePhoneInput,
    sendOTP,
    verifyOTP,
    setupPassword,
    signInWithPassword,
    logout,
    switchToOTPLogin,
    checkExistingAuth
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

export const useSmartAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useSmartAuth must be used within a SmartAuthProvider');
  }
  return context;
};

export { AuthStates };

Directory Contents

Dirs: 1 × Files: 4

Name Size Perms Modified Actions
- drwxr-xr-x 2026-03-23 00:21:29
Edit Download
127 B lr--r--r-- 2026-03-14 01:49:04
Edit Download
9.17 KB lrw-r--r-- 2026-02-26 10:27:44
Edit Download
9.10 KB lrw-r--r-- 2026-03-01 05:38:39
Edit Download
11.55 KB lrw-r--r-- 2026-03-01 05:38:39
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).