Merge pull request 'feat: rewrite the form to react-hook-form and add validation' (#80) from feature/react-hook-form into main
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				it-academy/dry-wash-pl/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	it-academy/dry-wash-pl/pipeline/head This commit looks good
				
			Reviewed-on: #80 Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
This commit is contained in:
		
						commit
						bd66f00b81
					
				| @ -86,6 +86,15 @@ | |||||||
|   "dry-wash.arm.master.drawer.inputPhone.placeholder": "Enter Phone Number", |   "dry-wash.arm.master.drawer.inputPhone.placeholder": "Enter Phone Number", | ||||||
|   "dry-wash.arm.master.drawer.button.save": "Save", |   "dry-wash.arm.master.drawer.button.save": "Save", | ||||||
|   "dry-wash.arm.master.drawer.button.cancel": "Cancel", |   "dry-wash.arm.master.drawer.button.cancel": "Cancel", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.create-master": "Master created", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.empty-fields": "Fields cannot be empty", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.base": "Error", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.create-master": "Error creating master", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.create-master-details": "Failed to add master. Please try again", | ||||||
|  |   "dry-wash.arm.master.drawer.form.name.required": "Master name is required", | ||||||
|  |   "dry-wash.arm.master.drawer.form.phone.required": "Phone number is required", | ||||||
|  |   "dry-wash.arm.master.drawer.form.phone.pattern": "Invalid phone number", | ||||||
|  |   "dry-wash.arm.master.drawer.form.name.minLength": "Name must contain at least 2 characters", | ||||||
|   "dry-wash.arm.master.sideBar.orders": "Orders", |   "dry-wash.arm.master.sideBar.orders": "Orders", | ||||||
|   "dry-wash.arm.master.sideBar.master": "Masters", |   "dry-wash.arm.master.sideBar.master": "Masters", | ||||||
|   "dry-wash.arm.master.sideBar.title": "Dry Master", |   "dry-wash.arm.master.sideBar.title": "Dry Master", | ||||||
|  | |||||||
| @ -36,6 +36,15 @@ | |||||||
|   "dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона", |   "dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона", | ||||||
|   "dry-wash.arm.master.drawer.button.save": "Сохранить", |   "dry-wash.arm.master.drawer.button.save": "Сохранить", | ||||||
|   "dry-wash.arm.master.drawer.button.cancel": "Отменить", |   "dry-wash.arm.master.drawer.button.cancel": "Отменить", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.create-master": "Мастер создан", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.empty-fields": "Поля не могут быть пустыми", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.base": "Ошибка", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.create-master": "Ошибка при создании мастера", | ||||||
|  |   "dry-wash.arm.master.drawer.toast.error.create-master-details": "Не удалось добавить мастера. Попробуйте еще раз", | ||||||
|  |   "dry-wash.arm.master.drawer.form.name.required": "Имя мастера обязательно", | ||||||
|  |   "dry-wash.arm.master.drawer.form.phone.required": "Телефон обязателен", | ||||||
|  |   "dry-wash.arm.master.drawer.form.phone.pattern": "Некорректный номер телефона", | ||||||
|  |   "dry-wash.arm.master.drawer.form.name.minLength": "Имя должно содержать минимум 2 символа", | ||||||
|   "dry-wash.arm.master.sideBar.orders": "Заказы", |   "dry-wash.arm.master.sideBar.orders": "Заказы", | ||||||
|   "dry-wash.arm.master.sideBar.master": "Мастера", |   "dry-wash.arm.master.sideBar.master": "Мастера", | ||||||
|   "dry-wash.arm.master.sideBar.title": "Сухой мастер", |   "dry-wash.arm.master.sideBar.title": "Сухой мастер", | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect } from 'react'; | ||||||
|  | import { useForm, SubmitHandler } from 'react-hook-form'; | ||||||
| import { | import { | ||||||
|   Button, |   Button, | ||||||
|   FormControl, |   FormControl, | ||||||
| @ -14,107 +15,136 @@ import { | |||||||
|   useToast, |   useToast, | ||||||
|   InputGroup, |   InputGroup, | ||||||
|   InputLeftElement, |   InputLeftElement, | ||||||
|  |   FormErrorMessage, | ||||||
| } from '@chakra-ui/react'; | } from '@chakra-ui/react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { PhoneIcon } from '@chakra-ui/icons'; | import { PhoneIcon } from '@chakra-ui/icons'; | ||||||
| 
 | 
 | ||||||
| import { api } from '../../__data__/service/api'; | import { api } from '../../__data__/service/api'; | ||||||
|  | import showToast from '../../helpers/showToast'; | ||||||
|  | import { DrawerInputs } from '../../models/arm/form'; | ||||||
| 
 | 
 | ||||||
| const MasterDrawer = ({ isOpen, onClose }) => { | interface MasterDrawerProps { | ||||||
|   const [addMaster, { error, isSuccess }] = api.useAddMasterMutation(); |   isOpen: boolean; | ||||||
|   const toast = useToast(); |   onClose: () => void; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   const [newMaster, setNewMaster] = useState({ name: '', phone: '' }); | const MasterDrawer = ({ isOpen, onClose }: MasterDrawerProps) => { | ||||||
|  |   const { | ||||||
|  |     register, | ||||||
|  |     handleSubmit, | ||||||
|  |     reset, | ||||||
|  |     formState: { errors }, | ||||||
|  |   } = useForm<DrawerInputs>(); | ||||||
| 
 | 
 | ||||||
|   const handleSave = async () => { |   const { t } = useTranslation('~', { | ||||||
|  |     keyPrefix: 'dry-wash.arm.master.drawer', | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const onSubmit: SubmitHandler<DrawerInputs> = async (data) => { | ||||||
|     const trimMaster = { |     const trimMaster = { | ||||||
|       phone: newMaster.phone.trim(), |       name: data.name.trim(), | ||||||
|       name: newMaster.name.trim(), |       phone: data.phone.trim(), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if (trimMaster.name === '' || trimMaster.phone === '') { |     const isEmptyFields = trimMaster.name === '' || trimMaster.phone === ''; | ||||||
|  | 
 | ||||||
|  |     if (isEmptyFields) { | ||||||
|  |       showToast({ | ||||||
|  |         toast, | ||||||
|  |         title: t('toast.error.base'), | ||||||
|  |         description: t('toast.error.empty-fields'), | ||||||
|  |         status: 'error', | ||||||
|  |       }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addMaster(trimMaster); |     await addMaster(trimMaster); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   const [addMaster, { error, isSuccess }] = api.useAddMasterMutation(); | ||||||
|  |   const toast = useToast(); | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (isSuccess) { |     if (isSuccess) { | ||||||
|       toast({ |       showToast({ | ||||||
|         title: 'Мастер создан.', |         toast, | ||||||
|         description: `Мастер "${newMaster.name}" успешно добавлен.`, |         title: t('toast.create-master'), | ||||||
|         status: 'success', |         status: 'success', | ||||||
|         duration: 5000, |  | ||||||
|         isClosable: true, |  | ||||||
|         position: 'top-right', |  | ||||||
|       }); |       }); | ||||||
|  |       reset(); | ||||||
|       onClose(); |       onClose(); | ||||||
|     } |     } | ||||||
|   }, [isSuccess]); |   }, [isSuccess]); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (error) { |     if (error) { | ||||||
|       toast({ |       showToast({ | ||||||
|         title: 'Ошибка при создании мастера.', |         toast, | ||||||
|         description: 'Не удалось добавить мастера. Попробуйте еще раз.', |         title: t('toast.error.create-master'), | ||||||
|  |         description: t('toast.error.create-master-details'), | ||||||
|         status: 'error', |         status: 'error', | ||||||
|         duration: 5000, |  | ||||||
|         isClosable: true, |  | ||||||
|         position: 'top-right', |  | ||||||
|       }); |       }); | ||||||
|       console.error(error); |       console.error(error); | ||||||
|     } |     } | ||||||
|   }, [error]); |   }, [error]); | ||||||
| 
 | 
 | ||||||
|   const { t } = useTranslation('~', { |  | ||||||
|     keyPrefix: 'dry-wash.arm.master.drawer', |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <Drawer isOpen={isOpen} onClose={onClose} size='md'> |     <Drawer isOpen={isOpen} onClose={onClose} size='md'> | ||||||
|       <DrawerOverlay /> |       <DrawerOverlay /> | ||||||
|       <DrawerContent> |       <DrawerContent> | ||||||
|         <DrawerCloseButton /> |         <form onSubmit={handleSubmit(onSubmit)}> | ||||||
|         <DrawerHeader>{t('title')}</DrawerHeader> |           <DrawerCloseButton /> | ||||||
|         <DrawerBody> |           <DrawerHeader>{t('title')}</DrawerHeader> | ||||||
|           <FormControl mb='4'> |           <DrawerBody> | ||||||
|             <FormLabel>{t('inputName.label')}</FormLabel> |             <FormControl mb='4' isInvalid={!!errors.name}> | ||||||
|             <Input |               <FormLabel>{t('inputName.label')}</FormLabel> | ||||||
|               // isInvalid
 |  | ||||||
|               value={newMaster.name} |  | ||||||
|               onChange={(e) => |  | ||||||
|                 setNewMaster({ ...newMaster, name: e.target.value }) |  | ||||||
|               } |  | ||||||
|               placeholder={t('inputName.placeholder')} |  | ||||||
|             /> |  | ||||||
|           </FormControl> |  | ||||||
|           <FormControl> |  | ||||||
|             <FormLabel>{t('inputPhone.label')}</FormLabel> |  | ||||||
| 
 |  | ||||||
|             <InputGroup> |  | ||||||
|               <InputLeftElement pointerEvents='none'> |  | ||||||
|                 <PhoneIcon color='gray.300' /> |  | ||||||
|               </InputLeftElement> |  | ||||||
|               <Input |               <Input | ||||||
|                 // isInvalid
 |                 {...register('name', { | ||||||
|                 value={newMaster.phone} |                   required: t('form.name.required'), | ||||||
|                 onChange={(e) => |                   minLength: { | ||||||
|                   setNewMaster({ ...newMaster, phone: e.target.value }) |                     value: 2, | ||||||
|                 } |                     message: t('form.name.minLength'), | ||||||
|                 placeholder={t('inputPhone.placeholder')} |                   }, | ||||||
|  |                 })} | ||||||
|  |                 placeholder={t('inputName.placeholder')} | ||||||
|               /> |               /> | ||||||
|             </InputGroup> |               <FormErrorMessage> | ||||||
|           </FormControl> |                 {errors.name && errors.name.message} | ||||||
|         </DrawerBody> |               </FormErrorMessage> | ||||||
|         <DrawerFooter> |             </FormControl> | ||||||
|           <Button colorScheme='teal' mr={3} onClick={handleSave}> |             <FormControl isInvalid={!!errors.phone}> | ||||||
|             {t('button.save')} |               <FormLabel>{t('inputPhone.label')}</FormLabel> | ||||||
|           </Button> |               <InputGroup> | ||||||
|           <Button variant='ghost' onClick={onClose}> |                 <InputLeftElement pointerEvents='none'> | ||||||
|             {t('button.cancel')} |                   <PhoneIcon color='gray.300' /> | ||||||
|           </Button> |                 </InputLeftElement> | ||||||
|         </DrawerFooter> |                 <Input | ||||||
|  |                   {...register('phone', { | ||||||
|  |                     required: t('form.phone.required'), | ||||||
|  |                     pattern: { | ||||||
|  |                       value: /^(\+7|8)\d{10}$/, | ||||||
|  |                       message: t('form.phone.pattern'), | ||||||
|  |                     }, | ||||||
|  |                     setValueAs: (value) => value.replace(/[^\d+]/g, ''), | ||||||
|  |                   })} | ||||||
|  |                   placeholder={t('inputPhone.placeholder')} | ||||||
|  |                 /> | ||||||
|  |               </InputGroup> | ||||||
|  |               <FormErrorMessage> | ||||||
|  |                 {errors.phone && errors.phone.message} | ||||||
|  |               </FormErrorMessage> | ||||||
|  |             </FormControl> | ||||||
|  |           </DrawerBody> | ||||||
|  |           <DrawerFooter> | ||||||
|  |             <Button colorScheme='teal' mr={3} type='submit'> | ||||||
|  |               {t('button.save')} | ||||||
|  |             </Button> | ||||||
|  |             <Button variant='ghost' onClick={onClose}> | ||||||
|  |               {t('button.cancel')} | ||||||
|  |             </Button> | ||||||
|  |           </DrawerFooter> | ||||||
|  |         </form> | ||||||
|       </DrawerContent> |       </DrawerContent> | ||||||
|     </Drawer> |     </Drawer> | ||||||
|   ); |   ); | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								src/helpers/showToast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/helpers/showToast.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | import { UseToastOptions } from '@chakra-ui/react'; | ||||||
|  | 
 | ||||||
|  | interface ShowToast { | ||||||
|  |   toast: (options: UseToastOptions) => void; | ||||||
|  |   title: string; | ||||||
|  |   description?: string; | ||||||
|  |   status: 'info' | 'warning' | 'success' | 'error'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const showToast = ({ toast, title, description, status }: ShowToast) => { | ||||||
|  |   toast({ | ||||||
|  |     title, | ||||||
|  |     description, | ||||||
|  |     status, | ||||||
|  |     duration: 5000, | ||||||
|  |     isClosable: true, | ||||||
|  |     position: 'top-right', | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default showToast; | ||||||
							
								
								
									
										4
									
								
								src/models/arm/form.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/models/arm/form.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | export type DrawerInputs = { | ||||||
|  |   phone: string; | ||||||
|  |   name: string; | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user