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;