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
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
5ad374cc1a
commit
de888d640c
@ -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<string, string>;
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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<AboutProps, AboutState> {
|
||||
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<keyof FormValues>)
|
||||
.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<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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);
|
@ -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<DangerZoneProps, DangerZoneState> {
|
||||
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,
|
@ -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) {
|
||||
<p>{t('settings.danger.delete-p1')}</p>
|
||||
<p>{t('settings.danger.delete-p2')}</p>
|
||||
<p>
|
||||
<Trans email={email} i18nKey='settings.danger.delete-p3'>
|
||||
<Trans i18nKey='settings.danger.delete-p3'>
|
||||
<a href={`mailto:${email}`} title={email}>
|
||||
{{ email }}
|
||||
</a>
|
||||
@ -73,6 +74,5 @@ function DeleteModal(props) {
|
||||
}
|
||||
|
||||
DeleteModal.displayName = 'DeleteModal';
|
||||
DeleteModal.propTypes = propTypes;
|
||||
|
||||
export default DeleteModal;
|
@ -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 (
|
||||
<Link style={{ textDecoration: 'none' }} to='/update-email'>
|
||||
@ -47,8 +58,9 @@ export function UpdateEmailButton() {
|
||||
);
|
||||
}
|
||||
|
||||
class EmailSettings extends Component {
|
||||
constructor(props) {
|
||||
class EmailSettings extends Component<EmailProps, EmailState> {
|
||||
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<HTMLInputElement>) => {
|
||||
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,
|
@ -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 ? (
|
||||
<Button
|
||||
@ -49,6 +50,5 @@ const Honesty = ({ isHonest, updateIsHonest }) => {
|
||||
};
|
||||
|
||||
Honesty.displayName = 'Honesty';
|
||||
Honesty.propTypes = propTypes;
|
||||
|
||||
export default Honesty;
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
@ -7,27 +6,38 @@ import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ControlLabel
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import isURL from 'validator/lib/isURL';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import { maybeUrlRE } from '../../utils';
|
||||
|
||||
import SectionHeader from './SectionHeader';
|
||||
import SectionHeader from './section-header';
|
||||
import { FullWidthRow } from '../helpers';
|
||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||
|
||||
const propTypes = {
|
||||
githubProfile: PropTypes.string,
|
||||
linkedin: PropTypes.string,
|
||||
t: PropTypes.func.isRequired,
|
||||
twitter: PropTypes.string,
|
||||
updateInternetSettings: PropTypes.func.isRequired,
|
||||
website: PropTypes.string
|
||||
interface InternetFormValues {
|
||||
githubProfile: string;
|
||||
linkedin: string;
|
||||
twitter: string;
|
||||
website: string;
|
||||
}
|
||||
|
||||
interface InternetProps extends InternetFormValues {
|
||||
t: (str: string) => string;
|
||||
updateInternetSettings: (formValues: InternetFormValues) => void;
|
||||
}
|
||||
|
||||
type InternetState = {
|
||||
formValues: InternetFormValues;
|
||||
originalValues: InternetFormValues;
|
||||
};
|
||||
|
||||
class InternetSettings extends Component {
|
||||
constructor(props) {
|
||||
class InternetSettings extends Component<InternetProps, InternetState> {
|
||||
static displayName: string;
|
||||
constructor(props: InternetProps) {
|
||||
super(props);
|
||||
const {
|
||||
githubProfile = '',
|
||||
@ -58,7 +68,7 @@ class InternetSettings extends Component {
|
||||
twitter !== originalValues.twitter ||
|
||||
website !== originalValues.website
|
||||
) {
|
||||
/* eslint-disable-next-line react/no-did-update-set-state */
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
return this.setState({
|
||||
originalValues: { githubProfile, linkedin, twitter, website }
|
||||
});
|
||||
@ -86,45 +96,50 @@ class InternetSettings extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
createHandleChange = key => e => {
|
||||
const value = e.target.value.slice(0);
|
||||
return this.setState(state => ({
|
||||
formValues: {
|
||||
...state.formValues,
|
||||
[key]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
createHandleChange =
|
||||
(key: keyof InternetFormValues) =>
|
||||
(e: React.FormEvent<HTMLInputElement>) => {
|
||||
const value = (e.target as HTMLInputElement).value.slice(0);
|
||||
return this.setState(state => ({
|
||||
formValues: {
|
||||
...state.formValues,
|
||||
[key]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
isFormPristine = () => {
|
||||
const { formValues, originalValues } = this.state;
|
||||
return Object.keys(originalValues)
|
||||
return (Object.keys(originalValues) as Array<keyof InternetFormValues>)
|
||||
.map(key => originalValues[key] === formValues[key])
|
||||
.every(bool => bool);
|
||||
};
|
||||
|
||||
isFormValid = () => {
|
||||
isFormValid = (): boolean => {
|
||||
const { formValues, originalValues } = this.state;
|
||||
const valueReducer = obj => {
|
||||
const valueReducer = (obj: InternetFormValues) => {
|
||||
return Object.values(obj).reduce(
|
||||
(acc, cur) => (acc ? acc : cur !== ''),
|
||||
(acc, cur): boolean => (acc ? acc : cur !== ''),
|
||||
false
|
||||
);
|
||||
) as boolean;
|
||||
};
|
||||
|
||||
let formHasValues = valueReducer(formValues);
|
||||
let OriginalHasValues = valueReducer(originalValues);
|
||||
const formHasValues = valueReducer(formValues);
|
||||
const OriginalHasValues = valueReducer(originalValues);
|
||||
|
||||
// check if user had values but wants to delete them all
|
||||
if (OriginalHasValues && !formHasValues) return true;
|
||||
|
||||
return Object.keys(formValues).reduce((bool, key) => {
|
||||
const maybeUrl = formValues[key];
|
||||
return maybeUrl ? isURL(maybeUrl) : bool;
|
||||
}, false);
|
||||
return (Object.keys(formValues) as Array<keyof InternetFormValues>).reduce(
|
||||
(bool: boolean, key: keyof InternetFormValues): boolean => {
|
||||
const maybeUrl = formValues[key];
|
||||
return maybeUrl ? isURL(maybeUrl) : bool;
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!this.isFormPristine() && this.isFormValid()) {
|
||||
// // Only submit the form if is has changed, and if it is valid
|
||||
@ -142,10 +157,10 @@ class InternetSettings extends Component {
|
||||
return null;
|
||||
};
|
||||
|
||||
renderHelpBlock = validationMessage =>
|
||||
renderHelpBlock = (validationMessage: string) =>
|
||||
validationMessage ? <HelpBlock>{validationMessage}</HelpBlock> : null;
|
||||
|
||||
renderCheck = (url, validation) =>
|
||||
renderCheck = (url: string, validation: string | null) =>
|
||||
url && validation === 'success' ? (
|
||||
<FormControl.Feedback>
|
||||
<span>
|
||||
@ -246,6 +261,5 @@ class InternetSettings extends Component {
|
||||
}
|
||||
|
||||
InternetSettings.displayName = 'InternetSettings';
|
||||
InternetSettings.propTypes = propTypes;
|
||||
|
||||
export default withTranslation()(InternetSettings);
|
@ -1,5 +1,4 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { nanoid } from 'nanoid';
|
||||
import {
|
||||
Button,
|
||||
@ -7,6 +6,8 @@ import {
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
HelpBlock
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { findIndex, find, isEqual } from 'lodash-es';
|
||||
import isURL from 'validator/lib/isURL';
|
||||
@ -15,22 +16,27 @@ import { withTranslation } from 'react-i18next';
|
||||
import { hasProtocolRE } from '../../utils';
|
||||
|
||||
import { FullWidthRow, ButtonSpacer, Spacer } from '../helpers';
|
||||
import SectionHeader from './SectionHeader';
|
||||
import SectionHeader from './section-header';
|
||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||
|
||||
const propTypes = {
|
||||
picture: PropTypes.string,
|
||||
portfolio: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
description: PropTypes.string,
|
||||
image: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string
|
||||
})
|
||||
),
|
||||
t: PropTypes.func.isRequired,
|
||||
updatePortfolio: PropTypes.func.isRequired,
|
||||
username: PropTypes.string
|
||||
type PortfolioValues = {
|
||||
id: string;
|
||||
description: string;
|
||||
image: string;
|
||||
title: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type PortfolioProps = {
|
||||
picture?: string;
|
||||
portfolio: PortfolioValues[];
|
||||
t: (str: string, obj?: { charsLeft: number }) => string;
|
||||
updatePortfolio: (obj: { portfolio: PortfolioValues[] }) => void;
|
||||
username?: string;
|
||||
};
|
||||
|
||||
type PortfolioState = {
|
||||
portfolio: PortfolioValues[];
|
||||
};
|
||||
|
||||
function createEmptyPortfolio() {
|
||||
@ -43,16 +49,18 @@ function createEmptyPortfolio() {
|
||||
};
|
||||
}
|
||||
|
||||
function createFindById(id) {
|
||||
return p => p.id === id;
|
||||
function createFindById(id: string) {
|
||||
return (p: PortfolioValues) => p.id === id;
|
||||
}
|
||||
|
||||
const mockEvent = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
preventDefault() {}
|
||||
};
|
||||
|
||||
class PortfolioSettings extends Component {
|
||||
constructor(props) {
|
||||
class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
|
||||
static displayName: string;
|
||||
constructor(props: PortfolioProps) {
|
||||
super(props);
|
||||
|
||||
const { portfolio = [] } = props;
|
||||
@ -62,24 +70,26 @@ class PortfolioSettings extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
createOnChangeHandler = (id, key) => e => {
|
||||
e.preventDefault();
|
||||
const userInput = e.target.value.slice();
|
||||
return this.setState(state => {
|
||||
const { portfolio: currentPortfolio } = state;
|
||||
const mutablePortfolio = currentPortfolio.slice(0);
|
||||
const index = findIndex(currentPortfolio, p => p.id === id);
|
||||
createOnChangeHandler =
|
||||
(id: string, key: 'description' | 'image' | 'title' | 'url') =>
|
||||
(e: React.FormEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
const userInput = (e.target as HTMLInputElement).value.slice();
|
||||
return this.setState(state => {
|
||||
const { portfolio: currentPortfolio } = state;
|
||||
const mutablePortfolio = currentPortfolio.slice(0);
|
||||
const index = findIndex(currentPortfolio, p => p.id === id);
|
||||
|
||||
mutablePortfolio[index] = {
|
||||
...mutablePortfolio[index],
|
||||
[key]: userInput
|
||||
};
|
||||
mutablePortfolio[index] = {
|
||||
...mutablePortfolio[index],
|
||||
[key]: userInput
|
||||
};
|
||||
|
||||
return { portfolio: mutablePortfolio };
|
||||
});
|
||||
};
|
||||
return { portfolio: mutablePortfolio };
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const { updatePortfolio } = this.props;
|
||||
const { portfolio } = this.state;
|
||||
@ -92,7 +102,7 @@ class PortfolioSettings extends Component {
|
||||
}));
|
||||
};
|
||||
|
||||
handleRemoveItem = id => {
|
||||
handleRemoveItem = (id: string) => {
|
||||
return this.setState(
|
||||
state => ({
|
||||
portfolio: state.portfolio.filter(p => p.id !== id)
|
||||
@ -101,7 +111,7 @@ class PortfolioSettings extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
isFormPristine = id => {
|
||||
isFormPristine = (id: string) => {
|
||||
const { portfolio } = this.state;
|
||||
const { portfolio: originalPortfolio } = this.props;
|
||||
const original = find(originalPortfolio, createFindById(id));
|
||||
@ -112,25 +122,26 @@ class PortfolioSettings extends Component {
|
||||
return isEqual(original, edited);
|
||||
};
|
||||
|
||||
isFormValid = id => {
|
||||
const { portfolio } = this.state;
|
||||
const toValidate = find(portfolio, createFindById(id));
|
||||
if (!toValidate) {
|
||||
return false;
|
||||
}
|
||||
const { title, url, image, description } = toValidate;
|
||||
// TODO: Check if this function is required or not
|
||||
// isFormValid = id => {
|
||||
// const { portfolio } = this.state;
|
||||
// const toValidate = find(portfolio, createFindById(id));
|
||||
// if (!toValidate) {
|
||||
// return false;
|
||||
// }
|
||||
// const { title, url, image, description } = toValidate;
|
||||
|
||||
const { state: titleState } = this.getTitleValidation(title);
|
||||
const { state: urlState } = this.getUrlValidation(url);
|
||||
const { state: imageState } = this.getUrlValidation(image, true);
|
||||
const { state: descriptionState } =
|
||||
this.getDescriptionValidation(description);
|
||||
return [titleState, imageState, urlState, descriptionState]
|
||||
.filter(Boolean)
|
||||
.every(state => state === 'success');
|
||||
};
|
||||
// const { state: titleState } = this.getTitleValidation(title);
|
||||
// const { state: urlState } = this.getUrlValidation(url);
|
||||
// const { state: imageState } = this.getUrlValidation(image, true);
|
||||
// const { state: descriptionState } =
|
||||
// this.getDescriptionValidation(description);
|
||||
// return [titleState, imageState, urlState, descriptionState]
|
||||
// .filter(Boolean)
|
||||
// .every(state => state === 'success');
|
||||
// };
|
||||
|
||||
getDescriptionValidation(description) {
|
||||
getDescriptionValidation(description: string) {
|
||||
const { t } = this.props;
|
||||
const len = description.length;
|
||||
const charsLeft = 288 - len;
|
||||
@ -152,7 +163,7 @@ class PortfolioSettings extends Component {
|
||||
return { state: 'success', message: '' };
|
||||
}
|
||||
|
||||
getTitleValidation(title) {
|
||||
getTitleValidation(title: string) {
|
||||
const { t } = this.props;
|
||||
if (!title) {
|
||||
return { state: 'error', message: t('validation.title-required') };
|
||||
@ -167,7 +178,7 @@ class PortfolioSettings extends Component {
|
||||
return { state: 'success', message: '' };
|
||||
}
|
||||
|
||||
getUrlValidation(maybeUrl, isImage) {
|
||||
getUrlValidation(maybeUrl: string, isImage?: boolean) {
|
||||
const { t } = this.props;
|
||||
const len = maybeUrl.length;
|
||||
if (len >= 4 && !hasProtocolRE.test(maybeUrl)) {
|
||||
@ -187,7 +198,11 @@ class PortfolioSettings extends Component {
|
||||
: { state: 'warning', message: t('validation.use-valid-url') };
|
||||
}
|
||||
|
||||
renderPortfolio = (portfolio, index, arr) => {
|
||||
renderPortfolio = (
|
||||
portfolio: PortfolioValues,
|
||||
index: number,
|
||||
arr: PortfolioValues[]
|
||||
) => {
|
||||
const { t } = this.props;
|
||||
const { id, title, description, url, image } = portfolio;
|
||||
const pristine = this.isFormPristine(id);
|
||||
@ -330,6 +345,5 @@ class PortfolioSettings extends Component {
|
||||
}
|
||||
|
||||
PortfolioSettings.displayName = 'PortfolioSettings';
|
||||
PortfolioSettings.propTypes = propTypes;
|
||||
|
||||
export default withTranslation()(PortfolioSettings);
|
@ -1,52 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { Button, Form } from '@freecodecamp/react-bootstrap';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { userSelector } from '../../redux';
|
||||
import { submitProfileUI } from '../../redux/settings';
|
||||
|
||||
import FullWidthRow from '../helpers/full-width-row';
|
||||
import Spacer from '../helpers/spacer';
|
||||
import ToggleSetting from './ToggleSetting';
|
||||
import SectionHeader from './SectionHeader';
|
||||
import ToggleSetting from './toggle-setting';
|
||||
import SectionHeader from './section-header';
|
||||
|
||||
const mapStateToProps = createSelector(userSelector, user => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
user
|
||||
}));
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators({ submitProfileUI }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
submitProfileUI: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
user: PropTypes.shape({
|
||||
profileUI: PropTypes.shape({
|
||||
isLocked: PropTypes.bool,
|
||||
showAbout: PropTypes.bool,
|
||||
showCerts: PropTypes.bool,
|
||||
showDonation: PropTypes.bool,
|
||||
showHeatMap: PropTypes.bool,
|
||||
showLocation: PropTypes.bool,
|
||||
showName: PropTypes.bool,
|
||||
showPoints: PropTypes.bool,
|
||||
showPortfolio: PropTypes.bool,
|
||||
showTimeLine: PropTypes.bool
|
||||
}),
|
||||
username: PropTypes.String
|
||||
})
|
||||
type ProfileUIType = {
|
||||
isLocked: boolean;
|
||||
showAbout: boolean;
|
||||
showCerts: boolean;
|
||||
showDonation: boolean;
|
||||
showHeatMap: boolean;
|
||||
showLocation: boolean;
|
||||
showName: boolean;
|
||||
showPoints: boolean;
|
||||
showPortfolio: boolean;
|
||||
showTimeLine: boolean;
|
||||
};
|
||||
|
||||
class PrivacySettings extends Component {
|
||||
handleSubmit = e => e.preventDefault();
|
||||
type PrivacyProps = {
|
||||
submitProfileUI: (profileUI: ProfileUIType) => void;
|
||||
t: (str: string) => string;
|
||||
user: {
|
||||
profileUI: ProfileUIType;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
|
||||
toggleFlag = flag => () => {
|
||||
class PrivacySettings extends Component<PrivacyProps> {
|
||||
static displayName: string;
|
||||
|
||||
handleSubmit = (e: React.FormEvent) => e.preventDefault();
|
||||
|
||||
toggleFlag = (flag: string) => () => {
|
||||
const privacyValues = { ...this.props.user.profileUI };
|
||||
privacyValues[flag] = !privacyValues[flag];
|
||||
privacyValues[flag as keyof ProfileUIType] =
|
||||
!privacyValues[flag as keyof ProfileUIType];
|
||||
this.props.submitProfileUI(privacyValues);
|
||||
};
|
||||
|
||||
@ -176,7 +184,6 @@ class PrivacySettings extends Component {
|
||||
}
|
||||
|
||||
PrivacySettings.displayName = 'PrivacySettings';
|
||||
PrivacySettings.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { 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';
|
||||
|
||||
const propTypes = {
|
||||
onHide: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool
|
||||
type ResetModalProps = {
|
||||
onHide: () => void;
|
||||
reset: () => void;
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
function ResetModal(props) {
|
||||
function ResetModal(props: ResetModalProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const { show, onHide } = props;
|
||||
|
||||
@ -64,6 +65,5 @@ function ResetModal(props) {
|
||||
}
|
||||
|
||||
ResetModal.displayName = 'ResetModal';
|
||||
ResetModal.propTypes = propTypes;
|
||||
|
||||
export default ResetModal;
|
@ -1,17 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FullWidthRow from '../helpers/full-width-row';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.element,
|
||||
PropTypes.node
|
||||
])
|
||||
type SectionHeaderProps = {
|
||||
children: string | React.ReactNode | React.ReactElement;
|
||||
};
|
||||
|
||||
function SectionHeader({ children }) {
|
||||
function SectionHeader({ children }: SectionHeaderProps): JSX.Element {
|
||||
return (
|
||||
<FullWidthRow>
|
||||
<h2 className='text-center'>{children}</h2>
|
||||
@ -21,6 +16,5 @@ function SectionHeader({ children }) {
|
||||
}
|
||||
|
||||
SectionHeader.displayName = 'SectionHeader';
|
||||
SectionHeader.propTypes = propTypes;
|
||||
|
||||
export default SectionHeader;
|
@ -1,20 +1,27 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { Form } from '@freecodecamp/react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ToggleSetting from './ToggleSetting';
|
||||
import ToggleSetting from './toggle-setting';
|
||||
|
||||
const propTypes = {
|
||||
currentTheme: PropTypes.string.isRequired,
|
||||
toggleNightMode: PropTypes.func.isRequired
|
||||
type ThemeProps = {
|
||||
currentTheme: string;
|
||||
toggleNightMode: (theme: 'default' | 'night') => void;
|
||||
};
|
||||
|
||||
export default function ThemeSettings({ currentTheme, toggleNightMode }) {
|
||||
export default function ThemeSettings({
|
||||
currentTheme,
|
||||
toggleNightMode
|
||||
}: ThemeProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Form inline={true} onSubmit={e => e.preventDefault()}>
|
||||
<Form
|
||||
inline={true}
|
||||
onSubmit={(e: React.FormEvent): void => e.preventDefault()}
|
||||
>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.night-mode')}
|
||||
flag={currentTheme === 'night'}
|
||||
@ -30,4 +37,3 @@ export default function ThemeSettings({ currentTheme, toggleNightMode }) {
|
||||
}
|
||||
|
||||
ThemeSettings.displayName = 'ThemeSettings';
|
||||
ThemeSettings.propTypes = propTypes;
|
@ -1,9 +1,10 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FormGroup,
|
||||
ControlLabel,
|
||||
HelpBlock
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import TB from '../helpers/toggle-button';
|
||||
@ -11,12 +12,14 @@ import { ButtonSpacer } from '../helpers';
|
||||
|
||||
import './toggle-setting.css';
|
||||
|
||||
const propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
explain: PropTypes.string,
|
||||
flag: PropTypes.bool.isRequired,
|
||||
flagName: PropTypes.string.isRequired,
|
||||
toggleFlag: PropTypes.func.isRequired
|
||||
type ToggleSettingProps = {
|
||||
action: string;
|
||||
explain?: string;
|
||||
flag: boolean;
|
||||
flagName: string;
|
||||
toggleFlag: () => void;
|
||||
offLabel: string;
|
||||
onLabel: string;
|
||||
};
|
||||
|
||||
export default function ToggleSetting({
|
||||
@ -26,7 +29,7 @@ export default function ToggleSetting({
|
||||
flagName,
|
||||
toggleFlag,
|
||||
...restProps
|
||||
}) {
|
||||
}: ToggleSettingProps): JSX.Element {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='toggle-setting-container'>
|
||||
@ -53,4 +56,3 @@ export default function ToggleSetting({
|
||||
}
|
||||
|
||||
ToggleSetting.displayName = 'ToggleSetting';
|
||||
ToggleSetting.propTypes = propTypes;
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
@ -8,8 +8,11 @@ import {
|
||||
FormControl,
|
||||
Alert,
|
||||
FormGroup
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
validateUsername,
|
||||
@ -20,24 +23,46 @@ import FullWidthRow from '../helpers/full-width-row';
|
||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||
import { isValidUsername } from '../../../../utils/validate';
|
||||
|
||||
const propTypes = {
|
||||
isValidUsername: PropTypes.bool,
|
||||
submitNewUsername: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
validateUsername: PropTypes.func.isRequired,
|
||||
validating: PropTypes.bool
|
||||
type UsernameProps = {
|
||||
isValidUsername: boolean;
|
||||
submitNewUsername: (name: string) => void;
|
||||
t: (str: string, obj?: { username: string }) => string;
|
||||
username: string;
|
||||
validateUsername: (name: string) => void;
|
||||
validating: boolean;
|
||||
};
|
||||
|
||||
type UsernameState = {
|
||||
isFormPristine: boolean;
|
||||
formValue: string;
|
||||
characterValidation: {
|
||||
valid: boolean;
|
||||
error: null | string;
|
||||
};
|
||||
submitClicked: boolean;
|
||||
isUserNew: boolean;
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
usernameValidationSelector,
|
||||
({ isValidUsername, fetchState }) => ({
|
||||
({
|
||||
isValidUsername,
|
||||
fetchState
|
||||
}: {
|
||||
isValidUsername: boolean;
|
||||
fetchState: {
|
||||
pending: boolean;
|
||||
complete: boolean;
|
||||
errored: boolean;
|
||||
error: boolean | null;
|
||||
};
|
||||
}) => ({
|
||||
isValidUsername,
|
||||
validating: fetchState.pending
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
submitNewUsername,
|
||||
@ -49,8 +74,9 @@ const mapDispatchToProps = dispatch =>
|
||||
const hex = '[0-9a-f]';
|
||||
const tempUserRegex = new RegExp(`^fcc${hex}{8}-(${hex}{4}-){3}${hex}{12}$`);
|
||||
|
||||
class UsernameSettings extends Component {
|
||||
constructor(props) {
|
||||
class UsernameSettings extends Component<UsernameProps, UsernameState> {
|
||||
static displayName: string;
|
||||
constructor(props: UsernameProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@ -66,13 +92,13 @@ class UsernameSettings extends Component {
|
||||
this.validateFormInput = this.validateFormInput.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
componentDidUpdate(prevProps: UsernameProps, prevState: UsernameState) {
|
||||
const { username: prevUsername } = prevProps;
|
||||
const { formValue: prevFormValue } = prevState;
|
||||
const { username } = this.props;
|
||||
const { formValue } = this.state;
|
||||
if (prevUsername !== username && prevFormValue === formValue) {
|
||||
/* eslint-disable-next-line react/no-did-update-set-state */
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
return this.setState({
|
||||
isFormPristine: username === formValue,
|
||||
submitClicked: false,
|
||||
@ -82,7 +108,7 @@ class UsernameSettings extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const { submitNewUsername } = this.props;
|
||||
const {
|
||||
@ -95,10 +121,10 @@ class UsernameSettings extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
handleChange(e: React.FormEvent<HTMLInputElement>) {
|
||||
e.preventDefault();
|
||||
const { username, validateUsername } = this.props;
|
||||
const newValue = e.target.value;
|
||||
const newValue = (e.target as HTMLInputElement).value;
|
||||
return this.setState(
|
||||
{
|
||||
formValue: newValue,
|
||||
@ -113,11 +139,15 @@ class UsernameSettings extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
validateFormInput(formValue) {
|
||||
validateFormInput(formValue: string) {
|
||||
return isValidUsername(formValue);
|
||||
}
|
||||
|
||||
renderAlerts(validating, error, isValidUsername) {
|
||||
renderAlerts(
|
||||
validating: boolean,
|
||||
error: string | null,
|
||||
isValidUsername: boolean
|
||||
) {
|
||||
const { t } = this.props;
|
||||
|
||||
if (!validating && error) {
|
||||
@ -203,7 +233,6 @@ class UsernameSettings extends Component {
|
||||
}
|
||||
|
||||
UsernameSettings.displayName = 'UsernameSettings';
|
||||
UsernameSettings.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
3
client/src/declarations.d.ts
vendored
3
client/src/declarations.d.ts
vendored
@ -1,3 +1,6 @@
|
||||
// eslint-disable-next-line import/unambiguous
|
||||
declare module '@freecodecamp/react-bootstrap';
|
||||
declare module '@freecodecamp/strip-comments';
|
||||
declare module '@types/react-redux';
|
||||
declare module '@types/validator';
|
||||
declare module '@types/lodash-es';
|
||||
|
Reference in New Issue
Block a user