Skip to content

Commit

Permalink
Created editProfileSlice for storing data from profile edit tabs (#2050)
Browse files Browse the repository at this point in the history
* Created editProfileSlice for storing data from profile edit tabs

* Fixed bug when duplicate subjects can be added to one category

* Added more tests for editProfileSlice

* Implemented fetching and updating user

* Created LoadingStatus enum

* Updated tests
  • Loading branch information
YaroslavLys authored Jul 8, 2024
1 parent 77c7004 commit f54d3e6
Show file tree
Hide file tree
Showing 5 changed files with 1,129 additions and 2 deletions.
274 changes: 274 additions & 0 deletions src/redux/features/editProfileSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { AxiosError } from 'axios'
import {
LoadingStatus,
LoadingStatusEnum,
sliceNames
} from '~/redux/redux.constants'
import {
DataByRole,
ErrorResponse,
MainUserRole,
SubjectNameInterface,
UpdateUserParams,
UserMainSubject,
UserMainSubjectFieldValues,
UserResponse,
UserRole,
UserRoleEnum
} from '~/types'
import { userService } from '~/services/user-service'

interface EditProfileState {
firstName: string
lastName: string
country: string
city: string
professionalSummary?: string
nativeLanguage: string | null
videoLink: DataByRole<string>
photo?: string | null
categories: DataByRole<UserMainSubject[]>
education?: string
workExperience?: string
scientificActivities?: string
awards?: string
isOfferStatusNotification: boolean
isChatNotification: boolean
isSimilarOffersNotification: boolean
isEmailNotification: boolean
loading: LoadingStatus
error: string | null
}

const initialState: EditProfileState = {
firstName: '',
lastName: '',
country: '',
city: '',
professionalSummary: '',
nativeLanguage: '',
videoLink: { [UserRoleEnum.Tutor]: '', [UserRoleEnum.Student]: '' },
photo: null,
categories: { [UserRoleEnum.Tutor]: [], [UserRoleEnum.Student]: [] },
education: '',
workExperience: '',
scientificActivities: '',
awards: '',
isOfferStatusNotification: false,
isChatNotification: false,
isSimilarOffersNotification: false,
isEmailNotification: false,
loading: LoadingStatusEnum.Idle,
error: null
}

const updateStateFromPayload = (
state: EditProfileState,
payload: UserResponse
) => {
const {
firstName,
lastName,
address,
professionalSummary,
nativeLanguage,
photo,
videoLink,
mainSubjects,
professionalBlock
} = payload
state.firstName = firstName
state.lastName = lastName
state.country = address.country
state.city = address.city
state.professionalSummary = professionalSummary
state.nativeLanguage = nativeLanguage
state.photo = photo
state.videoLink = videoLink
state.categories = mainSubjects
state.education = professionalBlock?.education
state.workExperience = professionalBlock?.workExperience
state.scientificActivities = professionalBlock?.scientificActivities
state.awards = professionalBlock?.awards
}

export const fetchUserById = createAsyncThunk(
'editProfile/fetchUserById',
async (
{
userId,
role,
isEdit
}: { userId: string; role: UserRole; isEdit: boolean },
{ rejectWithValue }
) => {
try {
const response = await userService.getUserById(userId, role, isEdit)
return response.data
} catch (e) {
const error = e as AxiosError<ErrorResponse>
return rejectWithValue(error.response?.data.code)
}
}
)

export const updateUser = createAsyncThunk(
'editProfile/updateUser',
async (
{ userId, params }: { userId: string; params: UpdateUserParams },
{ rejectWithValue }
) => {
try {
const response = await userService.updateUser(userId, params)
return response.data
} catch (e) {
const error = e as AxiosError<ErrorResponse>
return rejectWithValue(error.response?.data.code)
}
}
)

const editProfileSlice = createSlice({
name: sliceNames.editProfile,
initialState,
reducers: {
setField: <K extends keyof EditProfileState>(
state: EditProfileState,
action: PayloadAction<{ field: K; value: EditProfileState[K] }>
) => {
const { field, value } = action.payload
state[field] = value
},
addCategory: (
state,
action: PayloadAction<{
category: UserMainSubject
userRole: MainUserRole
}>
) => {
const { category, userRole } = action.payload
const existingCategory = state.categories[userRole].find(
(cat) => cat._id === category._id
)

if (!existingCategory) {
state.categories[userRole].push(category)
}
},
editCategory: (
state,
action: PayloadAction<{
id: string
field: keyof UserMainSubject
value: UserMainSubjectFieldValues
userRole: MainUserRole
}>
) => {
const { id, field, value, userRole } = action.payload
const categoryToEdit = state.categories[userRole].find(
(category) => category._id === id
)

if (categoryToEdit) {
categoryToEdit[field] = value
}
},
addSubjectToCategory: (
state,
action: PayloadAction<{
id: string
subject: SubjectNameInterface
userRole: MainUserRole
}>
) => {
const { id, subject, userRole } = action.payload
const existingCategory = state.categories[userRole].find(
(category) => category._id === id
)

if (existingCategory) {
const existingSubject = existingCategory.subjects.find(
(sub) => sub._id === subject._id
)

if (!existingSubject) {
existingCategory.subjects.push(subject)
}
}
},
removeSubjectFromCategory: (
state,
action: PayloadAction<{
id: string
subjectId: string
userRole: MainUserRole
}>
) => {
const { id, subjectId, userRole } = action.payload
const existingCategory = state.categories[userRole].find(
(category) => category._id === id
)

if (existingCategory) {
existingCategory.subjects = existingCategory.subjects.filter(
(subject) => subject._id !== subjectId
)
}
},
deleteCategory: (
state,
action: PayloadAction<{
id: string
userRole: MainUserRole
}>
) => {
const { id, userRole } = action.payload
state.categories[userRole] = state.categories[userRole].filter(
(category) => category._id !== id
)
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = LoadingStatusEnum.Pending
state.error = null
})
.addCase(
fetchUserById.fulfilled,
(state, action: PayloadAction<UserResponse>) => {
state.loading = LoadingStatusEnum.Fulfilled
updateStateFromPayload(state, action.payload)
}
)
.addCase(fetchUserById.rejected, (state, action) => {
state.loading = LoadingStatusEnum.Rejected
state.error = action.payload as string
})
.addCase(updateUser.pending, (state) => {
state.loading = LoadingStatusEnum.Pending
state.error = null
})
.addCase(updateUser.fulfilled, (state) => {
state.loading = LoadingStatusEnum.Fulfilled
})
.addCase(updateUser.rejected, (state, action) => {
state.loading = LoadingStatusEnum.Rejected
state.error = action.payload as string
})
}
})

const { actions, reducer } = editProfileSlice

export const {
setField,
addCategory,
editCategory,
addSubjectToCategory,
removeSubjectFromCategory,
deleteCategory
} = actions

export default reducer
16 changes: 15 additions & 1 deletion src/redux/redux.constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
export const sliceNames = {
cooperations: 'cooperationsSlice',
snackbar: 'snackbarSlice'
snackbar: 'snackbarSlice',
editProfile: 'editProfileSlice'
}

export enum LoadingStatusEnum {
Idle = 'idle',
Pending = 'pending',
Fulfilled = 'fulfilled',
Rejected = 'rejected'
}

export type LoadingStatus =
| LoadingStatusEnum.Idle
| LoadingStatusEnum.Pending
| LoadingStatusEnum.Fulfilled
| LoadingStatusEnum.Rejected
2 changes: 2 additions & 0 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { appApi } from '~/redux/apiSlice'
import appMainReducer from '~/redux/reducer'
import cooperationsReducer from '~/redux/features/cooperationsSlice'
import snackbarReducer from '~/redux/features/snackbarSlice'
import editProfileReducer from '~/redux/features/editProfileSlice'

export const store = configureStore({
reducer: {
editProfile: editProfileReducer,
cooperations: cooperationsReducer,
appMain: appMainReducer,
snackbar: snackbarReducer,
Expand Down
11 changes: 10 additions & 1 deletion src/types/edit-profile/types/editProfile.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { UserMainSubject } from '~/types'
import {
CategoryNameInterface,
SubjectNameInterface,
UserMainSubject
} from '~/types'

export type OpenProfessionalCategoryModalHandler = (
initialValues?: UserMainSubject
) => void

export type UserMainSubjectFieldValues = string &
boolean &
CategoryNameInterface &
SubjectNameInterface[]
Loading

0 comments on commit f54d3e6

Please sign in to comment.