From de888d640c27b996b3969d40d0691e254ab4d634 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 25 Jun 2021 20:01:11 +0400 Subject: [PATCH] feat(client): ts-migrate Settings component (#42341) * feat(client): change file format to Typescript * feat(client): migrate SectionHeader component * feat(client): migrate ToggleSetting component * feat(client): migrate Theme component * feat(client): migrate ResetModal component * feat(client): migrate DeleteModal component * feat(client): migrate DangerZone component * feat(client): migrate Honesty component * feat(client): migrate Privacy component * feat(client): migrate Username component * feat(client): migrate About component * feat(client): migrate Email component * feat(client): migrate Internet component * feat(client): migrate Portfolio component * feat(client): add required types packages * feat(client): undo file format change * feat(client): rename files to kebab-case * feat(client): rename temp files to kebab-case * feat(client): Review ts-migration * Fix imports * Fix some types * Remove unncessary comments * Consistent comment format * restore mistakenly deleted file --- .../src/client-only-routes/show-settings.tsx | 14 +- .../src/components/settings/Certification.js | 2 +- .../settings/{About.js => about.tsx} | 72 ++++++---- .../{DangerZone.js => danger-zone.tsx} | 29 ++-- .../{DeleteModal.js => delete-modal.tsx} | 16 +-- .../settings/{Email.js => email.tsx} | 67 +++++---- .../settings/{Honesty.js => honesty.tsx} | 14 +- .../settings/{Internet.js => internet.tsx} | 86 +++++++----- .../settings/{Portfolio.js => portfolio.tsx} | 128 ++++++++++-------- .../settings/{Privacy.js => privacy.tsx} | 61 +++++---- .../{ResetModal.js => reset-modal.tsx} | 14 +- .../{SectionHeader.js => section-header.tsx} | 12 +- .../settings/{Theme.js => theme.tsx} | 22 +-- .../{ToggleSetting.js => toggle-setting.tsx} | 20 +-- .../settings/{Username.js => username.tsx} | 69 +++++++--- client/src/declarations.d.ts | 3 + 16 files changed, 367 insertions(+), 262 deletions(-) rename client/src/components/settings/{About.js => about.tsx} (77%) rename client/src/components/settings/{DangerZone.js => danger-zone.tsx} (81%) rename client/src/components/settings/{DeleteModal.js => delete-modal.tsx} (85%) rename client/src/components/settings/{Email.js => email.tsx} (82%) rename client/src/components/settings/{Honesty.js => honesty.tsx} (78%) rename client/src/components/settings/{Internet.js => internet.tsx} (78%) rename client/src/components/settings/{Portfolio.js => portfolio.tsx} (75%) rename client/src/components/settings/{Privacy.js => privacy.tsx} (83%) rename client/src/components/settings/{ResetModal.js => reset-modal.tsx} (87%) rename client/src/components/settings/{SectionHeader.js => section-header.tsx} (54%) rename client/src/components/settings/{Theme.js => theme.tsx} (57%) rename client/src/components/settings/{ToggleSetting.js => toggle-setting.tsx} (77%) rename client/src/components/settings/{Username.js => username.tsx} (77%) diff --git a/client/src/client-only-routes/show-settings.tsx b/client/src/client-only-routes/show-settings.tsx index 1c7dc89ed7..3831ea954b 100644 --- a/client/src/client-only-routes/show-settings.tsx +++ b/client/src/client-only-routes/show-settings.tsx @@ -16,15 +16,15 @@ import { createFlashMessage } from '../components/Flash/redux'; import { useTranslation } from 'react-i18next'; import { Loader, Spacer } from '../components/helpers'; -import About from '../components/settings/About'; -import Privacy from '../components/settings/Privacy'; -import Email from '../components/settings/Email'; -import Internet from '../components/settings/Internet'; -import Portfolio from '../components/settings/Portfolio'; -import Honesty from '../components/settings/Honesty'; +import About from '../components/settings/about'; +import Privacy from '../components/settings/privacy'; +import Email from '../components/settings/email'; +import Internet from '../components/settings/internet'; +import Portfolio from '../components/settings/portfolio'; +import Honesty from '../components/settings/honesty'; import Certification from '../components/settings/Certification'; -import DangerZone from '../components/settings/DangerZone'; import { UserType } from '../redux/prop-types'; +import DangerZone from '../components/settings/danger-zone'; const { apiLocation } = envData as Record; diff --git a/client/src/components/settings/Certification.js b/client/src/components/settings/Certification.js index 5c6d2cd3b4..b6ee84c6b7 100644 --- a/client/src/components/settings/Certification.js +++ b/client/src/components/settings/Certification.js @@ -16,7 +16,7 @@ import { legacyProjectMap } from '../../resources/certAndProjectMap'; -import SectionHeader from './SectionHeader'; +import SectionHeader from './section-header'; import ProjectModal from '../SolutionViewer/ProjectModal'; import { FullWidthRow, Spacer } from '../helpers'; diff --git a/client/src/components/settings/About.js b/client/src/components/settings/about.tsx similarity index 77% rename from client/src/components/settings/About.js rename to client/src/components/settings/about.tsx index 189221cc4a..64901ddd5c 100644 --- a/client/src/components/settings/About.js +++ b/client/src/components/settings/about.tsx @@ -1,34 +1,51 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { FormGroup, ControlLabel, FormControl, HelpBlock, Alert + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore } from '@freecodecamp/react-bootstrap'; import { FullWidthRow, Spacer } from '../helpers'; -import ThemeSettings from './Theme'; -import UsernameSettings from './Username'; +import ThemeSettings from './theme'; +import UsernameSettings from './username'; import BlockSaveButton from '../helpers/form/block-save-button'; import { withTranslation } from 'react-i18next'; -const propTypes = { - about: PropTypes.string, - currentTheme: PropTypes.string, - location: PropTypes.string, - name: PropTypes.string, - picture: PropTypes.string, - points: PropTypes.number, - submitNewAbout: PropTypes.func.isRequired, - t: PropTypes.func.isRequired, - toggleNightMode: PropTypes.func.isRequired, - username: PropTypes.string +type FormValues = { + name: string; + location: string; + picture: string; + about: string; }; -class AboutSettings extends Component { - constructor(props) { +type AboutProps = { + about: string; + currentTheme: string; + location: string; + name: string; + picture: string; + points: number; + submitNewAbout: (formValues: FormValues) => void; + t: (str: string) => string; + toggleNightMode: () => void; + username: string; +}; + +type AboutState = { + formValues: FormValues; + originalValues: FormValues; + formClicked: boolean; + isPictureUrlValid: boolean; +}; + +class AboutSettings extends Component { + validationImage: HTMLImageElement; + static displayName: string; + constructor(props: AboutProps) { super(props); this.validationImage = new Image(); const { name = '', location = '', picture = '', about = '' } = props; @@ -56,7 +73,7 @@ class AboutSettings extends Component { picture === formValues.picture && about === formValues.about ) { - /* eslint-disable-next-line react/no-did-update-set-state */ + // eslint-disable-next-line react/no-did-update-set-state return this.setState({ originalValues: { name, @@ -74,13 +91,13 @@ class AboutSettings extends Component { const { formValues, originalValues } = this.state; return ( this.state.isPictureUrlValid === false || - Object.keys(originalValues) + (Object.keys(originalValues) as Array) .map(key => originalValues[key] === formValues[key]) .every(bool => bool) ); }; - handleSubmit = e => { + handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const { formValues } = this.state; const { submitNewAbout } = this.props; @@ -93,8 +110,8 @@ class AboutSettings extends Component { } }; - handleNameChange = e => { - const value = e.target.value.slice(0); + handleNameChange = (e: React.FormEvent) => { + const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { ...state.formValues, @@ -103,8 +120,8 @@ class AboutSettings extends Component { })); }; - handleLocationChange = e => { - const value = e.target.value.slice(0); + handleLocationChange = (e: React.FormEvent) => { + const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { ...state.formValues, @@ -126,8 +143,8 @@ class AboutSettings extends Component { loadEvent = () => this.setState({ isPictureUrlValid: true }); errorEvent = () => this.setState({ isPictureUrlValid: false }); - handlePictureChange = e => { - const value = e.target.value.slice(0); + handlePictureChange = (e: React.FormEvent) => { + const value = (e.target as HTMLInputElement).value.slice(0); this.validationImage.src = value; return this.setState(state => ({ formValues: { @@ -150,8 +167,8 @@ class AboutSettings extends Component { } }; - handleAboutChange = e => { - const value = e.target.value.slice(0); + handleAboutChange = (e: React.FormEvent) => { + const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ formValues: { ...state.formValues, @@ -229,6 +246,5 @@ class AboutSettings extends Component { } AboutSettings.displayName = 'AboutSettings'; -AboutSettings.propTypes = propTypes; export default withTranslation()(AboutSettings); diff --git a/client/src/components/settings/DangerZone.js b/client/src/components/settings/danger-zone.tsx similarity index 81% rename from client/src/components/settings/DangerZone.js rename to client/src/components/settings/danger-zone.tsx index 4c071b7e82..57da84187d 100644 --- a/client/src/components/settings/DangerZone.js +++ b/client/src/components/settings/danger-zone.tsx @@ -1,25 +1,32 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import { Button, Panel } from '@freecodecamp/react-bootstrap'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { withTranslation } from 'react-i18next'; +import type { Dispatch } from 'redux'; import { FullWidthRow, ButtonSpacer, Spacer } from '../helpers'; import { deleteAccount, resetProgress } from '../../redux/settings'; -import DeleteModal from './DeleteModal'; -import ResetModal from './ResetModal'; +import DeleteModal from './delete-modal'; +import ResetModal from './reset-modal'; import './danger-zone.css'; -const propTypes = { - deleteAccount: PropTypes.func.isRequired, - resetProgress: PropTypes.func.isRequired, - t: PropTypes.func.isRequired +type DangerZoneProps = { + deleteAccount: () => void; + resetProgress: () => void; + t: (str: string) => JSX.Element; +}; + +type DangerZoneState = { + reset: boolean; + delete: boolean; }; const mapStateToProps = () => ({}); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { deleteAccount, @@ -28,8 +35,9 @@ const mapDispatchToProps = dispatch => dispatch ); -class DangerZone extends Component { - constructor(props) { +class DangerZone extends Component { + static displayName: string; + constructor(props: DangerZoneProps) { super(props); this.state = { reset: false, @@ -103,7 +111,6 @@ class DangerZone extends Component { } DangerZone.displayName = 'DangerZone'; -DangerZone.propTypes = propTypes; export default connect( mapStateToProps, diff --git a/client/src/components/settings/DeleteModal.js b/client/src/components/settings/delete-modal.tsx similarity index 85% rename from client/src/components/settings/DeleteModal.js rename to client/src/components/settings/delete-modal.tsx index 160a2c5db1..6e68a2eba6 100644 --- a/client/src/components/settings/DeleteModal.js +++ b/client/src/components/settings/delete-modal.tsx @@ -1,19 +1,20 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Trans, useTranslation } from 'react-i18next'; import { ButtonSpacer } from '../helpers'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import { Button, Modal } from '@freecodecamp/react-bootstrap'; import './danger-zone.css'; -const propTypes = { - delete: PropTypes.func.isRequired, - onHide: PropTypes.func.isRequired, - show: PropTypes.bool +type DeleteModalProps = { + delete: () => void; + onHide: () => void; + show: boolean; }; -function DeleteModal(props) { +function DeleteModal(props: DeleteModalProps): JSX.Element { const { show, onHide } = props; const email = 'team@freecodecamp.org'; const { t } = useTranslation(); @@ -36,7 +37,7 @@ function DeleteModal(props) {

{t('settings.danger.delete-p1')}

{t('settings.danger.delete-p2')}

- + {{ email }} @@ -73,6 +74,5 @@ function DeleteModal(props) { } DeleteModal.displayName = 'DeleteModal'; -DeleteModal.propTypes = propTypes; export default DeleteModal; diff --git a/client/src/components/settings/Email.js b/client/src/components/settings/email.tsx similarity index 82% rename from client/src/components/settings/Email.js rename to client/src/components/settings/email.tsx index 9f69eddebd..eb6a34a74f 100644 --- a/client/src/components/settings/Email.js +++ b/client/src/components/settings/email.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Link } from 'gatsby'; @@ -10,33 +9,45 @@ import { ControlLabel, FormControl, Button + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore } from '@freecodecamp/react-bootstrap'; import isEmail from 'validator/lib/isEmail'; import { Trans, withTranslation } from 'react-i18next'; +import type { Dispatch } from 'redux'; import { updateMyEmail } from '../../redux/settings'; import { maybeEmailRE } from '../../utils'; import FullWidthRow from '../helpers/full-width-row'; import Spacer from '../helpers/spacer'; -import SectionHeader from './SectionHeader'; +import SectionHeader from './section-header'; import BlockSaveButton from '../helpers/form/block-save-button'; -import ToggleSetting from './ToggleSetting'; +import ToggleSetting from './toggle-setting'; const mapStateToProps = () => ({}); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({ updateMyEmail }, dispatch); -const propTypes = { - email: PropTypes.string, - isEmailVerified: PropTypes.bool, - sendQuincyEmail: PropTypes.bool, - t: PropTypes.func.isRequired, - updateMyEmail: PropTypes.func.isRequired, - updateQuincyEmail: PropTypes.func.isRequired +type EmailProps = { + email: string; + isEmailVerified: boolean; + sendQuincyEmail: boolean; + t: (str: string) => string; + updateMyEmail: (email: string) => void; + updateQuincyEmail: (sendQuincyEmail: boolean) => void; }; -export function UpdateEmailButton() { +type EmailState = { + emailForm: { + currentEmail: string; + newEmail: string; + confirmNewEmail: string; + isPristine: boolean; + }; +}; + +export function UpdateEmailButton(this: EmailSettings): JSX.Element { const { t } = this.props; return ( @@ -47,8 +58,9 @@ export function UpdateEmailButton() { ); } -class EmailSettings extends Component { - constructor(props) { +class EmailSettings extends Component { + static displayName: string; + constructor(props: EmailProps) { super(props); this.state = { @@ -61,7 +73,7 @@ class EmailSettings extends Component { }; } - handleSubmit = e => { + handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const { emailForm: { newEmail } @@ -70,17 +82,19 @@ class EmailSettings extends Component { return updateMyEmail(newEmail); }; - createHandleEmailFormChange = key => e => { - e.preventDefault(); - const userInput = e.target.value.slice(); - return this.setState(state => ({ - emailForm: { - ...state.emailForm, - [key]: userInput, - isPristine: userInput === state.emailForm.currentEmail - } - })); - }; + createHandleEmailFormChange = + (key: 'newEmail' | 'confirmNewEmail') => + (e: React.FormEvent) => { + e.preventDefault(); + const userInput = (e.target as HTMLInputElement).value.slice(); + return this.setState(state => ({ + emailForm: { + ...state.emailForm, + [key]: userInput, + isPristine: userInput === state.emailForm.currentEmail + } + })); + }; getValidationForNewEmail = () => { const { @@ -242,7 +256,6 @@ class EmailSettings extends Component { } EmailSettings.displayName = 'EmailSettings'; -EmailSettings.propTypes = propTypes; export default connect( mapStateToProps, diff --git a/client/src/components/settings/Honesty.js b/client/src/components/settings/honesty.tsx similarity index 78% rename from client/src/components/settings/Honesty.js rename to client/src/components/settings/honesty.tsx index 90ddd2c33f..25f73c157d 100644 --- a/client/src/components/settings/Honesty.js +++ b/client/src/components/settings/honesty.tsx @@ -1,20 +1,21 @@ import React from 'react'; -import PropTypes from 'prop-types'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import { Button, Panel } from '@freecodecamp/react-bootstrap'; import { useTranslation } from 'react-i18next'; import { FullWidthRow } from '../helpers'; -import SectionHeader from './SectionHeader'; +import SectionHeader from './section-header'; import HonestyPolicy from '../../resources/honesty-policy'; import './honesty.css'; -const propTypes = { - isHonest: PropTypes.bool, - updateIsHonest: PropTypes.func.isRequired +type HonestyProps = { + isHonest: boolean; + updateIsHonest: (obj: { isHonest: boolean }) => void; }; -const Honesty = ({ isHonest, updateIsHonest }) => { +const Honesty = ({ isHonest, updateIsHonest }: HonestyProps): JSX.Element => { const { t } = useTranslation(); const button = isHonest ? (