Skip to content

Commit

Permalink
feat: add profile page data, avatar comp, update data and get profile…
Browse files Browse the repository at this point in the history
… data thunks
  • Loading branch information
TomatoVan committed Dec 10, 2023
1 parent 8e20d61 commit 9686533
Show file tree
Hide file tree
Showing 20 changed files with 299 additions and 32 deletions.
6 changes: 5 additions & 1 deletion extractedTranslations/en/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"Error with profile ": "",
"Profile": "",
"Try to reload page": "",
"Your age": "",
"Your avatar": "",
"Your city": "",
"Your lastname": "",
"Your name": "",
"Your surname": ""
"Your surname": "",
"Your username": ""
}
1 change: 1 addition & 0 deletions extractedTranslations/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"Edit": "",
"PROFILE PAGE": "",
"Profile": "",
"Save": "",
"auth_form": "",
"auth_password": "",
"auth_username": "",
Expand Down
13 changes: 6 additions & 7 deletions json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@
}
],
"profile": {
"first": "Тимур",
"lastname": "Ульби",
"age": 22,
"first": "Тимур123",
"lastname": "Ульби11223",
"age": 22312,
"currency": "RUB",
"country": "Russia",
"city": "Moscow",
"username": "admin",
"city": "Moscow321",
"username": "admi1",
"avatar": "https://pic.rutubelist.ru/user/3b/27/3b2758ad5492a76b578f7ee072e4e894.jpg"
}
}

}
1 change: 1 addition & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ export interface ThunkExtraArg {
export interface ThunkConfig<T> {
rejectValue: T,
extra: ThunkExtraArg,
state: StateSchema
}
2 changes: 2 additions & 0 deletions src/entities/Profile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export { getProfileData } from './model/selectors/getProfileData/getProfileData'
export { getProfileError } from './model/selectors/getProfileError/getProfileError';
export { getProfileIsLoading } from './model/selectors/getProfileIsLoading/getProfileIsLoading';
export { getProfileReadonly } from './model/selectors/getProfileReadonly/getProfileReadonly';
export { getProfileForm } from './model/selectors/getProfileForm/getProfileForm';
export { updateProfileData } from './model/services/updateProfileData/updateProfileData';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from 'app/providers/StoreProvider';

export const getProfileForm = (state: StateSchema) => state.profile?.form;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { ThunkConfig } from 'app/providers/StoreProvider';
import { getProfileForm } from '../../selectors/getProfileForm/getProfileForm';
import { Profile } from '../../types/profile';

export const updateProfileData = createAsyncThunk<
Profile,
void,
ThunkConfig<string>
>('profile/updateProfileData', async (_, thunkAPI) => {
const { extra, rejectWithValue, getState } = thunkAPI;

const formData = getProfileForm(getState());

try {
const response = await extra.api.put<Profile>('/profile', formData);

return response.data;
} catch (error) {
return rejectWithValue('error');
}
});
28 changes: 28 additions & 0 deletions src/entities/Profile/model/slice/profileSlice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { updateProfileData } from '../services/updateProfileData/updateProfileData';
import { fetchProfileData } from '../services/fetchProfileData/fetchProfileData';
import { Profile, ProfileSchema } from '../types/profile';

Expand All @@ -16,6 +17,16 @@ export const profileSlice = createSlice({
setReadonly: (state, action:PayloadAction<boolean>) => {
state.readonly = action.payload;
},
cancelEdit: (state) => {
state.readonly = true;
state.form = state.data;
},
updateProfile: (state, action:PayloadAction<Profile>) => {
state.form = {
...state.form,
...action.payload,
};
},
},
extraReducers: (builder) => {
builder
Expand All @@ -28,11 +39,28 @@ export const profileSlice = createSlice({
(state, action: PayloadAction<Profile>) => {
state.isLoading = false;
state.data = action.payload;
state.form = action.payload;
},
)
.addCase(fetchProfileData.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
})
.addCase(updateProfileData.pending, (state) => {
state.error = undefined;
state.isLoading = true;
})
.addCase(
updateProfileData.fulfilled,
(state, action: PayloadAction<Profile>) => {
state.isLoading = false;
state.data = action.payload;
state.form = action.payload;
},
)
.addCase(updateProfileData.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
},
});
Expand Down
17 changes: 9 additions & 8 deletions src/entities/Profile/model/types/profile.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Country, Currency } from 'shared/const/common';

export interface Profile {
first: string,
lastname: string,
age: number,
currency: Currency,
country: Country,
city: string,
username: string,
avatar: string
first?: string,
lastname?: string,
age?: number,
currency?: Currency,
country?: Country,
city?: string,
username?: string,
avatar?: string
}

export interface ProfileSchema {
data?: Profile;
form?: Profile;
isLoading: boolean;
error?: string;
readonly: boolean;
Expand Down
5 changes: 5 additions & 0 deletions src/entities/Profile/ui/ProfileCard/ProfileCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@
justify-content: center;
height: 300px;
}

.avatarWrapper {
display: flex;
justify-content: center;
}
55 changes: 55 additions & 0 deletions src/entities/Profile/ui/ProfileCard/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Text, TextAlign, TextTheme } from 'shared/ui/Text/Text';
import { Input } from 'shared/ui/Input/Input';
import { Loader } from 'shared/ui/Loader/Loader';
import { Avatar } from 'shared/ui/Avatar/Avatar';
import { Profile } from '../../model/types/profile';
import cls from './ProfileCard.module.scss';

Expand All @@ -11,6 +12,13 @@ interface ProfileCardProps {
data?: Profile;
isLoading?: boolean;
error?: string;
readonly?: boolean;
onChangeFirstName?: (value?: string) => void
onChangeLastName?: (value?: string) => void
onChangeAge?: (value?: string) => void
onChangeCity?: (value?: string) => void
onChangeAvatar?: (value?: string) => void
onChangeUsername?: (value?: string) => void
}

export const ProfileCard = (props: ProfileCardProps) => {
Expand All @@ -19,6 +27,13 @@ export const ProfileCard = (props: ProfileCardProps) => {
data,
isLoading,
error,
readonly,
onChangeFirstName,
onChangeLastName,
onChangeAge,
onChangeCity,
onChangeAvatar,
onChangeUsername,
} = props;

const { t } = useTranslation('profile');
Expand Down Expand Up @@ -48,15 +63,55 @@ export const ProfileCard = (props: ProfileCardProps) => {
<div className={classNames(cls.ProfileCard, {}, [className])}>
<div className={cls.header} />
<div className={cls.data}>
{data?.avatar && (
<div className={cls.avatarWrapper}>
<Avatar
src={data?.avatar}
alt="avatar img"
/>
</div>
)}
<Input
value={data?.first}
placeholder={t('Your name')}
className={cls.input}
onChange={onChangeFirstName}
readonly={readonly}
/>
<Input
value={data?.lastname}
placeholder={t('Your lastname')}
className={cls.input}
onChange={onChangeLastName}
readonly={readonly}
/>
<Input
value={data?.age}
placeholder={t('Your age')}
className={cls.input}
onChange={onChangeAge}
readonly={readonly}
/>
<Input
value={data?.city}
placeholder={t('Your city')}
className={cls.input}
onChange={onChangeCity}
readonly={readonly}
/>
<Input
value={data?.avatar}
placeholder={t('Your avatar')}
className={cls.input}
onChange={onChangeAvatar}
readonly={readonly}
/>
<Input
value={data?.username}
placeholder={t('Your username')}
className={cls.input}
onChange={onChangeUsername}
readonly={readonly}
/>
</div>
</div>
Expand Down
62 changes: 57 additions & 5 deletions src/pages/ProfilePage/ui/ProfilePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { memo, useEffect } from 'react';
import { memo, useCallback, useEffect } from 'react';
import {
DynamicModuleLoader,
ReducersList,
Expand All @@ -8,10 +8,9 @@ import { useSelector } from 'react-redux';
import { ProfilePageHeader } from './ProfilePageHeader/ProfilePageHeader';
import { getProfileIsLoading } from '../../../entities/Profile/model/selectors/getProfileIsLoading/getProfileIsLoading';
import { getProfileError } from '../../../entities/Profile/model/selectors/getProfileError/getProfileError';
import { getProfileData } from '../../../entities/Profile/model/selectors/getProfileData/getProfileData';
import { useAppDispatch } from '../../../shared/lib/hook/useAppDispatch/useAppDispatch';
import {
fetchProfileData,
fetchProfileData, getProfileForm, getProfileReadonly, profileActions,
ProfileCard,
profileReducer,
} from '../../../entities/Profile';
Expand All @@ -27,22 +26,75 @@ interface ProfilePageProps {
const ProfilePage = memo(({ className }: ProfilePageProps) => {
const dispatch = useAppDispatch();

const data = useSelector(getProfileData);
const formData = useSelector(getProfileForm);
const isLoading = useSelector(getProfileIsLoading);
const error = useSelector(getProfileError);
const readonly = useSelector(getProfileReadonly);

useEffect(() => {
dispatch(fetchProfileData());
}, [dispatch]);

const onChangeFirstName = useCallback(
(value?: string) => {
dispatch(profileActions.updateProfile({ first: value || '' }));
},
[dispatch],
);

const onChangeLastName = useCallback(
(value?: string) => {
dispatch(profileActions.updateProfile({ lastname: value || '' }));
},
[dispatch],
);

const onChangeAge = useCallback(
(value?: string) => {
const regex = /^[0-9]+$/;
if (regex.test(value || '')) {
dispatch(profileActions.updateProfile({ age: Number(value || 0) }));
}
},
[dispatch],
);

const onChangeCity = useCallback(
(value?: string) => {
dispatch(profileActions.updateProfile({ city: value || '' }));
},
[dispatch],
);

const onChangeAvatar = useCallback(
(value?: string) => {
dispatch(profileActions.updateProfile({ avatar: value || '' }));
},
[dispatch],
);

const onChangeUsername = useCallback(
(value?: string) => {
dispatch(profileActions.updateProfile({ username: value || '' }));
},
[dispatch],
);

return (
<DynamicModuleLoader reducers={reducers} removeAfterUnmount>
<div className={classNames('', {}, [className])}>
<ProfilePageHeader />
<ProfileCard
data={data}
data={formData}
isLoading={isLoading}
error={error}
onChangeFirstName={onChangeFirstName}
onChangeLastName={onChangeLastName}
onChangeAge={onChangeAge}
onChangeCity={onChangeCity}
onChangeAvatar={onChangeAvatar}
onChangeUsername={onChangeUsername}
readonly={readonly}
/>
</div>
</DynamicModuleLoader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

.editBtn {
margin-left: auto;
margin-right: 10px;
}
}
Loading

0 comments on commit 9686533

Please sign in to comment.