1+ /* eslint-disable react/jsx-props-no-spreading */
12import React , { useMemo , useEffect , useCallback , useState } from 'react' ;
23import { Button , Checkbox , Header , Select , TextField } from '@megafon/ui-core' ;
34import type { ISelectItem } from '@megafon/ui-core/dist/lib/components/Select/Select' ;
5+ import type { TextFieldProps } from '@megafon/ui-core/dist/lib/components/TextField/TextField' ;
46import { cnCreate } from '@megafon/ui-helpers' ;
57import { ReactComponent as ArrowIcon } from '@megafon/ui-icons/system-16-arrow_left_16.svg' ;
68import { ReactComponent as Minus } from '@megafon/ui-icons/system-16-minus_16.svg' ;
@@ -10,26 +12,28 @@ import type { SimulationResponse, HoverflyMatcher, PairItemRequest, SimulationIt
1012import CollapseWrapper from 'components/CollapseWrapper/CollapseWrapper' ;
1113import './Simulation.pcss' ;
1214import { useSelector } from 'store/hooks' ;
13- import { hightlightHtml , MirrorBodyType } from 'utils' ;
15+ import { convertStringToInteger , hightlightHtml , MirrorBodyType } from 'utils' ;
1416import {
1517 BODY_FORMATS ,
18+ FIELDS_ERROR ,
1619 headerEmpty ,
1720 initialBodyState ,
21+ initialFieldsError ,
1822 initialHeaderQuery ,
1923 initialPairState ,
2024 initialServerState ,
2125 initialState ,
2226 MATCHES ,
2327 METHODS ,
2428 serverStateEmpty ,
25- STATUS_CODES ,
2629} from '../constants' ;
2730import type {
2831 ServerState ,
2932 SimulationsServerState ,
3033 SimulationHeadersQueryState ,
3134 SimulationHeaderState ,
3235 SimulationHtmlState ,
36+ SimulationFieldsErrorState ,
3337} from '../types' ;
3438import {
3539 addOrRemoveEl ,
@@ -44,9 +48,12 @@ import {
4448 getRequireStateList ,
4549 getResponseHeaderStateList ,
4650 getTransitionStateList ,
51+ getVerificationFields ,
52+ isFieldsError ,
4753 mergeBodyStateToCurrentPair ,
4854 mergeCurrentStateToMainState ,
4955 mergeHeaderStateToCurrentPair ,
56+ mergeMatcherValueToCurrentPair ,
5057 mergeServerStateToCurrentPair ,
5158} from '../utils' ;
5259
@@ -58,6 +65,14 @@ require('codemirror/mode/javascript/javascript');
5865require ( 'codemirror/mode/htmlmixed/htmlmixed' ) ;
5966
6067type InputChange = React . ChangeEvent < HTMLInputElement > ;
68+ type InputFocus = React . FocusEvent < HTMLInputElement > ;
69+ type InputPropsType = {
70+ placeholder ?: string ;
71+ name ?: string ;
72+ inputMask ?: string ;
73+ inputMode ?: TextFieldProps [ 'inputMode' ] ;
74+ onBlurField ?: ( e : InputFocus ) => void ;
75+ } ;
6176type ChangeCurrentNames = keyof Omit < PairItemRequest , 'headers' | 'query' | 'requiresState' > ;
6277
6378// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -86,15 +101,23 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
86101 const [ currentPair , setCurrentPair ] = useState < SimulationItem | undefined > ( initialPairState ) ;
87102 const [ headerQuery , setHeaderQuery ] = useState < SimulationHeadersQueryState > ( initialHeaderQuery ) ;
88103 const [ body , setBody ] = useState < SimulationHtmlState > ( initialBodyState ) ;
104+ const [ fieldsError , setFieldsError ] = useState < SimulationFieldsErrorState > ( initialFieldsError ) ;
89105
90106 const method = currentPair ?. request . method ?. [ 0 ] . value || 'GET' ;
91107 const { transitionsState, requiresState } = serverState ;
92108
109+ const isAnyFieldError : boolean = useMemo ( ( ) => ! ! Object . values ( fieldsError ) . find ( error => ! ! error ) , [ fieldsError ] ) ;
110+ const isDisabledButton : boolean =
111+ ! currentPair ?. request ?. path ?. [ 0 ] ?. value || ! currentPair ?. response ?. status || isAnyFieldError ;
112+
93113 function handleSubmit ( ) {
94114 if ( state && simulationStore . type !== 'pending' ) {
95115 const pair = currentPair
96116 ? mergeServerStateToCurrentPair (
97- mergeHeaderStateToCurrentPair ( mergeBodyStateToCurrentPair ( currentPair , body ) , headerQuery ) ,
117+ mergeHeaderStateToCurrentPair (
118+ mergeBodyStateToCurrentPair ( mergeMatcherValueToCurrentPair ( currentPair ) , body ) ,
119+ headerQuery ,
120+ ) ,
98121 serverState ,
99122 )
100123 : null ;
@@ -153,9 +176,27 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
153176 return ( e : InputChange ) => setCurrentPair ( prev => changeCurrentPairRequest ( prev , name , key , e . target . value ) ) ;
154177 }
155178
156- function handleChooseCurrentResponse ( name : keyof PairItemResponse ) {
157- return ( _e : React . MouseEvent < HTMLDivElement > , dataItem ?: ISelectItem < number > ) => {
158- setCurrentPair ( prev => changeCurrentPairResponse ( prev , name , dataItem ?. value || 0 ) ) ;
179+ function handleChangeCurrentResponse ( name : keyof PairItemResponse , isValueNumber ?: boolean ) {
180+ return ( e : InputChange ) => {
181+ const currentValue : string | number = isValueNumber
182+ ? convertStringToInteger ( e . target . value ) || ''
183+ : e . target . value ;
184+
185+ setCurrentPair ( prev => changeCurrentPairResponse ( prev , name , currentValue ) ) ;
186+ } ;
187+ }
188+
189+ function handleBlurCurrentResponse ( errorName : keyof SimulationFieldsErrorState ) {
190+ return ( e : InputFocus ) => {
191+ const isFieldError : boolean = isFieldsError ( errorName , e . target . value ) ;
192+
193+ if ( isFieldError ) {
194+ setFieldsError ( prev => ( { ...prev , [ errorName ] : FIELDS_ERROR [ errorName ] } ) ) ;
195+
196+ return ;
197+ }
198+
199+ setFieldsError ( prev => ( { ...prev , [ errorName ] : '' } ) ) ;
159200 } ;
160201 }
161202
@@ -223,25 +264,37 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
223264 ) ;
224265
225266 const renderField = useCallback (
226- ( onChangeField : ( e : InputChange ) => void , value : string , placeholder ?: string ) => (
267+ (
268+ onChangeField : ( e : InputChange ) => void ,
269+ value : string ,
270+ { placeholder, name, inputMask, inputMode, onBlurField } : InputPropsType ,
271+ ) => (
227272 < TextField
228273 className = { cn ( 'field' ) }
229274 classes = { { input : cn ( 'input' ) } }
230275 placeholder = { placeholder }
231276 value = { value || '' }
277+ mask = { inputMask }
278+ inputMode = { inputMode }
232279 onChange = { onChangeField }
280+ onBlur = { onBlurField }
281+ { ...getVerificationFields ( fieldsError [ name || '' ] ) }
233282 />
234283 ) ,
235- [ ] ,
284+ [ fieldsError ] ,
236285 ) ;
237286
238287 const renderFieldList = ( list : ServerState [ ] , name : keyof SimulationsServerState ) => (
239288 < div className = { cn ( 'field-list' ) } >
240289 { list . map ( ( values , index ) => (
241290 // eslint-disable-next-line react/no-array-index-key
242291 < div className = { cn ( 'fields' ) } key = { index } >
243- { renderField ( handleChangeServerState ( name , 'key' , index ) , values . key || '' , 'State key' ) }
244- { renderField ( handleChangeServerState ( name , 'value' , index ) , values . value || '' , 'State value' ) }
292+ { renderField ( handleChangeServerState ( name , 'key' , index ) , values . key || '' , {
293+ placeholder : 'State key' ,
294+ } ) }
295+ { renderField ( handleChangeServerState ( name , 'value' , index ) , values . value || '' , {
296+ placeholder : 'State value' ,
297+ } ) }
245298 { renderDeleteButton ( handleToggleServerState ( name , index ) ) }
246299 </ div >
247300 ) ) }
@@ -262,12 +315,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
262315 </ Header > ,
263316 ) }
264317 < div className = { cn ( 'action-button' ) } >
265- < Button
266- sizeAll = "medium"
267- fullWidth
268- disabled = { ! currentPair ?. request ?. path ?. [ 0 ] ?. value }
269- onClick = { handleSubmit }
270- >
318+ < Button sizeAll = "medium" fullWidth disabled = { isDisabledButton } onClick = { handleSubmit } >
271319 { routeIndex === undefined ? 'Create' : 'Update' }
272320 </ Button >
273321 { routeIndex !== undefined && (
@@ -327,7 +375,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
327375 { renderField (
328376 handleChangeCurrentRequest ( 'destination' , 'value' ) ,
329377 currentPair ?. request . destination ?. [ 0 ] . value || '' ,
330- 'localhost:8080' ,
378+ { placeholder : 'localhost:8080' } ,
331379 ) }
332380 </ div >
333381 </ SimulationFieldsBlock >
@@ -342,7 +390,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
342390 { renderField (
343391 handleChangeCurrentRequest ( 'path' , 'value' ) ,
344392 currentPair ?. request . path ?. [ 0 ] . value || '' ,
345- 'api/v1/match' ,
393+ { placeholder : 'api/v1/match' } ,
346394 ) }
347395 </ div >
348396 </ SimulationFieldsBlock >
@@ -359,7 +407,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
359407 { renderField (
360408 handleChangeHeaderQuery ( 'query' , 'key' , index ) ,
361409 header . key ,
362- 'Query key' ,
410+ { placeholder : 'Query key' } ,
363411 ) }
364412 < Select
365413 classes = { { control : cn ( 'select-control' ) } }
@@ -370,7 +418,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
370418 { renderField (
371419 handleChangeHeaderQuery ( 'query' , 'value' , index ) ,
372420 header . value ,
373- 'Query keys(s)' ,
421+ { placeholder : 'Query keys(s)' } ,
374422 ) }
375423 { renderDeleteButton ( handleToggleHeaderQuery ( 'query' , index ) ) }
376424 </ div >
@@ -391,7 +439,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
391439 { renderField (
392440 handleChangeHeaderQuery ( 'request' , 'key' , index ) ,
393441 header . key ,
394- 'Header key' ,
442+ { placeholder : 'Header key' } ,
395443 ) }
396444 < Select
397445 classes = { { control : cn ( 'select-contol' ) } }
@@ -402,7 +450,7 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
402450 { renderField (
403451 handleChangeHeaderQuery ( 'request' , 'value' , index ) ,
404452 header . value ,
405- 'Header keys(s)' ,
453+ { placeholder : 'Header keys(s)' } ,
406454 ) }
407455 { renderDeleteButton ( handleToggleHeaderQuery ( 'request' , index ) ) }
408456 </ div >
@@ -440,12 +488,17 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
440488 < div className = { cn ( 'collapse-content' ) } >
441489 < SimulationFieldsBlock title = "Status code" >
442490 < div className = { cn ( 'fields' ) } >
443- < Select
444- classes = { { control : cn ( 'select-contol' ) } }
445- items = { STATUS_CODES }
446- currentValue = { currentPair ?. response . status || STATUS_CODES [ 0 ] . value }
447- onSelect = { handleChooseCurrentResponse ( 'status' ) }
448- />
491+ { renderField (
492+ handleChangeCurrentResponse ( 'status' , true ) ,
493+ `${ currentPair ?. response . status } ` ,
494+ {
495+ placeholder : '200' ,
496+ name : 'statusCode' ,
497+ inputMask : '999' ,
498+ inputMode : 'numeric' ,
499+ onBlurField : handleBlurCurrentResponse ( 'statusCode' ) ,
500+ } ,
501+ ) }
449502 </ div >
450503 </ SimulationFieldsBlock >
451504 < SimulationFieldsBlock
@@ -461,12 +514,12 @@ const Simulation: React.FC<ISimulationProps> = ({ onBack, onChange, onDelete })
461514 { renderField (
462515 handleChangeHeaderQuery ( 'response' , 'key' , index ) ,
463516 header . key ,
464- 'Header key' ,
517+ { placeholder : 'Header key' } ,
465518 ) }
466519 { renderField (
467520 handleChangeHeaderQuery ( 'response' , 'value' , index ) ,
468521 header . value ,
469- 'Header value(s)' ,
522+ { placeholder : 'Header value(s)' } ,
470523 ) }
471524 { renderDeleteButton ( handleToggleHeaderQuery ( 'response' , index ) ) }
472525 </ div >
0 commit comments