Skip to content

Commit 27e02f9

Browse files
authored
Merge pull request #81 from topcoder-platform/TCA-468_TCA-466_input-validation-error
TCA-468 TCA-466 - fix issues with input error message
2 parents cc24e4d + 4fef172 commit 27e02f9

File tree

4 files changed

+125
-39
lines changed

4 files changed

+125
-39
lines changed

client/src/assets/icons/warning.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
function Warning(
5+
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
6+
): JSX.Element {
7+
const { t } = useTranslation();
8+
9+
return (
10+
<svg
11+
width='10'
12+
height='9'
13+
viewBox='0 0 10 9'
14+
xmlns='http://www.w3.org/2000/svg'
15+
{...props}
16+
>
17+
<g>
18+
<title>{t('icons.fail')}</title>
19+
<path
20+
fillRule='evenodd'
21+
clipRule='evenodd'
22+
d={
23+
'M3.95423 0.859306C4.413 0.0437243 5.58725 0.0437239 6.04602 ' +
24+
'0.859306L9.3942 6.81163C9.84416 7.61155 9.2661 8.59994 8.34831 ' +
25+
'8.59994H1.65194C0.734151 8.59994 0.156094 7.61156 0.606052 ' +
26+
'6.81163L3.95423 0.859306ZM5.60007 6.80001C5.60007 7.13138 5.33144 ' +
27+
'7.40001 5.00007 7.40001C4.6687 7.40001 4.40007 7.13138 4.40007 ' +
28+
'6.80001C4.40007 6.46864 4.6687 6.20001 5.00007 6.20001C5.33144 ' +
29+
'6.20001 5.60007 6.46864 5.60007 6.80001ZM5.00007 2.00001C4.6687 ' +
30+
'2.00001 4.40007 2.26864 4.40007 2.60001V4.40001C4.40007 4.73138 ' +
31+
'4.6687 5.00001 5.00007 5.00001C5.33144 5.00001 5.60007 4.73138 ' +
32+
'5.60007 4.40001V2.60001C5.60007 2.26864 5.33144 2.00001 5.00007 ' +
33+
'2.00001Z'
34+
}
35+
fill='currentColor'
36+
/>
37+
</g>
38+
</svg>
39+
);
40+
}
41+
42+
Warning.displayName = 'Warning';
43+
44+
export default Warning;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.input-help-box {
2+
max-width: 320px;
3+
margin-top: -11px;
4+
}
5+
6+
.input-message {
7+
font-family: 'Roboto';
8+
font-style: normal;
9+
font-weight: 400;
10+
font-size: 12px;
11+
line-height: 14px;
12+
display: flex;
13+
align-items: center;
14+
}
15+
16+
.input-message > svg {
17+
margin-right: 7px;
18+
}
19+
20+
.input-message.is-error {
21+
color: var(--tc-red-100);
22+
}
23+
24+
.input-message.is-warn {
25+
color: var(--tc-legacy-120);
26+
}

client/src/components/formHelpers/form-fields.tsx

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import {
2-
Alert,
32
ControlLabel,
43
FormControl,
54
FormGroup,
65
HelpBlock
76
} from '@freecodecamp/react-bootstrap';
8-
import { kebabCase } from 'lodash-es';
7+
import { kebabCase, set } from 'lodash-es';
98
import normalizeUrl from 'normalize-url';
10-
import React from 'react';
9+
import React, { Fragment, useState } from 'react';
1110
import { Field } from 'react-final-form';
12-
import { useTranslation } from 'react-i18next';
11+
import Warning from '../../assets/icons/warning';
1312
import { FormOptions } from './form';
1413
import {
1514
editorValidator,
@@ -18,14 +17,14 @@ import {
1817
fCCValidator,
1918
httpValidator
2019
} from './form-validators';
20+
import './form-field.css';
2121

2222
type FormFieldsProps = {
2323
formFields: { name: string; label: string }[];
2424
options: FormOptions;
2525
};
2626

2727
function FormFields(props: FormFieldsProps): JSX.Element {
28-
const { t } = useTranslation();
2928
const { formFields, options = {} }: FormFieldsProps = props;
3029
const {
3130
ignored = [],
@@ -36,6 +35,10 @@ function FormFields(props: FormFieldsProps): JSX.Element {
3635
isLocalLinkAllowed = false
3736
} = options;
3837

38+
const [blured, setBlured] = useState<boolean[]>([]);
39+
const markAsBlured = (index: number) =>
40+
setBlured(prevState => set([...prevState], index, true));
41+
3942
const nullOrWarning = (
4043
value: string,
4144
error: unknown,
@@ -59,22 +62,29 @@ function FormFields(props: FormFieldsProps): JSX.Element {
5962
const message: string = (error ||
6063
validationError ||
6164
validationWarning) as string;
65+
66+
const hasError = error || validationError;
67+
const classNames = [
68+
'input-message',
69+
hasError && 'is-error',
70+
!hasError && 'is-warn'
71+
]
72+
.filter(Boolean)
73+
.join(' ');
6274
return message ? (
63-
<HelpBlock>
64-
<Alert
65-
bsStyle={error || validationError ? 'danger' : 'info'}
66-
closeLabel={t('buttons.close')}
67-
>
75+
<HelpBlock className='input-help-box'>
76+
<div className={classNames}>
77+
<Warning />
6878
{message}
69-
</Alert>
79+
</div>
7080
</HelpBlock>
7181
) : null;
7282
};
7383
return (
7484
<>
7585
{formFields
7686
.filter(formField => !ignored.includes(formField.name))
77-
.map(({ name, label }) => (
87+
.map(({ name, label }, i) => (
7888
// TODO: verify if the value is always a string
7989
<Field key={`${kebabCase(name)}-field`} name={name}>
8090
{({ input: { value, onChange }, meta: { pristine, error } }) => {
@@ -84,33 +94,37 @@ function FormFields(props: FormFieldsProps): JSX.Element {
8494
name in placeholders ? placeholders[name] : '';
8595
const isURL = types[name] === 'url';
8696
return (
87-
<FormGroup key={key} className='embedded'>
88-
{type === 'hidden' ? null : (
89-
<ControlLabel htmlFor={key}>
90-
{label}
91-
{required.includes(name) && (
92-
<span className='required-star'>*</span>
93-
)}
94-
</ControlLabel>
95-
)}
96-
<FormControl
97-
componentClass={type === 'textarea' ? type : 'input'}
98-
id={key}
99-
name={name}
100-
onChange={onChange}
101-
placeholder={placeholder}
102-
required={required.includes(name)}
103-
rows={4}
104-
type={type}
105-
value={value as string}
106-
/>
107-
{nullOrWarning(
108-
value as string,
109-
!pristine && error,
110-
isURL,
111-
name
112-
)}
113-
</FormGroup>
97+
<Fragment key={key}>
98+
<FormGroup className='embedded'>
99+
{type === 'hidden' ? null : (
100+
<ControlLabel htmlFor={key}>
101+
{label}
102+
{required.includes(name) && (
103+
<span className='required-star'>*</span>
104+
)}
105+
</ControlLabel>
106+
)}
107+
<FormControl
108+
componentClass={type === 'textarea' ? type : 'input'}
109+
id={key}
110+
name={name}
111+
onChange={onChange}
112+
placeholder={placeholder}
113+
required={required.includes(name)}
114+
rows={4}
115+
type={type}
116+
value={value as string}
117+
onBlur={() => markAsBlured(i)}
118+
/>
119+
</FormGroup>
120+
{blured[i] &&
121+
nullOrWarning(
122+
value as string,
123+
!pristine && error,
124+
isURL,
125+
name
126+
)}
127+
</Fragment>
114128
);
115129
}}
116130
</Field>

client/src/components/layouts/variables.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
--tc-blue-140: #16679a;
4646
--tc-blue-25: #bae1f9;
4747
--tc-blue-10: #eaf6fd;
48+
--tc-red-100: #ef476f;
49+
--tc-legacy-120: #f46500;
4850
}
4951

5052
.dark-palette {

0 commit comments

Comments
 (0)