diff --git a/client/src/client-only-routes/show-user.tsx b/client/src/client-only-routes/show-user.tsx index 6a67c2fb163..a9f7c02234f 100644 --- a/client/src/client-only-routes/show-user.tsx +++ b/client/src/client-only-routes/show-user.tsx @@ -1,19 +1,20 @@ -import { - FormControl, - FormGroup, - ControlLabel, - Button -} from '@freecodecamp/react-bootstrap'; +import { Button } from '@freecodecamp/react-bootstrap'; import React, { useState } from 'react'; import Helmet from 'react-helmet'; import type { TFunction } from 'i18next'; import { Trans, withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { Col, Row, Panel } from '@freecodecamp/ui'; +import { + FormGroup, + FormControl, + ControlLabel, + Panel, + Col, + Row +} from '@freecodecamp/ui'; import Login from '../components/Header/components/login'; - import { Spacer, Loader, FullWidthRow } from '../components/helpers'; import { reportUser } from '../redux/actions'; import { @@ -123,6 +124,7 @@ function ShowUser({ {t('report.what')} { ); }; - handleSubmit = (e: React.FormEvent) => { + handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const { formValues } = this.state; const { submitNewAbout } = this.props; @@ -127,7 +127,7 @@ class AboutSettings extends Component { } }; - handleNameChange = (e: React.FormEvent) => { + handleNameChange = (e: React.ChangeEvent) => { const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { @@ -137,7 +137,7 @@ class AboutSettings extends Component { })); }; - handleLocationChange = (e: React.FormEvent) => { + handleLocationChange = (e: React.ChangeEvent) => { const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { @@ -163,7 +163,7 @@ class AboutSettings extends Component { isPictureUrlValid: state.formValues.picture === '' })); - handlePictureChange = (e: React.FormEvent) => { + handlePictureChange = (e: React.ChangeEvent) => { const value = (e.target as HTMLInputElement).value.slice(0); if (isURL(value, { require_protocol: true })) { this.validationImage.src = encodeURI(value); @@ -180,7 +180,7 @@ class AboutSettings extends Component { })); }; - handleAboutChange = (e: React.FormEvent) => { + handleAboutChange = (e: React.ChangeEvent) => { const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { diff --git a/client/src/components/settings/email.tsx b/client/src/components/settings/email.tsx index 5be10377326..04e120ee46e 100644 --- a/client/src/components/settings/email.tsx +++ b/client/src/components/settings/email.tsx @@ -1,11 +1,12 @@ +import { Button } from '@freecodecamp/react-bootstrap'; import { HelpBlock, + Alert, FormGroup, - ControlLabel, + FormGroupProps, FormControl, - Button -} from '@freecodecamp/react-bootstrap'; -import { Alert } from '@freecodecamp/ui'; + ControlLabel +} from '@freecodecamp/ui'; import { Link } from 'gatsby'; import React, { useState } from 'react'; import type { TFunction } from 'i18next'; @@ -44,6 +45,11 @@ interface EmailForm { isPristine: boolean; } +interface EmailValidation { + state: FormGroupProps['validationState']; + message: string; +} + function EmailSettings({ email, isEmailVerified, @@ -78,7 +84,7 @@ function EmailSettings({ }; } - function getValidationForNewEmail() { + function getValidationForNewEmail(): EmailValidation { const { newEmail, currentEmail } = emailForm; if (!maybeEmailRE.test(newEmail)) { return { @@ -102,7 +108,7 @@ function EmailSettings({ } } - function getValidationForConfirmEmail() { + function getValidationForConfirmEmail(): EmailValidation { const { confirmNewEmail, newEmail } = emailForm; if (!maybeEmailRE.test(newEmail)) { return { @@ -181,6 +187,7 @@ function EmailSettings({
e.preventDefault() })} @@ -198,13 +205,16 @@ function EmailSettings({ > {t('settings.email.new')} {newEmailValidationMessage ? ( - {newEmailValidationMessage} + + {newEmailValidationMessage} + ) : null} {t('settings.email.confirm')} {confirmEmailValidationMessage ? ( - {confirmEmailValidationMessage} + + {confirmEmailValidationMessage} + ) : null} diff --git a/client/src/components/settings/internet.tsx b/client/src/components/settings/internet.tsx index cfb970f90a9..998ef1edcf4 100644 --- a/client/src/components/settings/internet.tsx +++ b/client/src/components/settings/internet.tsx @@ -1,15 +1,16 @@ import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - HelpBlock, - FormControl, - FormGroup, - ControlLabel -} from '@freecodecamp/react-bootstrap'; import React, { Component } from 'react'; import type { TFunction } from 'i18next'; import { withTranslation } from 'react-i18next'; import isURL from 'validator/lib/isURL'; +import { + FormControl, + FormGroup, + ControlLabel, + HelpBlock, + type FormGroupProps +} from '@freecodecamp/ui'; import { maybeUrlRE } from '../../utils'; @@ -34,6 +35,11 @@ type InternetState = { originalValues: Socials; }; +interface URLValidation { + state: FormGroupProps['validationState']; + message: string; +} + function Info({ message }: { message: string }) { return message ? {message} : null; } @@ -78,7 +84,7 @@ class InternetSettings extends Component { return null; } - getValidationStateFor(maybeURl = '') { + getValidationStateFor(maybeURl = ''): URLValidation { const { t } = this.props; if (!maybeURl || !maybeUrlRE.test(maybeURl)) { return { @@ -152,7 +158,7 @@ class InternetSettings extends Component { return null; }; - renderCheck = (url: string, validation: string | null) => + renderCheck = (url: string, validation: FormGroupProps['validationState']) => url && validation === 'success' ? ( diff --git a/client/src/components/settings/portfolio.tsx b/client/src/components/settings/portfolio.tsx index 41a7f0253b0..de3fb41ccaf 100644 --- a/client/src/components/settings/portfolio.tsx +++ b/client/src/components/settings/portfolio.tsx @@ -1,14 +1,15 @@ -import { - Button, - FormGroup, - ControlLabel, - FormControl, - HelpBlock -} from '@freecodecamp/react-bootstrap'; +import { Button } from '@freecodecamp/react-bootstrap'; import { findIndex, find, isEqual } from 'lodash-es'; import { nanoid } from 'nanoid'; import React, { Component } from 'react'; import type { TFunction } from 'i18next'; +import { + FormGroup, + FormControl, + ControlLabel, + HelpBlock, + FormGroupProps +} from '@freecodecamp/ui'; import { withTranslation } from 'react-i18next'; import isURL from 'validator/lib/isURL'; import { PortfolioProjectData } from '../../redux/prop-types'; @@ -32,6 +33,11 @@ type PortfolioState = { unsavedItemId: string | null; }; +interface ProfileValidation { + state: FormGroupProps['validationState']; + message: string; +} + function createEmptyPortfolioItem(): PortfolioProjectData { return { id: nanoid(), @@ -61,9 +67,9 @@ class PortfolioSettings extends Component { createOnChangeHandler = (id: string, key: 'description' | 'image' | 'title' | 'url') => - (e: React.FormEvent) => { + (e: React.ChangeEvent) => { e.preventDefault(); - const userInput = (e.target as HTMLInputElement).value.slice(); + const userInput = e.target.value.slice(); return this.setState(state => { const { portfolio: currentPortfolio } = state; const mutablePortfolio = currentPortfolio.slice(0); @@ -114,7 +120,7 @@ class PortfolioSettings extends Component { return isEqual(original, edited); }; - getDescriptionValidation(description: string) { + getDescriptionValidation(description: string): ProfileValidation { const { t } = this.props; const len = description.length; const charsLeft = 288 - len; @@ -136,10 +142,13 @@ class PortfolioSettings extends Component { return { state: 'success', message: '' }; } - getTitleValidation(title: string) { + getTitleValidation(title: string): ProfileValidation { const { t } = this.props; if (!title) { - return { state: 'error', message: t('validation.title-required') }; + return { + state: 'error', + message: t('validation.title-required') + }; } const len = title.length; if (len < 2) { @@ -155,7 +164,10 @@ class PortfolioSettings extends Component { const { t } = this.props; const len = maybeUrl.length; if (len >= 4 && !hasProtocolRE.test(maybeUrl)) { - return { state: 'error', message: t('validation.invalid-protocol') }; + return { + state: 'error', + message: t('validation.invalid-protocol') + }; } if (isImage && !maybeUrl) { return { state: null, message: '' }; @@ -242,7 +254,6 @@ class PortfolioSettings extends Component { if (isButtonDisabled) return null; return this.updateItem(id); }; - return ( handleSubmit(e, id)} id='portfolio-items'> @@ -258,8 +269,11 @@ class PortfolioSettings extends Component { required={true} type='text' value={title} + data-cy='portfolio-title' /> - {titleMessage ? {titleMessage} : null} + {titleMessage ? ( + {titleMessage} + ) : null} { required={true} type='url' value={url} + data-cy='portfolio-url' /> - {urlMessage ? {urlMessage} : null} + {urlMessage ? ( + {urlMessage} + ) : null} { onChange={this.createOnChangeHandler(id, 'image')} type='url' value={image} + data-cy='portfolio-image' /> - {imageMessage ? {imageMessage} : null} + {imageMessage ? ( + {imageMessage} + ) : null} { componentClass='textarea' onChange={this.createOnChangeHandler(id, 'description')} value={description} + data-cy='portfolio-description' /> {descriptionMessage ? ( - {descriptionMessage} + + {descriptionMessage} + ) : null} {

{t('settings.share-projects')}