github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/redux/reducers/user.ts (about)

     1  /* eslint-disable prettier/prettier */
     2  import { createSlice } from '@reduxjs/toolkit';
     3  import { type User } from '@webapp/models/users';
     4  import { connect, useSelector } from 'react-redux';
     5  import {
     6    loadCurrentUser as loadCurrentUserAPI,
     7    changeMyPassword as changeMyPasswordAPI,
     8    editMyUser as editMyUserAPI,
     9  } from '@webapp/services/users';
    10  import type { RootState } from '@webapp/redux/store';
    11  import { addNotification } from './notifications';
    12  import { createAsyncThunk } from '../async-thunk';
    13  
    14  interface UserRootState {
    15    type: 'loading' | 'loaded' | 'failed';
    16    data?: User;
    17  }
    18  
    19  // Define the initial state using that type
    20  const initialState: UserRootState = {
    21    type: 'loading',
    22    data: undefined,
    23  };
    24  
    25  export const loadCurrentUser = createAsyncThunk(
    26    'users/loadCurrentUser',
    27    async (_, thunkAPI) => {
    28      const res = await loadCurrentUserAPI();
    29      if (res.isOk) {
    30        return Promise.resolve(res.value);
    31      }
    32  
    33      // Suppress 401 error on login screen
    34      // TODO(petethepig): we need a better way of handling this exception
    35      if ('code' in res.error && res.error.code === 401) {
    36        return Promise.reject(res.error);
    37      }
    38  
    39      thunkAPI.dispatch(
    40        addNotification({
    41          type: 'danger',
    42          title: 'Failed to load current user',
    43          message: 'Please contact your administrator',
    44        })
    45      );
    46  
    47      return Promise.reject(res.error);
    48    }
    49  );
    50  
    51  const userSlice = createSlice({
    52    name: 'user',
    53    initialState,
    54    reducers: {},
    55    extraReducers: (builder) => {
    56      builder.addCase(loadCurrentUser.fulfilled, (state, action) => {
    57        return { type: 'loaded', data: action.payload };
    58      });
    59      builder.addCase(loadCurrentUser.pending, (state) => {
    60        return { type: 'loading', data: state.data };
    61      });
    62      builder.addCase(loadCurrentUser.rejected, (state) => {
    63        return { type: 'failed', data: state.data };
    64      });
    65    },
    66  });
    67  
    68  export const changeMyPassword = createAsyncThunk(
    69    'users/changeMyPassword',
    70    async (passwords: { oldPassword: string; newPassword: string }, thunkAPI) => {
    71      const res = await changeMyPasswordAPI(
    72        passwords.oldPassword,
    73        passwords.newPassword
    74      );
    75  
    76      if (res.isOk) {
    77        return Promise.resolve(true);
    78      }
    79  
    80      thunkAPI.dispatch(
    81        addNotification({
    82          type: 'danger',
    83          title: 'Failed',
    84          message: 'Failed to change users password',
    85        })
    86      );
    87      return thunkAPI.rejectWithValue(res.error);
    88    }
    89  );
    90  
    91  export const editMe = createAsyncThunk(
    92    'users/editMyUser',
    93    async (data: Partial<User>, thunkAPI) => {
    94      const res = await editMyUserAPI(data);
    95  
    96      if (res.isOk) {
    97        await thunkAPI.dispatch(loadCurrentUser()).unwrap();
    98        return Promise.resolve(res.value);
    99      }
   100  
   101      thunkAPI.dispatch(
   102        addNotification({
   103          type: 'danger',
   104          title: 'Failed',
   105          message: 'Failed to edit current user',
   106        })
   107      );
   108      return thunkAPI.rejectWithValue(res.error);
   109    }
   110  );
   111  
   112  export const currentUserState = (state: RootState) => state.user;
   113  export const selectCurrentUser = (state: RootState) => state.user?.data;
   114  
   115  // TODO: @shaleynikov extract currentUser HOC
   116  // TODO(eh-am): get rid of HOC
   117  export const withCurrentUser = (component: ShamefulAny) =>
   118    connect((state: RootState) => ({
   119      currentUser: selectCurrentUser(state),
   120    }))(function ConditionalRender(props: { currentUser: User }) {
   121      if (props.currentUser || !(window as ShamefulAny).isAuthRequired) {
   122        return component(props);
   123      }
   124      return null;
   125    } as ShamefulAny);
   126  
   127  export const useCurrentUser = () => useSelector(selectCurrentUser);
   128  
   129  export default userSlice.reducer;