mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: create user modal layout setup & validator
This commit is contained in:
parent
f8ab92b312
commit
7bb1141af2
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
import { Box, Stack } from '@chakra-ui/layout';
|
import { Box, Stack } from '@chakra-ui/layout';
|
||||||
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
||||||
@ -10,6 +10,7 @@ import UpdatePasswordSchema from './UpdatePasswordForm.validator';
|
|||||||
import { useToast } from '@chakra-ui/toast';
|
import { useToast } from '@chakra-ui/toast';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { ProfilePageViewContext } from 'contexts';
|
import { ProfilePageViewContext } from 'contexts';
|
||||||
|
import useIsMountedRef from 'hooks/useIsMountedRef';
|
||||||
|
|
||||||
type UpdatePasswordInputType = {
|
type UpdatePasswordInputType = {
|
||||||
old_password: string;
|
old_password: string;
|
||||||
@ -23,6 +24,8 @@ type UpdatePasswordInputType = {
|
|||||||
const UpdatePasswordForm: React.FC = () => {
|
const UpdatePasswordForm: React.FC = () => {
|
||||||
const { setStatusPageView } = useContext(ProfilePageViewContext);
|
const { setStatusPageView } = useContext(ProfilePageViewContext);
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
|
const isMountedRef = useIsMountedRef();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
register,
|
||||||
@ -38,10 +41,11 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
|
|
||||||
const onSubmit: SubmitHandler<UpdatePasswordInputType> = async ({ old_password, new_password, confirm_password }) => {
|
const onSubmit: SubmitHandler<UpdatePasswordInputType> = async ({ old_password, new_password, confirm_password }) => {
|
||||||
try {
|
try {
|
||||||
|
setLoading(true);
|
||||||
await userStore.updateCurrentUserPassword(old_password, new_password);
|
await userStore.updateCurrentUserPassword(old_password, new_password);
|
||||||
toast({ title: 'Password changed.', status: 'success' });
|
toast({ title: 'Password changed.', status: 'success' });
|
||||||
reset();
|
reset();
|
||||||
setStatusPageView('default');
|
setStatusPageView({ status: 'default' });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
@ -54,6 +58,10 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +110,15 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button colorScheme='blue' mt='4' type='submit' size='md' variant='outline' loadingText='Saving...'>
|
<Button
|
||||||
|
colorScheme='blue'
|
||||||
|
mt='4'
|
||||||
|
type='submit'
|
||||||
|
size='md'
|
||||||
|
variant='outline'
|
||||||
|
isLoading={loading}
|
||||||
|
loadingText='Saving...'
|
||||||
|
>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
FormHelperText,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
Divider,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
|
import { MdSupervisorAccount } from 'react-icons/md';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { UserRoleEnum } from 'enums';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useDisclosure } from '@chakra-ui/hooks';
|
||||||
|
|
||||||
|
const CreateUserModal: React.FC = () => {
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={onOpen} variant='outline' colorScheme='blue' leftIcon={<AddIcon />}>
|
||||||
|
Create new
|
||||||
|
</Button>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<MdSupervisorAccount />
|
||||||
|
<Text ml={2}>Create new user</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<Divider />
|
||||||
|
<ModalBody pb={6}>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>First name</FormLabel>
|
||||||
|
<Input placeholder='First name' />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<FormLabel>Last name</FormLabel>
|
||||||
|
<Input placeholder='Last name' />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<FormLabel>Login</FormLabel>
|
||||||
|
<Input placeholder='e.g student893' />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<Input placeholder='example@email.com' />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<FormLabel>Role</FormLabel>
|
||||||
|
<Select w='full' mr='2'>
|
||||||
|
<option value={UserRoleEnum.STUDENT}>Student</option>
|
||||||
|
<option value={UserRoleEnum.TEACHER}>Teacher</option>
|
||||||
|
<option value={UserRoleEnum.ADMIN}>Administrator</option>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
<Input placeholder='******' />
|
||||||
|
<FormHelperText>Must be at least 6 characters</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant='outline' mr={3} colorScheme='blue' leftIcon={<AddIcon />}>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateUserModal;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import yup from 'schema';
|
||||||
|
import { UserRoleEnum } from 'enums';
|
||||||
|
|
||||||
|
const CreateUserSchema = yup.object().shape({
|
||||||
|
first_name: yup.string().required('First field name is required'),
|
||||||
|
last_name: yup.string().required('Last field name is required'),
|
||||||
|
login: yup.string().required('Login field is required'),
|
||||||
|
email: yup.string().required('Email field is required'),
|
||||||
|
role: yup.mixed<UserRoleEnum>().required('Role field is required').oneOf(Object.values(UserRoleEnum)),
|
||||||
|
password: yup.string().required('Password field is required').min(6, 'Password must be at least 6 characters.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CreateUserSchema;
|
||||||
3
src/pages/users/components/CreateUserModal/index.ts
Normal file
3
src/pages/users/components/CreateUserModal/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import CreateUserModal from './CreateUserModal';
|
||||||
|
|
||||||
|
export default CreateUserModal;
|
||||||
@ -73,7 +73,7 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Flex mt='auto' mb='16' flexDirection={{ base: 'column', sm: 'column', md: 'row' }}>
|
<Flex mt='auto' mb='6rem' flexDirection={{ base: 'column', sm: 'column', md: 'row' }}>
|
||||||
<Box textAlign={{ base: 'center', sm: 'center', md: 'left' }} mb={{ base: '2', md: '0' }}>
|
<Box textAlign={{ base: 'center', sm: 'center', md: 'left' }} mb={{ base: '2', md: '0' }}>
|
||||||
Page <b>{pagination.current_page}</b> of {pagesCount}
|
Page <b>{pagination.current_page}</b> of {pagesCount}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Box, Flex, Button, Heading, Text } from '@chakra-ui/react';
|
import { Box, Flex, Button, Heading, Text } from '@chakra-ui/react';
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
|
||||||
import { FaFileExcel } from 'react-icons/fa';
|
import { FaFileExcel } from 'react-icons/fa';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,6 +27,7 @@ import { useLocation } from 'react-router';
|
|||||||
* Context
|
* Context
|
||||||
*/
|
*/
|
||||||
import { UsersPageContext } from 'contexts';
|
import { UsersPageContext } from 'contexts';
|
||||||
|
import CreateUserModal from './components/CreateUserModal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users Page
|
* Users Page
|
||||||
@ -46,23 +46,21 @@ const UsersPage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UsersPageContext.Provider value={{ searchingRole, setSearchingRole, loading, setLoading, search, setSearch }}>
|
<UsersPageContext.Provider value={{ searchingRole, setSearchingRole, loading, setLoading, search, setSearch }}>
|
||||||
<Box h='full'>
|
<Box h='100%'>
|
||||||
<Heading as='h1'>User management</Heading>
|
<Heading as='h1'>User management</Heading>
|
||||||
<Text mt='2'>You can add or delete your users on this page.</Text>
|
<Text mt='2'>You can add or delete your users on this page.</Text>
|
||||||
{userStore.users !== null ? (
|
{userStore.users !== null && userStore.pagination !== undefined ? (
|
||||||
<Flex mt='5' flexDir='column' h='full'>
|
<Flex mt='5' flexDir='column' h='full'>
|
||||||
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
|
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
|
||||||
<UserSearch />
|
<UserSearch />
|
||||||
<Flex sx={{ gap: '10px' }}>
|
<Flex sx={{ gap: '10px' }}>
|
||||||
<Button variant='outline' color='blue.500' leftIcon={<AddIcon />}>
|
<CreateUserModal />
|
||||||
Create new
|
|
||||||
</Button>
|
|
||||||
<Button variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}>
|
<Button variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}>
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
{userStore.pagination && <UserList users={userStore.users} pagination={userStore.pagination} />}
|
<UserList users={userStore.users} pagination={userStore.pagination} />
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<LoadingPage />
|
<LoadingPage />
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchAll(params?: FetchUsersParamsType): Promise<IApiRespose<IUser[]>> {
|
public async fetchAll(params?: FetchUsersParamsType): Promise<IApiRespose<IUser[]>> {
|
||||||
const { page = 1, limit = 1, role, search } = params || {};
|
const { page = 1, limit = 6, role, search } = params || {};
|
||||||
const result = await this.api.get('/v1/users', {
|
const result = await this.api.get('/v1/users', {
|
||||||
params: {
|
params: {
|
||||||
page,
|
page,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user