feat: added eslint & fixed errors

This commit is contained in:
Sergey Yarkov 2021-12-11 19:31:36 +03:00
parent 5c0599fe0a
commit ef07c9c7be
32 changed files with 1576 additions and 234 deletions

20
.eslintrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"react/display-name": "off",
"react/prop-types": "off"
}
}

1382
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,11 @@
"@types/react-helmet": "^6.1.2",
"@types/react-router-dom": "^5.1.8",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"@vitejs/plugin-react": "^1.0.0",
"eslint": "^8.4.1",
"eslint-plugin-react": "^7.27.1",
"rollup-plugin-visualizer": "^5.5.2",
"typescript": "^4.5.2",
"vite": "^2.6.4"

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import {
AlertDialog,
AlertDialogBody,

View File

@ -36,7 +36,7 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
if (isMountedRef.current && location.pathname !== '/courses') {
history.push('/courses');
}
} catch (error: any) {
} catch (error) {
console.error(error);
}
}

View File

@ -28,7 +28,7 @@ const DeleteLessonDialog: React.FC<DeleteLessonDialogPropsType> = ({ lesson, isO
if (isConfirmed) {
try {
await deleteLesson(lesson.id);
} catch (error: any) {
} catch (error) {
console.error(error);
}
}

View File

@ -50,7 +50,7 @@ const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose
role: searchingRole,
search,
});
} catch (error: any) {
} catch (error) {
console.error(error);
}
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import Cookies from 'js-cookie';
import axios, { AxiosError } from 'axios';
import { Redirect } from 'react-router';
import { Icon, Box, Heading, Text } from '@chakra-ui/react';
import { MdError } from 'react-icons/md';
@ -11,7 +12,7 @@ import { useEffect } from 'react';
import { useToast } from '@chakra-ui/toast';
import { useRootStore } from '@educt/hooks/useRootStore';
const ErrorFallback: React.FC<{ error: any }> = props => {
const ErrorFallback: React.FC<{ error: Error | AxiosError }> = props => {
const { userStore, authStore } = useRootStore();
const { error } = props;
const toast = useToast();
@ -20,6 +21,7 @@ const ErrorFallback: React.FC<{ error: any }> = props => {
toast({ title: `${error.message}`, duration: 10000, isClosable: true, status: 'error' });
});
if (axios.isAxiosError(error)) {
if (error.response) {
if (error.response.status === 401) {
userStore.me = null;
@ -27,6 +29,7 @@ const ErrorFallback: React.FC<{ error: any }> = props => {
Cookies.remove('logged_in');
} else if (error.response.status === 404) return <Redirect to='/404' />;
}
}
return (
<Box textAlign='center' mt={40}>

View File

@ -47,7 +47,9 @@ export const PasswordField = React.forwardRef<HTMLInputElement, PasswordFieldPro
<FormLabel>Password</FormLabel>
</Flex>
<InputGroup>
<InputLeftElement children={<Icon as={MdVpnKey} />} />
<InputLeftElement>
<Icon as={MdVpnKey} />
</InputLeftElement>
<InputRightElement>
<IconButton
bg='transparent !important'
@ -74,3 +76,5 @@ export const PasswordField = React.forwardRef<HTMLInputElement, PasswordFieldPro
</FormControl>
);
});
PasswordField.displayName = 'PasswordField';

View File

@ -19,7 +19,6 @@ import FileSelect from '@educt/components/FileSelect';
/**
* Hooks
*/
import { useToast } from '@chakra-ui/toast';
import { useErrorHandler } from 'react-error-boundary';
/**
@ -51,7 +50,6 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
} = reactHookForm;
const handleError = useErrorHandler();
const toast = useToast();
/**
* Load users into select field
@ -64,13 +62,9 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
label: user.fullname,
value: user.id,
}));
} catch (error: any) {
if (error.response) {
toast({ title: 'Error' });
} else {
} catch (error) {
handleError(error);
}
}
};
/**
@ -83,13 +77,9 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
label: category.title,
value: category.id,
}));
} catch (error: any) {
if (error.response) {
toast({ title: 'Error' });
} else {
} catch (error) {
handleError(error);
}
}
};
const watchTitle = watch('title');
@ -102,14 +92,11 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
<FormLabel>Name</FormLabel>
<InputGroup>
<Input pr='60px' size='md' placeholder='Course name' type='text' {...register('title')} />
<InputRightElement
mr='2'
children={
<InputRightElement mr='2'>
<Text as='small' color={!!errors.title || watchTitle?.length > 90 ? 'red.500' : 'gray.500'}>
{watchTitle?.length || 0}/90
</Text>
}
/>
</InputRightElement>
</InputGroup>
<FormHelperText>e.g Web Development in Javascript</FormHelperText>
<Text as='small' color='red.500'>

View File

@ -26,9 +26,7 @@ import { useCreateCourse } from '@educt/hooks/queries';
*/
import CourseFormSchema from './CourseForm.validator';
type CreateFormCoursePropsType = {};
const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
const CreateFormCourse: React.FC = () => {
const form = useForm<InputFields>({ resolver: yupResolver(CourseFormSchema) });
const { createCourse, isLoading } = useCreateCourse();
const history = useHistory();

View File

@ -29,10 +29,10 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose
const ids = students.map(s => s.id);
await CourseServiceInstance.attachStudentsList(courseId, ids);
toast({ title: 'Students successfully attached.', status: 'success' });
toast({ title: 'Students successfully added.', status: 'success' });
onClose();
} catch (error: any) {
throw error;
} catch (error) {
console.error(error);
} finally {
if (isMountedRef.current) setIsLoading(false);
}

View File

@ -42,7 +42,7 @@ const SelectUsersInput: React.FC<SelectUsersInputPropsType> = ({ placeholder, se
setIsLoading(true);
const { data } = await UserServiceInstance.fetchAll({ search, ...params });
setUsers(data);
} catch (error: any) {
} catch (error) {
handleError(error);
} finally {
setIsLoading(false);
@ -58,6 +58,7 @@ const SelectUsersInput: React.FC<SelectUsersInputPropsType> = ({ placeholder, se
return updated;
});
};
const handleRemoveSelected = (id: IUser['id']) => () => {
setSelected(prev => {
const updated = prev.filter(u => u.id !== id);
@ -91,7 +92,9 @@ const SelectUsersInput: React.FC<SelectUsersInputPropsType> = ({ placeholder, se
type='text'
placeholder={placeholder || 'Search for users...'}
/>
<InputRightElement pointerEvents='none' children={<SearchIcon color='gray.300' />} />
<InputRightElement pointerEvents='none'>
<SearchIcon color='gray.300' />
</InputRightElement>
</InputGroup>
{isLoading && (

View File

@ -1,4 +1,4 @@
import { BadgeProps, Badge, Box } from '@chakra-ui/react';
import { BadgeProps, Badge } from '@chakra-ui/react';
import React from 'react';
import { UserRoleEnum } from '@educt/enums';
import { IUserRole } from '@educt/interfaces';

View File

@ -6,23 +6,23 @@ export const StoreContext = createContext<RootStore | undefined>(undefined);
export const ChangeEmailPageContext = createContext<ChangeEmailPageContextType>({
isCodeSent: false,
setIsCodeSent: () => {},
setIsCodeSent: () => null,
confirmEmailData: undefined,
setConfirmEmailData: () => {},
setConfirmEmailData: () => null,
});
export const UsersPageContext = createContext<UsersPageContextType>({
searchingRole: undefined,
setSearchingRole: () => {},
setSearchingRole: () => null,
searchingPage: 1,
setSearchingPage: () => {},
setSearchingPage: () => null,
search: undefined,
setSearch: () => {},
setSearch: () => null,
});
export const CoursesPageContext = createContext<CoursesPageContextType>({
selectedCategory: undefined,
setSelectedCategory: () => {},
setSelectedCategory: () => null,
courseStatus: undefined,
setCourseStatus: () => {},
setCourseStatus: () => null,
});

View File

@ -4,7 +4,7 @@ import { useRootStore } from '../../useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync';
type LogoutResultDataType = {};
type LogoutResultDataType = Record<string, never>;
const useLogout = () => {
const { authStore } = useRootStore();
@ -18,7 +18,7 @@ const useLogout = () => {
history.push('/auth');
toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
return result.data;
} catch (error: any) {
} catch (error) {
handleError(error);
return Promise.reject(error);
}

View File

@ -7,7 +7,7 @@ import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore';
import { useToast } from '@chakra-ui/toast';
type SetCourseStatusResultDataType = {};
type SetCourseStatusResultDataType = Record<string, never>;
export type SetCourseStatusFnType = (id: string, newStatus: CourseStatusEnum) => Promise<SetCourseStatusResultDataType>;
const useSetCourseStatus = () => {

View File

@ -9,9 +9,8 @@ import {
Icon,
Text,
Stack,
Box,
} from '@chakra-ui/react';
import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
import { MdAccountCircle } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup';
/**
@ -58,7 +57,9 @@ const AuthForm: React.FC = () => {
<FormControl id='login'>
<FormLabel>Login</FormLabel>
<InputGroup>
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
<InputLeftElement>
<Icon as={MdAccountCircle} />
</InputLeftElement>
<Input type='text' placeholder='Login' {...register('login')} isInvalid={!!errors.login} />
</InputGroup>
<Text as='small' color='red.500'>

View File

@ -74,13 +74,9 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
try {
const data = await LessonServiceInstance.saveOrder(ids);
return data;
} catch (error: any) {
if (error.response) {
console.error(error);
} else {
} catch (error) {
handleError(error);
}
}
};
const handleDragEnd = (result: DropResult) => {

View File

@ -19,8 +19,8 @@ const BulkActionsMenu: React.FC<BulkActionsMenuPropsType> = ({ selected, rows, s
);
setRows(rows.filter(r => selected.every(s => s.id !== r.id)));
};
const handleDelete = async () => {};
const handleExport = async () => {};
const handleDelete = async () => undefined;
const handleExport = async () => undefined;
return (
<Menu isLazy>

View File

@ -93,7 +93,9 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
<Flex justifyContent='space-between' flexDir={{ base: 'column', lg: 'row' }}>
<Flex mb='2'>
<InputGroup mr='2' borderRadius='lg'>
<InputLeftElement pointerEvents='none' children={<MdSearch color='gray.300' />} />
<InputLeftElement pointerEvents='none'>
<MdSearch color='gray.300' />
</InputLeftElement>
<Input value={search} size='sm' onChange={handleSearch} type='text' placeholder='Search for student...' />
</InputGroup>
</Flex>

View File

@ -52,8 +52,6 @@ import { UsersPageContext } from '@educt/contexts';
import CreateUserSchema from './CreateUserForm.validator';
import { CreateButton } from '@educt/components/Buttons';
type CreateUserFormPropsType = {};
type CreateUserInputType = {
first_name: string;
last_name: string;
@ -63,7 +61,7 @@ type CreateUserInputType = {
password: string;
};
const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
const CreateUserModal: React.FC = () => {
const { userStore } = useRootStore();
const { searchingRole, search } = useContext(UsersPageContext);
const { createUser, isLoading } = useCreateUser();

View File

@ -50,7 +50,9 @@ const UserSearch: React.FC = () => {
<option value={UserRoleEnum.STUDENT}>Student</option>
</Select>
<InputGroup maxW='400px' w='full' size='sm'>
<InputLeftElement pointerEvents='none' children={<SearchIcon color='gray.300' />} />
<InputLeftElement pointerEvents='none'>
<SearchIcon color='gray.300' />
</InputLeftElement>
<Input placeholder='Search for a user...' value={search || ''} onChange={onSearch} />
</InputGroup>
</Flex>

View File

@ -11,7 +11,7 @@ export interface AuthServiceImpl {
/**
* Logout user
*/
requestLogout(): Promise<IApiRespose<{}>>;
requestLogout(): Promise<IApiRespose<Record<string, never>>>;
}
class AuthService implements AuthServiceImpl {
@ -41,7 +41,7 @@ class AuthService implements AuthServiceImpl {
*
* @returns Empty obj
*/
public async requestLogout(): Promise<IApiRespose<{}>> {
public async requestLogout(): Promise<IApiRespose<Record<string, never>>> {
const result = await this.api.post('v1/auth/logout');
return result.data;
}

View File

@ -49,7 +49,7 @@ class CourseService {
* @param status Course status
* @returns Empty data
*/
public async setStatus(id: string, status: CourseStatusEnum): Promise<IApiRespose<{}>> {
public async setStatus(id: string, status: CourseStatusEnum): Promise<IApiRespose<Record<string, never>>> {
const result = await this.api.post(`/v1/courses/${id}/set-status`, {
status,
});
@ -97,7 +97,7 @@ class CourseService {
return result.data;
}
public async attachStudentsList(courseId: string, ids: Array<string>): Promise<IApiRespose<{}>> {
public async attachStudentsList(courseId: string, ids: Array<string>): Promise<IApiRespose<Record<string, never>>> {
const result = await this.api.post(
`/v1/courses/${courseId}/attach-student-list`,
helpers.transformToFormData({ students: ids })

View File

@ -99,7 +99,7 @@ class UserService {
* @param newPassword New password
* @returns Empty object
*/
public async updatePassword(oldPassword: string, newPassword: string): Promise<IApiRespose<{}>> {
public async updatePassword(oldPassword: string, newPassword: string): Promise<IApiRespose<Record<string, never>>> {
const result = await this.api.patch('v1/me/password', {
oldPassword,
newPassword,

View File

@ -14,7 +14,7 @@ import RootStore from './RootStore';
export default class AuthStore {
public root: RootStore;
public isLoggedIn: boolean = !!Cookies.get('logged_in');
public isLoggedIn = !!Cookies.get('logged_in');
constructor(root: RootStore) {
this.root = root;
@ -26,28 +26,20 @@ export default class AuthStore {
}
public async login(login: string, password: string) {
try {
const result = await AuthServiceInstance.requestLogin(login, password);
this.setIsLoggedIn(true);
Cookies.set('logged_in', 'true');
return result;
} catch (error) {
throw error;
}
}
public async logout() {
try {
const result = await AuthServiceInstance.requestLogout();
this.setIsLoggedIn(false);
this.root.userStore.reset();
Cookies.remove('logged_in');
return result;
} catch (error) {
throw error;
}
}
}

View File

@ -20,7 +20,7 @@ export default class CategoryStore {
public categories: ICategory[] | null = null;
public isLoading: boolean = false;
public isLoading = false;
constructor(root: RootStore) {
this.root = root;
@ -41,8 +41,6 @@ export default class CategoryStore {
});
return result;
} catch (error: any) {
throw error;
} finally {
this.setLoading(false);
}

View File

@ -22,7 +22,7 @@ export default class CourseStore {
public courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null = null;
public isLoading: boolean = false;
public isLoading = false;
constructor(root: RootStore) {
this.root = root;
@ -46,15 +46,12 @@ export default class CourseStore {
});
return result;
} catch (error: any) {
throw error;
} finally {
this.setLoading(false);
}
}
public async deleteCourse(id: string) {
try {
const result = await CourseServiceInstance.delete(id);
/**
@ -67,9 +64,6 @@ export default class CourseStore {
});
return result;
} catch (error: any) {
throw error;
}
}
public async setCourseStatus(id: string, status: CourseStatusEnum) {
@ -95,7 +89,7 @@ export default class CourseStore {
*/
const result = await CourseServiceInstance.setStatus(id, status);
return result;
} catch (error: any) {
} catch (error) {
/**
* Resume status if there was an error on request
*/

View File

@ -27,7 +27,7 @@ export default class UserStore {
public pagination: IPaginationMeta | undefined;
public isLoading: boolean = false;
public isLoading = false;
constructor(root: RootStore) {
this.root = root;
@ -49,15 +49,12 @@ export default class UserStore {
});
return result;
} catch (error: any) {
throw error;
} finally {
this.setLoading(false);
}
}
public async loadCurrentUserData() {
try {
const result = await UserServiceInstance.fetchMe();
const {
data: { id, first_name, last_name, fullname, email, roles, contacts, courses, created_at, updated_at },
@ -82,13 +79,9 @@ export default class UserStore {
});
return result;
} catch (error: any) {
throw error;
}
}
public async createUser(data: CreateUserParamsType, paramsContext?: FetchUsersParamsType) {
try {
const result = await UserServiceInstance.create(data);
if (this.users !== null) {
@ -99,13 +92,9 @@ export default class UserStore {
}
return result;
} catch (error: any) {
throw error;
}
}
public async updateUser(id: string, params: UpdateUserParamsType) {
try {
const result = await UserServiceInstance.update(id, params);
runInAction(() => {
@ -119,9 +108,6 @@ export default class UserStore {
});
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -131,7 +117,6 @@ export default class UserStore {
* @returns
*/
public async deleteUser(id: string, paramsContext?: FetchUsersParamsType) {
try {
const result = await UserServiceInstance.delete(id);
if (this.users !== null) {
@ -142,9 +127,6 @@ export default class UserStore {
}
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -154,7 +136,6 @@ export default class UserStore {
* @returns Updated user contacts
*/
public async updateCurrentUserContacts(data: IUserContacts) {
try {
const result = await UserServiceInstance.updateContacts(data);
runInAction(() => {
@ -164,9 +145,6 @@ export default class UserStore {
});
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -177,12 +155,8 @@ export default class UserStore {
* @returns Data result status
*/
public async updateCurrentUserPassword(oldPassword: string, newPassword: string) {
try {
const result = await UserServiceInstance.updatePassword(oldPassword, newPassword);
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -192,12 +166,8 @@ export default class UserStore {
* @returns Expires at code in seconds
*/
public async updateCurrentUserEmail(email: string) {
try {
const result = await UserServiceInstance.updateEmail(email);
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -208,7 +178,6 @@ export default class UserStore {
* @returns Updated user email
*/
public async updateCurrentUserEmailConfirm(email: string, confirmationCode: string) {
try {
const result = await UserServiceInstance.updateEmailConfirm(email, confirmationCode);
runInAction(() => {
if (this.me) {
@ -216,9 +185,6 @@ export default class UserStore {
}
});
return result;
} catch (error: any) {
throw error;
}
}
public reset(): void {

View File

@ -23,7 +23,6 @@ export default class EditCourseStore {
* @returns Course response
*/
public async loadCourseById(id: string) {
try {
const result = await CourseServiceInstance.fetchById(id);
runInAction(() => {
@ -31,9 +30,6 @@ export default class EditCourseStore {
});
return result;
} catch (error: any) {
throw error;
}
}
/**
@ -43,7 +39,6 @@ export default class EditCourseStore {
* @returns Deleted lesson response
*/
public async deleteLessonById(id: string) {
try {
const result = await LessonServiceInstance.deleteLesson(id);
runInAction(() => {
@ -53,9 +48,6 @@ export default class EditCourseStore {
});
return result;
} catch (error) {
throw error;
}
}
/**

View File

@ -1,6 +1,6 @@
import React from 'react';
import { CourseStatusEnum, UserRoleEnum } from '@educt/enums';
import { ICategory, ICourse, IUser } from '@educt/interfaces';
import { ICategory } from '@educt/interfaces';
import { IconType } from 'react-icons/lib';
export type LinkType = {