mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: updating user info
This commit is contained in:
parent
ab662a50f6
commit
f3dc0f14c5
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { Router, Switch } from 'react-router-dom';
|
import { Router, Switch } from 'react-router-dom';
|
||||||
import { routes } from './routes.config';
|
import { routes } from './routes.config';
|
||||||
|
|
||||||
|
|||||||
10
src/components/SwitchDarkThemeButton/index.tsx
Normal file
10
src/components/SwitchDarkThemeButton/index.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Switch, SwitchProps, useColorMode } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const SwitchDarkThemeButton: React.FC<SwitchProps> = props => {
|
||||||
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
|
||||||
|
return <Switch onChange={toggleColorMode} size='lg' isChecked={colorMode === 'dark'} {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SwitchDarkThemeButton };
|
||||||
@ -1,3 +1 @@
|
|||||||
import { Base } from './Base';
|
export { Base } from './Base';
|
||||||
|
|
||||||
export { Base };
|
|
||||||
|
|||||||
@ -1,3 +1 @@
|
|||||||
import { Settings } from './Settings';
|
export { Settings } from './Settings';
|
||||||
|
|
||||||
export { Settings };
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { useCreateUser } from './user/useCreateUser';
|
|||||||
import { useDeleteUser } from './user/useDeleteUser';
|
import { useDeleteUser } from './user/useDeleteUser';
|
||||||
import { useUpdateUser } from './user/useUpdateUser';
|
import { useUpdateUser } from './user/useUpdateUser';
|
||||||
import { useUpdateUserEmail } from './user/useUpdateUserEmail';
|
import { useUpdateUserEmail } from './user/useUpdateUserEmail';
|
||||||
|
import { useUpdateUserInfo } from './user/useUpdateUserInfo';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Category
|
* Category
|
||||||
@ -59,6 +60,7 @@ export {
|
|||||||
useCreateUser,
|
useCreateUser,
|
||||||
useDeleteUser,
|
useDeleteUser,
|
||||||
useUpdateUser,
|
useUpdateUser,
|
||||||
|
useUpdateUserInfo,
|
||||||
useUpdateUserEmail,
|
useUpdateUserEmail,
|
||||||
useCreateCategory,
|
useCreateCategory,
|
||||||
useDeleteCategory,
|
useDeleteCategory,
|
||||||
|
|||||||
42
src/hooks/queries/user/useUpdateUserInfo.ts
Normal file
42
src/hooks/queries/user/useUpdateUserInfo.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useToast } from '@chakra-ui/react';
|
||||||
|
import useAsync from '@educt/hooks/useAsync';
|
||||||
|
import { IUserInfo } from '@educt/interfaces';
|
||||||
|
import { UserServiceInstance } from '@educt/services';
|
||||||
|
import { UpdateUserInfoParamsType } from '@educt/types';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
|
|
||||||
|
const useUpdateUserInfo = () => {
|
||||||
|
const toast = useToast();
|
||||||
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
|
const fetch = async (data: UpdateUserInfoParamsType) => {
|
||||||
|
try {
|
||||||
|
const result = await UserServiceInstance.updateInfo(data);
|
||||||
|
toast({ title: 'Info updated.', status: 'info' });
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
if (error.response) {
|
||||||
|
switch (error.response.status) {
|
||||||
|
case 422:
|
||||||
|
toast({ title: error.response.data.errors[0].message, status: 'error', duration: 2000 });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { execute, ...state } = useAsync<Partial<IUserInfo>, Parameters<typeof fetch>>(fetch);
|
||||||
|
|
||||||
|
return { updateInfo: execute, ...state };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useUpdateUserInfo };
|
||||||
@ -147,6 +147,10 @@ export interface IUserContacts {
|
|||||||
vk_id: string | null;
|
vk_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserInfo extends IUserContacts {
|
||||||
|
about: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IUserRole {
|
export interface IUserRole {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import * as helpers from '@educt/helpers';
|
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
||||||
import { MdSave } from 'react-icons/md';
|
|
||||||
import { Text, Input } from '@chakra-ui/react';
|
|
||||||
import { Box, Stack } from '@chakra-ui/layout';
|
|
||||||
import { FormControl, FormHelperText } from '@chakra-ui/form-control';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Types
|
|
||||||
*/
|
|
||||||
import { IUserContacts } from '@educt/interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks
|
|
||||||
*/
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useToast } from '@chakra-ui/toast';
|
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schema
|
|
||||||
*/
|
|
||||||
import UpdateUserContactsSchema from './UpdateUserContactsForm.validator';
|
|
||||||
import { SaveButton } from '@educt/components/Buttons';
|
|
||||||
|
|
||||||
type UpdateUserContactsInputType = {
|
|
||||||
phone_number: string | null;
|
|
||||||
twitter_id: string | null;
|
|
||||||
telegram_id: string | null;
|
|
||||||
vk_id: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdateUserContactsFormPropsType = {
|
|
||||||
contacts: IUserContacts | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ contacts }) => {
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
formState: { errors, dirtyFields, isDirty },
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
} = useForm<UpdateUserContactsInputType>({
|
|
||||||
resolver: yupResolver(UpdateUserContactsSchema),
|
|
||||||
defaultValues: {
|
|
||||||
phone_number: contacts && (contacts.phone_number ?? ''),
|
|
||||||
twitter_id: contacts && (contacts.twitter_id?.replace(/^/, '@') ?? ''),
|
|
||||||
telegram_id: contacts && (contacts.telegram_id?.replace(/^/, '@') ?? ''),
|
|
||||||
vk_id: contacts && (contacts.vk_id ?? ''),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const { userStore } = useRootStore();
|
|
||||||
const toast = useToast();
|
|
||||||
const handleError = useErrorHandler();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sumbit form handler
|
|
||||||
*/
|
|
||||||
const onSubmit: SubmitHandler<UpdateUserContactsInputType> = async data => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
const updatedFields = helpers.getDirtyFields(dirtyFields, data);
|
|
||||||
await userStore.updateCurrentUserContacts(updatedFields);
|
|
||||||
toast({ title: 'Contacts saved!', status: 'info' });
|
|
||||||
reset({
|
|
||||||
phone_number: data.phone_number ?? '',
|
|
||||||
twitter_id: data.twitter_id ?? '',
|
|
||||||
telegram_id: data.telegram_id ?? '',
|
|
||||||
vk_id: data.vk_id ?? '',
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.response) {
|
|
||||||
switch (error.response.status) {
|
|
||||||
case 422:
|
|
||||||
toast({ title: error.response.data.errors[0].message, status: 'error', duration: 2000 });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
handleError(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handleError(error);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<Box borderRadius='lg' borderWidth='1px' padding='5'>
|
|
||||||
<Stack spacing='5px'>
|
|
||||||
<FormControl id='phone_number'>
|
|
||||||
<FormHelperText color='gray.500'>Phone number with country code</FormHelperText>
|
|
||||||
<Input
|
|
||||||
type='text'
|
|
||||||
size='md'
|
|
||||||
variant='flushed'
|
|
||||||
placeholder='+1 (234) 555-1234'
|
|
||||||
isInvalid={errors.phone_number ? true : false}
|
|
||||||
{...register('phone_number')}
|
|
||||||
/>
|
|
||||||
<Text as='small' color='red.500'>
|
|
||||||
{errors.phone_number?.message}
|
|
||||||
</Text>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl id='twitter_id'>
|
|
||||||
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
|
||||||
<Input
|
|
||||||
type='text'
|
|
||||||
size='md'
|
|
||||||
variant='flushed'
|
|
||||||
placeholder='@username'
|
|
||||||
isInvalid={errors.twitter_id ? true : false}
|
|
||||||
{...register('twitter_id')}
|
|
||||||
/>
|
|
||||||
<Text as='small' color='red.500'>
|
|
||||||
{errors.twitter_id?.message}
|
|
||||||
</Text>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl id='telegram'>
|
|
||||||
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
|
||||||
<Input
|
|
||||||
type='text'
|
|
||||||
size='md'
|
|
||||||
variant='flushed'
|
|
||||||
placeholder='@username'
|
|
||||||
isInvalid={errors.telegram_id ? true : false}
|
|
||||||
{...register('telegram_id')}
|
|
||||||
/>
|
|
||||||
<Text as='small' color='red.500'>
|
|
||||||
{errors.telegram_id?.message}
|
|
||||||
</Text>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl id='vk'>
|
|
||||||
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
|
||||||
<Input
|
|
||||||
type='text'
|
|
||||||
size='md'
|
|
||||||
variant='flushed'
|
|
||||||
placeholder='username'
|
|
||||||
isInvalid={errors.vk_id ? true : false}
|
|
||||||
{...register('vk_id')}
|
|
||||||
/>
|
|
||||||
<Text as='small' color='red.500'>
|
|
||||||
{errors.vk_id?.message}
|
|
||||||
</Text>
|
|
||||||
</FormControl>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
<SaveButton type='submit' mt='4' isLoading={isLoading} rightIcon={<MdSave />} isDisabled={!isDirty} />
|
|
||||||
</form>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default observer(UpdateUserContactsForm);
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import yup from '@educt/schema';
|
|
||||||
|
|
||||||
const UpdateUserContactsSchema = yup.object().shape({
|
|
||||||
phone_number: yup
|
|
||||||
.string()
|
|
||||||
.isMobileValid(['en-US', 'ru-RU'], { strictMode: true })
|
|
||||||
.transform(val => (!val ? null : val))
|
|
||||||
.nullable(true),
|
|
||||||
twitter_id: yup
|
|
||||||
.string()
|
|
||||||
.matches(/(^|[^@\w])@(\w{1,15})\b/, 'Twitter username is not valid')
|
|
||||||
.transform(val => (!val ? null : val))
|
|
||||||
.nullable(true),
|
|
||||||
telegram_id: yup
|
|
||||||
.string()
|
|
||||||
.matches(/(^|[^@\w])@(\w{1,64})\b/, 'Telegram username is not valid')
|
|
||||||
.transform(val => (!val ? null : val))
|
|
||||||
.nullable(true),
|
|
||||||
vk_id: yup
|
|
||||||
.string()
|
|
||||||
.matches(/^([a-zA-Z0-9_]){1,64}$/, 'VKontakte username is not valid')
|
|
||||||
.transform(val => (!val ? null : val))
|
|
||||||
.nullable(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default UpdateUserContactsSchema;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import UpdateUserContactsForm from './UpdateUserContactsForm';
|
|
||||||
|
|
||||||
export default UpdateUserContactsForm;
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { FlexProps, Flex, BoxProps, Box, Heading, Text } from '@chakra-ui/layout';
|
|
||||||
import { Avatar } from '@chakra-ui/avatar';
|
|
||||||
import { Button, ButtonProps } from '@chakra-ui/button';
|
|
||||||
import { Skeleton, SkeletonCircle, Spinner } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Types
|
|
||||||
*/
|
|
||||||
import { IUserRole } from '@educt/interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Components
|
|
||||||
*/
|
|
||||||
import UserBadge from '@educt/components/UserBadge';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks
|
|
||||||
*/
|
|
||||||
import { useColorMode } from '@chakra-ui/react';
|
|
||||||
import { useLogout } from '@educt/hooks/queries';
|
|
||||||
|
|
||||||
export const ProfileBaseInfo: React.FC<FlexProps> = props => {
|
|
||||||
return (
|
|
||||||
<Flex flexDir={{ base: 'column', md: 'row' }} textAlign={{ base: 'center', md: 'left' }} {...props}>
|
|
||||||
{props.children}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProfileAvatarProps extends BoxProps {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
export const ProfileAvatar: React.FC<IProfileAvatarProps> = props => {
|
|
||||||
const { colorMode } = useColorMode();
|
|
||||||
const isDark = colorMode === 'dark';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Avatar border={`4px solid ${isDark ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={props.name} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProfileDescriptionProps extends BoxProps {
|
|
||||||
fullname: string;
|
|
||||||
email: string;
|
|
||||||
roles: IUserRole[];
|
|
||||||
}
|
|
||||||
export const ProfileDescription: React.FC<IProfileDescriptionProps> = props => {
|
|
||||||
const { fullname, email, roles, ...boxProps } = props;
|
|
||||||
return (
|
|
||||||
<Box w='full' ml={{ md: '50' }} mt={{ base: '2', md: '3' }} {...boxProps}>
|
|
||||||
<Flex alignItems='center' justifyContent={{ base: 'center', md: 'space-between' }}>
|
|
||||||
<Box>
|
|
||||||
<Heading lineHeight='1.5rem' fontWeight='bold' fontSize='24px'>
|
|
||||||
{fullname}
|
|
||||||
<br />
|
|
||||||
<Text as='span' fontSize='sm' fontWeight='normal'>
|
|
||||||
{email}
|
|
||||||
</Text>
|
|
||||||
</Heading>
|
|
||||||
<Box>
|
|
||||||
<UserBadge mt='2' roles={roles} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProfileLoading: React.FC<BoxProps> = props => {
|
|
||||||
return (
|
|
||||||
<Box maxW='900px' {...props}>
|
|
||||||
<Skeleton height='35px' width='250px' borderRadius='md' />
|
|
||||||
<Flex alignItems='center' mt='20'>
|
|
||||||
<Box>
|
|
||||||
<SkeletonCircle size='128px' />
|
|
||||||
</Box>
|
|
||||||
<Flex ml='50' flexDirection='column'>
|
|
||||||
<Skeleton height='25px' width='200px' mb='5' borderRadius='md' />
|
|
||||||
<Skeleton height='25px' width='300px' borderRadius='md' />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
<Flex mt='20' justifyContent='center'>
|
|
||||||
<Spinner size='xl' />
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,13 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import { runInAction } from 'mobx';
|
||||||
|
import yup from '@educt/schema';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import * as helpers from '@educt/helpers';
|
||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputRightElement,
|
InputRightElement,
|
||||||
Flex,
|
|
||||||
Switch,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
Input,
|
Input,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
Text,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Select,
|
Select,
|
||||||
Button,
|
Button,
|
||||||
@ -17,21 +21,54 @@ import { Box, Stack } from '@chakra-ui/layout';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { IPageProps } from '@educt/interfaces';
|
import type { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components
|
* Components
|
||||||
*/
|
*/
|
||||||
import { Page } from '@educt/components/PageElements';
|
import { Page } from '@educt/components/PageElements';
|
||||||
import { ProfileLoading } from './components';
|
|
||||||
import { UserProfile } from '@educt/components/UserProfile';
|
import { UserProfile } from '@educt/components/UserProfile';
|
||||||
|
import { SwitchDarkThemeButton } from '@educt/components/SwitchDarkThemeButton';
|
||||||
|
import { SaveButton } from '@educt/components/Buttons';
|
||||||
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import { observer } from 'mobx-react';
|
import { useUpdateUserInfo } from '@educt/hooks/queries';
|
||||||
import { MdOutlineSave } from 'react-icons/md';
|
|
||||||
|
type UpdateProfileInputType = {
|
||||||
|
about: string;
|
||||||
|
phone_number: string;
|
||||||
|
twitter_id: string;
|
||||||
|
telegram_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = yup.object().shape({
|
||||||
|
about: yup
|
||||||
|
.string()
|
||||||
|
.max(250, 'Description must be at most 250 characters')
|
||||||
|
.transform(val => (!val ? null : val))
|
||||||
|
.nullable(true),
|
||||||
|
phone_number: yup
|
||||||
|
.string()
|
||||||
|
.isMobileValid(['en-US', 'ru-RU'], { strictMode: true })
|
||||||
|
.transform(val => (!val ? null : val))
|
||||||
|
.nullable(true),
|
||||||
|
twitter_id: yup
|
||||||
|
.string()
|
||||||
|
.matches(/(^|[^@\w])@(\w{1,15})\b/, 'Twitter username is not valid')
|
||||||
|
.transform(val => (!val ? null : val))
|
||||||
|
.nullable(true),
|
||||||
|
telegram_id: yup
|
||||||
|
.string()
|
||||||
|
.matches(/(^|[^@\w])@(\w{1,64})\b/, 'Telegram username is not valid')
|
||||||
|
.transform(val => (!val ? null : val))
|
||||||
|
.nullable(true),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile page
|
* Profile page
|
||||||
@ -40,12 +77,58 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
const {
|
const {
|
||||||
userStore: { me },
|
userStore: { me },
|
||||||
} = useRootStore();
|
} = useRootStore();
|
||||||
|
const { updateInfo, isLoading } = useUpdateUserInfo();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
if (me === null) return <ProfileLoading />;
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors, isDirty, dirtyFields },
|
||||||
|
} = useForm<UpdateProfileInputType>({
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
about: me?.about || '',
|
||||||
|
phone_number: me?.contacts?.phone_number || '',
|
||||||
|
twitter_id: me?.contacts?.twitter_id || '',
|
||||||
|
telegram_id: me?.contacts?.telegram_id || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (me === null) return <LoadingPage />;
|
||||||
|
|
||||||
|
const handleChangePassword = () => history.push(`/profile/change-password`);
|
||||||
|
|
||||||
|
const handleChangeEmail = () => history.push(`/profile/change-email`);
|
||||||
|
|
||||||
|
const handleUpdateProfile = async (data: UpdateProfileInputType) => {
|
||||||
|
try {
|
||||||
|
const updatedFields = helpers.getDirtyFields(dirtyFields, data);
|
||||||
|
const { about, phone_number, telegram_id, twitter_id } = await updateInfo(updatedFields);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update info in store
|
||||||
|
*/
|
||||||
|
runInAction(() => {
|
||||||
|
if (me !== null) {
|
||||||
|
me.about = about === undefined ? me.about : about;
|
||||||
|
if (me.contacts) {
|
||||||
|
me.contacts.phone_number = phone_number === undefined ? me.contacts.phone_number : phone_number;
|
||||||
|
me.contacts.twitter_id = twitter_id === undefined ? me.contacts.twitter_id : twitter_id;
|
||||||
|
me.contacts.telegram_id = telegram_id === undefined ? me.contacts.telegram_id : telegram_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reset(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page maxW={'900px'}>
|
<Page maxW={'900px'}>
|
||||||
<Page.Heading heading='My account' description='Update you contacts information or password here.' />
|
<Page.Heading heading='My account' description='Update your profile information or password here.' />
|
||||||
<Page.Content>
|
<Page.Content>
|
||||||
<UserProfile mt='14'>
|
<UserProfile mt='14'>
|
||||||
<UserProfile.Base>
|
<UserProfile.Base>
|
||||||
@ -57,22 +140,23 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
</UserProfile.Info>
|
</UserProfile.Info>
|
||||||
</UserProfile.Base>
|
</UserProfile.Base>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(handleUpdateProfile)}>
|
||||||
<UserProfile.Settings>
|
<UserProfile.Settings>
|
||||||
{/* Personal Info */}
|
{/* Personal Info */}
|
||||||
<UserProfile.Settings.Entry>
|
<UserProfile.Settings.Entry>
|
||||||
<UserProfile.Settings.Title title={'Personal Info'} />
|
<UserProfile.Settings.Title title={'Personal Info'} />
|
||||||
<UserProfile.Settings.EntryContent>
|
<UserProfile.Settings.EntryContent>
|
||||||
<Stack spacing={'6'}>
|
<Stack spacing={'6'}>
|
||||||
<FormControl>
|
<FormControl isReadOnly>
|
||||||
<FormLabel>Fullname</FormLabel>
|
<FormLabel>Fullname</FormLabel>
|
||||||
<Input />
|
<Input defaultValue={me.fullname} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl isReadOnly>
|
<FormControl isReadOnly>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<InputGroup size='md'>
|
<InputGroup size='md'>
|
||||||
<Input defaultValue={'serzh.yarkov@gmail.com'} />
|
<Input defaultValue={me.email} />
|
||||||
<InputRightElement width='4.5rem' pr='0.5rem'>
|
<InputRightElement width='4.5rem' pr='0.5rem'>
|
||||||
<Button h='1.75rem' size='sm'>
|
<Button onClick={handleChangeEmail} h='1.75rem' size='sm'>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
@ -80,7 +164,10 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>About</FormLabel>
|
<FormLabel>About</FormLabel>
|
||||||
<Textarea minH='130px' maxH='500px' />
|
<Textarea minH='130px' maxH='500px' isInvalid={!!errors.about} {...register('about')} />
|
||||||
|
<Text as='small' color='red.500'>
|
||||||
|
{errors.about?.message}
|
||||||
|
</Text>
|
||||||
<FormHelperText>Write description for your profile.</FormHelperText>
|
<FormHelperText>Write description for your profile.</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -94,15 +181,28 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
<Stack spacing={'6'}>
|
<Stack spacing={'6'}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Phone</FormLabel>
|
<FormLabel>Phone</FormLabel>
|
||||||
<Input placeholder='+1 (234) 555-1234' />
|
<Input
|
||||||
|
placeholder='+1 (234) 555-1234'
|
||||||
|
isInvalid={!!errors.phone_number}
|
||||||
|
{...register('phone_number')}
|
||||||
|
/>
|
||||||
|
<Text as='small' color='red.500'>
|
||||||
|
{errors.phone_number?.message}
|
||||||
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Twitter</FormLabel>
|
<FormLabel>Twitter</FormLabel>
|
||||||
<Input placeholder='@sergeyyarkov' />
|
<Input placeholder='@username' isInvalid={!!errors.twitter_id} {...register('twitter_id')} />
|
||||||
|
<Text as='small' color='red.500'>
|
||||||
|
{errors.twitter_id?.message}
|
||||||
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Telegram</FormLabel>
|
<FormLabel>Telegram</FormLabel>
|
||||||
<Input placeholder='@sergeyyarkov' />
|
<Input placeholder='@username' isInvalid={!!errors.telegram_id} {...register('telegram_id')} />
|
||||||
|
<Text as='small' color='red.500'>
|
||||||
|
{errors.telegram_id?.message}
|
||||||
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
</UserProfile.Settings.EntryContent>
|
</UserProfile.Settings.EntryContent>
|
||||||
@ -117,7 +217,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
<InputGroup size='md'>
|
<InputGroup size='md'>
|
||||||
<Input defaultValue={'**********'} />
|
<Input defaultValue={'**********'} />
|
||||||
<InputRightElement width='4.5rem' pr='0.5rem'>
|
<InputRightElement width='4.5rem' pr='0.5rem'>
|
||||||
<Button h='1.75rem' size='sm'>
|
<Button onClick={handleChangePassword} h='1.75rem' size='sm'>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
@ -143,23 +243,23 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
<UserProfile.Settings.Entry>
|
<UserProfile.Settings.Entry>
|
||||||
<UserProfile.Settings.Title title='Night Theme' />
|
<UserProfile.Settings.Title title='Night Theme' />
|
||||||
<UserProfile.Settings.EntryContent>
|
<UserProfile.Settings.EntryContent>
|
||||||
<Flex mt={{ base: '6', md: '0' }}>
|
<Box>
|
||||||
<Switch size='lg' />
|
<SwitchDarkThemeButton />
|
||||||
</Flex>
|
</Box>
|
||||||
</UserProfile.Settings.EntryContent>
|
</UserProfile.Settings.EntryContent>
|
||||||
</UserProfile.Settings.Entry>
|
</UserProfile.Settings.Entry>
|
||||||
|
|
||||||
|
{/* Save Button */}
|
||||||
<UserProfile.Settings.Entry>
|
<UserProfile.Settings.Entry>
|
||||||
<UserProfile.Settings.Title />
|
<UserProfile.Settings.Title />
|
||||||
<UserProfile.Settings.EntryContent>
|
<UserProfile.Settings.EntryContent>
|
||||||
<Box>
|
<Box>
|
||||||
<Button variant={'outline'} leftIcon={<MdOutlineSave />}>
|
<SaveButton type='submit' isLoading={isLoading} isDisabled={!isDirty} />
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
</UserProfile.Settings.EntryContent>
|
</UserProfile.Settings.EntryContent>
|
||||||
</UserProfile.Settings.Entry>
|
</UserProfile.Settings.Entry>
|
||||||
</UserProfile.Settings>
|
</UserProfile.Settings>
|
||||||
|
</form>
|
||||||
</UserProfile>
|
</UserProfile>
|
||||||
</Page.Content>
|
</Page.Content>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
import { ApiServiceInstance } from '.';
|
import { ApiServiceInstance } from '.';
|
||||||
import * as helpers from '@educt/helpers';
|
import * as helpers from '@educt/helpers';
|
||||||
import { IApiRespose, IMe, IUser, IUserContacts } from '@educt/interfaces';
|
import { IApiRespose, IMe, IUser, IUserContacts, IUserInfo } from '@educt/interfaces';
|
||||||
import {
|
import {
|
||||||
CreateUserParamsType,
|
CreateUserParamsType,
|
||||||
FetchUsersParamsType,
|
FetchUsersParamsType,
|
||||||
UpdateUserParamsType,
|
UpdateUserParamsType,
|
||||||
UpdateUserContactsParamsType,
|
UpdateUserContactsParamsType,
|
||||||
|
UpdateUserInfoParamsType,
|
||||||
} from '@educt/types';
|
} from '@educt/types';
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
@ -108,6 +109,17 @@ class UserService {
|
|||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user info
|
||||||
|
*
|
||||||
|
* @param data Fields to update
|
||||||
|
* @returns Updated fields
|
||||||
|
*/
|
||||||
|
public async updateInfo(data: UpdateUserInfoParamsType): Promise<IApiRespose<Partial<IUserInfo>>> {
|
||||||
|
const result = await this.api.patch('v1/me/info', data);
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update password of authorized user
|
* Update password of authorized user
|
||||||
*
|
*
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
CreateUserParamsType,
|
CreateUserParamsType,
|
||||||
FetchUsersParamsType,
|
FetchUsersParamsType,
|
||||||
UpdateUserContactsParamsType,
|
UpdateUserContactsParamsType,
|
||||||
|
UpdateUserInfoParamsType,
|
||||||
UpdateUserParamsType,
|
UpdateUserParamsType,
|
||||||
} from '@educt/types';
|
} from '@educt/types';
|
||||||
import { IUser, IMe, IPaginationMeta } from '@educt/interfaces';
|
import { IUser, IMe, IPaginationMeta } from '@educt/interfaces';
|
||||||
|
|||||||
5
src/types/index.d.ts
vendored
5
src/types/index.d.ts
vendored
@ -134,6 +134,7 @@ export type CreateUserParamsType = {
|
|||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
export type UpdateUserParamsType = { first_name?: string; last_name?: string; email?: string };
|
export type UpdateUserParamsType = { first_name?: string; last_name?: string; email?: string };
|
||||||
|
|
||||||
export type UpdateUserContactsParamsType = {
|
export type UpdateUserContactsParamsType = {
|
||||||
phone_number?: string | null | undefined;
|
phone_number?: string | null | undefined;
|
||||||
twitter_id?: string | null | undefined;
|
twitter_id?: string | null | undefined;
|
||||||
@ -141,6 +142,10 @@ export type UpdateUserContactsParamsType = {
|
|||||||
vk_id?: string | null | undefined;
|
vk_id?: string | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateUserInfoParamsType = {
|
||||||
|
about?: string | null | undefined;
|
||||||
|
} & UpdateUserContactsParamsType;
|
||||||
|
|
||||||
export type CreateCourseParamsType = {
|
export type CreateCourseParamsType = {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user