feat(client): ts-migrate /client-only-routes/ (#42587)

* feat(client): refactor /client-only-routes/ to ts

* ts refactor with no-verify for Spacer

* kebaberise filenames

* fix imports
This commit is contained in:
Shaun Hamilton
2021-06-25 15:58:07 +01:00
committed by Mrugesh Mohapatra
parent 4f179ec8e1
commit 40323aef6a
13 changed files with 224 additions and 176 deletions

View File

@@ -1,12 +1,11 @@
/* eslint-disable react/jsx-sort-props */ /* eslint-disable react/jsx-sort-props */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import { bindActionCreators, Dispatch } from 'redux';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { Grid, Row, Col, Image, Button } from '@freecodecamp/react-bootstrap'; import { Grid, Row, Col, Image, Button } from '@freecodecamp/react-bootstrap';
import ShowProjectLinks from './ShowProjectLinks'; import ShowProjectLinks from './show-project-links';
import FreeCodeCampLogo from '../assets/icons/FreeCodeCamp-logo'; import FreeCodeCampLogo from '../assets/icons/FreeCodeCamp-logo';
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
import DonateForm from '../components/Donation/DonateForm'; import DonateForm from '../components/Donation/DonateForm';
@@ -23,62 +22,70 @@ import {
userByNameSelector, userByNameSelector,
fetchProfileForUser fetchProfileForUser
} from '../redux'; } from '../redux';
import { certMap } from '../../src/resources/certAndProjectMap'; import { certMap } from '../resources/certAndProjectMap';
import { createFlashMessage } from '../components/Flash/redux'; import { createFlashMessage } from '../components/Flash/redux';
import standardErrorMessage from '../utils/standardErrorMessage'; import standardErrorMessage from '../utils/standardErrorMessage';
import reallyWeirdErrorMessage from '../utils/reallyWeirdErrorMessage'; import reallyWeirdErrorMessage from '../utils/reallyWeirdErrorMessage';
import { langCodes } from '../../../config/i18n/all-langs'; import { langCodes } from '../../../config/i18n/all-langs';
// eslint-disable-next-line
// @ts-ignore
import envData from '../../../config/env.json'; import envData from '../../../config/env.json';
import RedirectHome from '../components/RedirectHome'; import RedirectHome from '../components/RedirectHome';
import { Loader, Spacer } from '../components/helpers'; import { Loader, Spacer } from '../components/helpers';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { User } from '../redux/prop-types'; import { UserType } from '../redux/prop-types';
const { clientLocale } = envData; const { clientLocale } = envData as { clientLocale: keyof typeof langCodes };
const localeCode = langCodes[clientLocale]; const localeCode = langCodes[clientLocale];
type CertType = {
const propTypes = { username: string;
cert: PropTypes.shape({ name: string;
username: PropTypes.string, certName: string;
name: PropTypes.string, certTitle: string;
certName: PropTypes.string, completionTime: number;
certTitle: PropTypes.string, date: number;
completionTime: PropTypes.number,
date: PropTypes.number
}),
certDashedName: PropTypes.string,
certSlug: PropTypes.string,
createFlashMessage: PropTypes.func.isRequired,
executeGA: PropTypes.func,
fetchProfileForUser: PropTypes.func,
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isDonating: PropTypes.bool,
isValidCert: PropTypes.bool,
location: PropTypes.shape({
pathname: PropTypes.string
}),
showCert: PropTypes.func.isRequired,
signedInUserName: PropTypes.string,
user: User,
userFetchState: PropTypes.shape({
complete: PropTypes.bool
}),
userFullName: PropTypes.string,
username: PropTypes.string
}; };
interface IShowCertificationProps {
cert: CertType;
certDashedName: string;
certSlug: string;
createFlashMessage: (payload: typeof standardErrorMessage) => void;
executeGA: (payload: Record<string, unknown>) => void;
fetchProfileForUser: (username: string) => void;
fetchState: {
pending: boolean;
complete: boolean;
errored: boolean;
};
isDonating: boolean;
isValidCert: boolean;
location: {
pathname: string;
};
showCert: ({
username,
certSlug
}: {
username: string;
certSlug: string;
}) => void;
signedInUserName: string;
user: UserType;
userFetchState: {
complete: boolean;
};
userFullName: string;
username: string;
}
const requestedUserSelector = (state, { username = '' }) => const requestedUserSelector = (state: unknown, { username = '' }) =>
userByNameSelector(username.toLowerCase())(state); userByNameSelector(username.toLowerCase())(state) as UserType;
const validCertSlugs = certMap.map(cert => cert.certSlug); const validCertSlugs = certMap.map(cert => cert.certSlug);
const mapStateToProps = (state, props) => { const mapStateToProps = (state: unknown, props: IShowCertificationProps) => {
const isValidCert = validCertSlugs.some(slug => slug === props.certSlug); const isValidCert = validCertSlugs.some(slug => slug === props.certSlug);
return createSelector( return createSelector(
showCertSelector, showCertSelector,
@@ -87,7 +94,14 @@ const mapStateToProps = (state, props) => {
userFetchStateSelector, userFetchStateSelector,
isDonatingSelector, isDonatingSelector,
requestedUserSelector, requestedUserSelector,
(cert, fetchState, signedInUserName, userFetchState, isDonating, user) => ({ (
cert: CertType,
fetchState: IShowCertificationProps['fetchState'],
signedInUserName: string,
userFetchState: IShowCertificationProps['userFetchState'],
isDonating: boolean,
user
) => ({
cert, cert,
fetchState, fetchState,
isValidCert, isValidCert,
@@ -99,13 +113,13 @@ const mapStateToProps = (state, props) => {
); );
}; };
const mapDispatchToProps = dispatch => const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators( bindActionCreators(
{ createFlashMessage, showCert, fetchProfileForUser, executeGA }, { createFlashMessage, showCert, fetchProfileForUser, executeGA },
dispatch dispatch
); );
const ShowCertification = props => { const ShowCertification = (props: IShowCertificationProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isDonationSubmitted, setIsDonationSubmitted] = useState(false); const [isDonationSubmitted, setIsDonationSubmitted] = useState(false);
const [isDonationDisplayed, setIsDonationDisplayed] = useState(false); const [isDonationDisplayed, setIsDonationDisplayed] = useState(false);
@@ -131,6 +145,7 @@ const ShowCertification = props => {
} = props; } = props;
if (!signedInUserName || signedInUserName !== username) { if (!signedInUserName || signedInUserName !== username) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (isEmpty(user) && username) { if (isEmpty(user) && username) {
fetchProfileForUser(username); fetchProfileForUser(username);
} }
@@ -168,7 +183,11 @@ const ShowCertification = props => {
setIsDonationClosed(true); setIsDonationClosed(true);
}; };
const handleProcessing = (duration, amount, action) => { const handleProcessing = (
duration: string,
amount: number,
action: string
) => {
props.executeGA({ props.executeGA({
type: 'event', type: 'event',
data: { data: {
@@ -241,7 +260,7 @@ const ShowCertification = props => {
</div> </div>
); );
let donationSection = ( const donationSection = (
<div className='donation-section'> <div className='donation-section'>
{!isDonationSubmitted && ( {!isDonationSubmitted && (
<Row> <Row>
@@ -327,12 +346,7 @@ const ShowCertification = props => {
<main className='information'> <main className='information'>
<div className='information-container'> <div className='information-container'>
<Trans <Trans title={certTitle} i18nKey='certification.fulltext'>
user={displayName}
title={certTitle}
time={completionTime}
i18nKey='certification.fulltext'
>
<h3>placeholder</h3> <h3>placeholder</h3>
<h1> <h1>
<strong>{{ user: displayName }}</strong> <strong>{{ user: displayName }}</strong>
@@ -376,6 +390,5 @@ const ShowCertification = props => {
}; };
ShowCertification.displayName = 'ShowCertification'; ShowCertification.displayName = 'ShowCertification';
ShowCertification.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(ShowCertification); export default connect(mapStateToProps, mapDispatchToProps)(ShowCertification);

View File

@@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
@@ -13,46 +12,58 @@ import {
import FourOhFour from '../components/FourOhFour'; import FourOhFour from '../components/FourOhFour';
import Profile from '../components/profile/Profile'; import Profile from '../components/profile/Profile';
import { isBrowser } from '../../utils/index'; import { isBrowser } from '../../utils/index';
import { UserType } from '../redux/prop-types';
const propTypes = { interface IShowProfileOrFourOhFourProps {
fetchProfileForUser: PropTypes.func.isRequired, fetchProfileForUser: (username: string) => void;
isSessionUser: PropTypes.bool, fetchState: {
maybeUser: PropTypes.string, pending: boolean;
requestedUser: PropTypes.shape({ complete: boolean;
username: PropTypes.string, errored: boolean;
profileUI: PropTypes.object };
}), isSessionUser: boolean;
showLoading: PropTypes.bool maybeUser: string;
}; requestedUser: UserType;
showLoading: boolean;
}
const createRequestedUserSelector = const createRequestedUserSelector =
() => () =>
(state, { maybeUser = '' }) => (state: unknown, { maybeUser = '' }) =>
userByNameSelector(maybeUser.toLowerCase())(state); userByNameSelector(maybeUser.toLowerCase())(state) as string;
const createIsSessionUserSelector = const createIsSessionUserSelector =
() => () =>
(state, { maybeUser = '' }) => (state: unknown, { maybeUser = '' }) =>
maybeUser.toLowerCase() === usernameSelector(state); maybeUser.toLowerCase() === usernameSelector(state);
const makeMapStateToProps = () => (state, props) => { const makeMapStateToProps =
const requestedUserSelector = createRequestedUserSelector(); () => (state: unknown, props: IShowProfileOrFourOhFourProps) => {
const isSessionUserSelector = createIsSessionUserSelector(); const requestedUserSelector = createRequestedUserSelector();
const fetchState = userProfileFetchStateSelector(state, props); const isSessionUserSelector = createIsSessionUserSelector();
return { const fetchState = userProfileFetchStateSelector(
requestedUser: requestedUserSelector(state, props), state
isSessionUser: isSessionUserSelector(state, props), ) as IShowProfileOrFourOhFourProps['fetchState'];
showLoading: fetchState.pending, return {
fetchState requestedUser: requestedUserSelector(
state,
props
) as IShowProfileOrFourOhFourProps['requestedUser'],
isSessionUser: isSessionUserSelector(state, props),
showLoading: fetchState.pending,
fetchState
};
}; };
};
const mapDispatchToProps = { const mapDispatchToProps: {
fetchProfileForUser: IShowProfileOrFourOhFourProps['fetchProfileForUser'];
} = {
fetchProfileForUser fetchProfileForUser
}; };
class ShowProfileOrFourOhFour extends Component { class ShowProfileOrFourOhFour extends Component<IShowProfileOrFourOhFourProps> {
componentDidMount() { componentDidMount() {
const { requestedUser, maybeUser, fetchProfileForUser } = this.props; const { requestedUser, maybeUser, fetchProfileForUser } = this.props;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (isEmpty(requestedUser)) { if (isEmpty(requestedUser)) {
fetchProfileForUser(maybeUser); fetchProfileForUser(maybeUser);
} }
@@ -64,6 +75,7 @@ class ShowProfileOrFourOhFour extends Component {
} }
const { isSessionUser, requestedUser, showLoading } = this.props; const { isSessionUser, requestedUser, showLoading } = this.props;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (isEmpty(requestedUser)) { if (isEmpty(requestedUser)) {
if (showLoading) { if (showLoading) {
// We don't know if /:maybeUser is a user or not, we will show // We don't know if /:maybeUser is a user or not, we will show
@@ -78,13 +90,17 @@ class ShowProfileOrFourOhFour extends Component {
// We have a response from the API, and we have some state in the // We have a response from the API, and we have some state in the
// store for /:maybeUser, we now handover rendering to the Profile component // store for /:maybeUser, we now handover rendering to the Profile component
// eslint-disable-next-line
// @ts-ignore TODO: sort out whether user.portfolio is an array or obj. lit.
return <Profile isSessionUser={isSessionUser} user={requestedUser} />; return <Profile isSessionUser={isSessionUser} user={requestedUser} />;
} }
} }
// eslint-disable-next-line
// @ts-ignore
ShowProfileOrFourOhFour.displayName = 'ShowProfileOrFourOhFour'; ShowProfileOrFourOhFour.displayName = 'ShowProfileOrFourOhFour';
ShowProfileOrFourOhFour.propTypes = propTypes;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
export default connect( export default connect(
makeMapStateToProps, makeMapStateToProps,
mapDispatchToProps mapDispatchToProps

View File

@@ -1,6 +1,5 @@
/* eslint-disable react/jsx-sort-props */ /* eslint-disable react/jsx-sort-props */
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import '../components/layouts/project-links.css'; import '../components/layouts/project-links.css';
import { maybeUrlRE } from '../utils'; import { maybeUrlRE } from '../utils';
import { Spacer, Link } from '../components/helpers'; import { Spacer, Link } from '../components/helpers';
@@ -8,69 +7,63 @@ import { projectMap, legacyProjectMap } from '../resources/certAndProjectMap';
import ProjectModal from '../components/SolutionViewer/ProjectModal'; import ProjectModal from '../components/SolutionViewer/ProjectModal';
import { find, first } from 'lodash-es'; import { find, first } from 'lodash-es';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import {
ChallengeFileType,
CompletedChallenge,
UserType
} from '../redux/prop-types';
const propTypes = { interface IShowProjectLinksProps {
certName: PropTypes.string, certName: string;
name: PropTypes.string, name: string;
user: PropTypes.shape({ user: UserType;
completedChallenges: PropTypes.arrayOf( }
PropTypes.shape({
id: PropTypes.string, type SolutionStateType = {
solution: PropTypes.string, projectTitle: string;
githubLink: PropTypes.string, challengeFiles: ChallengeFileType[] | null;
files: PropTypes.arrayOf( solution: null | string;
PropTypes.shape({ isOpen: boolean;
contents: PropTypes.string,
ext: PropTypes.string,
key: PropTypes.string,
name: PropTypes.string,
path: PropTypes.string
})
)
})
),
username: PropTypes.string
})
}; };
const initSolutionState = { const initSolutionState: SolutionStateType = {
projectTitle: '', projectTitle: '',
files: null, challengeFiles: null,
solution: null, solution: null,
isOpen: false isOpen: false
}; };
const ShowProjectLinks = props => { const ShowProjectLinks = (props: IShowProjectLinksProps): JSX.Element => {
const [solutionState, setSolutionState] = useState(initSolutionState); const [solutionState, setSolutionState] = useState(initSolutionState);
const handleSolutionModalHide = () => setSolutionState(initSolutionState); const handleSolutionModalHide = () => setSolutionState(initSolutionState);
const { t } = useTranslation(); const { t } = useTranslation();
const getProjectSolution = (projectId, projectTitle) => { const getProjectSolution = (projectId: string, projectTitle: string) => {
const { const {
user: { completedChallenges } user: { completedChallenges }
} = props; } = props;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const completedProject = find( const completedProject = find(
completedChallenges, completedChallenges,
({ id }) => projectId === id ({ id }) => projectId === id
); ) as CompletedChallenge;
if (!completedProject) { if (!completedProject) {
return null; return null;
} }
const { solution, githubLink, files } = completedProject; const { solution, githubLink, challengeFiles } = completedProject;
const onClickHandler = () => const onClickHandler = () =>
setSolutionState({ setSolutionState({
projectTitle, projectTitle,
files, challengeFiles,
solution, solution,
isOpen: true isOpen: true
}); });
if (files && files.length) { if (challengeFiles) {
return ( return (
<button <button
onClick={onClickHandler} onClick={onClickHandler}
@@ -96,7 +89,6 @@ const ShowProjectLinks = props => {
if (maybeUrlRE.test(solution)) { if (maybeUrlRE.test(solution)) {
return ( return (
<a <a
block={'true'}
className='btn-invert' className='btn-invert'
href={solution} href={solution}
rel='noopener noreferrer' rel='noopener noreferrer'
@@ -113,7 +105,7 @@ const ShowProjectLinks = props => {
); );
}; };
const renderProjectsFor = certName => { const renderProjectsFor = (certName: string) => {
if (certName === 'Legacy Full Stack') { if (certName === 'Legacy Full Stack') {
const legacyCerts = [ const legacyCerts = [
{ title: 'Responsive Web Design' }, { title: 'Responsive Web Design' },
@@ -124,8 +116,13 @@ const ShowProjectLinks = props => {
{ title: 'Legacy Information Security and Quality Assurance' } { title: 'Legacy Information Security and Quality Assurance' }
]; ];
return legacyCerts.map((cert, ind) => { return legacyCerts.map((cert, ind) => {
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
// @ts-expect-error Error expected until projectMap is typed
const mapToUse = projectMap[cert.title] || legacyProjectMap[cert.title]; const mapToUse = projectMap[cert.title] || legacyProjectMap[cert.title];
const { certSlug } = first(mapToUse); const { certSlug } = first(mapToUse) as { certSlug: string };
const certLocation = `/certification/${username}/${certSlug}`; const certLocation = `/certification/${username}/${certSlug}`;
return ( return (
<li key={ind}> <li key={ind}>
@@ -141,16 +138,23 @@ const ShowProjectLinks = props => {
); );
}); });
} }
// @ts-expect-error Error expected until projectMap is typed
return (projectMap[certName] || legacyProjectMap[certName]).map( return (projectMap[certName] || legacyProjectMap[certName]).map(
// @ts-expect-error Error expected until projectMap is typed
({ link, title, id }) => ( ({ link, title, id }) => (
<li key={id}> <li key={id}>
{/* @ts-expect-error Link needs to be typed */}
<Link to={link} className='project-link'> <Link to={link} className='project-link'>
{t(`certification.project.title.${title}`, title)} {t(`certification.project.title.${title as string}`, title)}
</Link> </Link>
: {getProjectSolution(id, title)} : {getProjectSolution(id, title)}
</li> </li>
) )
); );
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
}; };
const { const {
@@ -158,7 +162,7 @@ const ShowProjectLinks = props => {
name, name,
user: { username } user: { username }
} = props; } = props;
const { files, isOpen, projectTitle, solution } = solutionState; const { challengeFiles, isOpen, projectTitle, solution } = solutionState;
return ( return (
<div> <div>
{t( {t(
@@ -172,7 +176,7 @@ const ShowProjectLinks = props => {
<Spacer /> <Spacer />
{isOpen ? ( {isOpen ? (
<ProjectModal <ProjectModal
files={files} files={challengeFiles}
handleSolutionModalHide={handleSolutionModalHide} handleSolutionModalHide={handleSolutionModalHide}
isOpen={isOpen} isOpen={isOpen}
projectTitle={projectTitle} projectTitle={projectTitle}
@@ -202,7 +206,6 @@ const ShowProjectLinks = props => {
); );
}; };
ShowProjectLinks.propTypes = propTypes;
ShowProjectLinks.displayName = 'ShowProjectLinks'; ShowProjectLinks.displayName = 'ShowProjectLinks';
export default ShowProjectLinks; export default ShowProjectLinks;

View File

@@ -1,10 +1,12 @@
/* eslint-disable */
// @ts-nocheck Likely need to not use ShallowRenderer
import React from 'react'; import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow'; import ShallowRenderer from 'react-test-renderer/shallow';
import envData from '../../../config/env.json'; import envData from '../../../config/env.json';
import { ShowSettings } from './ShowSettings'; import { ShowSettings } from './show-settings';
const { apiLocation } = envData; const { apiLocation } = envData as Record<string, string>;
jest.mock('../analytics'); jest.mock('../analytics');
@@ -53,3 +55,4 @@ const loggedInProps = {
}; };
const loggedOutProps = { ...loggedInProps }; const loggedOutProps = { ...loggedInProps };
loggedOutProps.isSignedIn = false; loggedOutProps.isSignedIn = false;
/* eslint-disable */

View File

@@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { Grid } from '@freecodecamp/react-bootstrap'; import { Grid } from '@freecodecamp/react-bootstrap';
@@ -25,30 +24,31 @@ import Portfolio from '../components/settings/Portfolio';
import Honesty from '../components/settings/Honesty'; import Honesty from '../components/settings/Honesty';
import Certification from '../components/settings/Certification'; import Certification from '../components/settings/Certification';
import DangerZone from '../components/settings/DangerZone'; import DangerZone from '../components/settings/DangerZone';
import { User } from '../redux/prop-types'; import { UserType } from '../redux/prop-types';
const { apiLocation } = envData; const { apiLocation } = envData as Record<string, string>;
const propTypes = { // TODO: update types for actions
createFlashMessage: PropTypes.func.isRequired, interface IShowSettingsProps {
isSignedIn: PropTypes.bool.isRequired, createFlashMessage: (paylaod: string[]) => void;
navigate: PropTypes.func.isRequired, isSignedIn: boolean;
showLoading: PropTypes.bool.isRequired, navigate: (location: string) => void;
submitNewAbout: PropTypes.func.isRequired, showLoading: boolean;
toggleNightMode: PropTypes.func.isRequired, submitNewAbout: () => void;
updateInternetSettings: PropTypes.func.isRequired, toggleNightMode: (theme: string) => void;
updateIsHonest: PropTypes.func.isRequired, updateInternetSettings: () => void;
updatePortfolio: PropTypes.func.isRequired, updateIsHonest: () => void;
updateQuincyEmail: PropTypes.func.isRequired, updatePortfolio: () => void;
user: User, updateQuincyEmail: (isSendQuincyEmail: boolean) => void;
verifyCert: PropTypes.func.isRequired user: UserType;
}; verifyCert: () => void;
}
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
signInLoadingSelector, signInLoadingSelector,
userSelector, userSelector,
isSignedInSelector, isSignedInSelector,
(showLoading, user, isSignedIn) => ({ (showLoading: boolean, user: UserType, isSignedIn) => ({
showLoading, showLoading,
user, user,
isSignedIn isSignedIn
@@ -59,15 +59,16 @@ const mapDispatchToProps = {
createFlashMessage, createFlashMessage,
navigate, navigate,
submitNewAbout, submitNewAbout,
toggleNightMode: theme => updateUserFlag({ theme }), toggleNightMode: (theme: string) => updateUserFlag({ theme }),
updateInternetSettings: updateUserFlag, updateInternetSettings: updateUserFlag,
updateIsHonest: updateUserFlag, updateIsHonest: updateUserFlag,
updatePortfolio: updateUserFlag, updatePortfolio: updateUserFlag,
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail }), updateQuincyEmail: (sendQuincyEmail: boolean) =>
updateUserFlag({ sendQuincyEmail }),
verifyCert verifyCert
}; };
export function ShowSettings(props) { export function ShowSettings(props: IShowSettingsProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
createFlashMessage, createFlashMessage,
@@ -199,6 +200,5 @@ export function ShowSettings(props) {
} }
ShowSettings.displayName = 'ShowSettings'; ShowSettings.displayName = 'ShowSettings';
ShowSettings.propTypes = propTypes; // eslint-disable-next-line @typescript-eslint/no-unsafe-call
export default connect(mapStateToProps, mapDispatchToProps)(ShowSettings); export default connect(mapStateToProps, mapDispatchToProps)(ShowSettings);

View File

@@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Grid, Panel, Button } from '@freecodecamp/react-bootstrap'; import { Grid, Panel, Button } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -8,9 +7,13 @@ import envData from '../../../config/env.json';
import FullWidthRow from '../components/helpers/full-width-row'; import FullWidthRow from '../components/helpers/full-width-row';
import { Spacer } from '../components/helpers'; import { Spacer } from '../components/helpers';
const { apiLocation } = envData; const { apiLocation } = envData as Record<string, string>;
function ShowUnsubscribed({ unsubscribeId }) { function ShowUnsubscribed({
unsubscribeId
}: {
unsubscribeId: string;
}): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Fragment> <Fragment>
@@ -47,8 +50,5 @@ function ShowUnsubscribed({ unsubscribeId }) {
} }
ShowUnsubscribed.displayName = 'ShowUnsubscribed'; ShowUnsubscribed.displayName = 'ShowUnsubscribed';
ShowUnsubscribed.propTypes = {
unsubscribeId: PropTypes.string
};
export default ShowUnsubscribed; export default ShowUnsubscribed;

View File

@@ -1,5 +1,4 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { import {
@@ -24,24 +23,31 @@ import {
} from '../redux'; } from '../redux';
import { Spacer, Loader, FullWidthRow } from '../components/helpers'; import { Spacer, Loader, FullWidthRow } from '../components/helpers';
const propTypes = { interface IShowUserProps {
email: PropTypes.string, email: string;
isSignedIn: PropTypes.bool, isSignedIn: boolean;
reportUser: PropTypes.func.isRequired, reportUser: (payload: {
t: PropTypes.func.isRequired, username: string;
userFetchState: PropTypes.shape({ reportDescription: string;
pending: PropTypes.bool, }) => void;
complete: PropTypes.bool, t: (payload: unknown, ops?: Record<string, unknown>) => string;
errored: PropTypes.bool userFetchState: {
}), pending: boolean;
username: PropTypes.string complete: boolean;
}; errored: boolean;
};
username: string;
}
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
isSignedInSelector, isSignedInSelector,
userFetchStateSelector, userFetchStateSelector,
userSelector, userSelector,
(isSignedIn, userFetchState, { email }) => ({ (
isSignedIn,
userFetchState: IShowUserProps['userFetchState'],
{ email }: { email: string }
) => ({
isSignedIn, isSignedIn,
userFetchState, userFetchState,
email email
@@ -52,8 +58,11 @@ const mapDispatchToProps = {
reportUser reportUser
}; };
class ShowUser extends Component { class ShowUser extends Component<IShowUserProps> {
constructor(props) { state: {
textarea: string;
};
constructor(props: IShowUserProps) {
super(props); super(props);
this.state = { this.state = {
@@ -63,14 +72,14 @@ class ShowUser extends Component {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
} }
handleChange(e) { handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
const textarea = e.target.value.slice(); const textarea = e.target.value.slice();
return this.setState({ return this.setState({
textarea textarea
}); });
} }
handleSubmit(e) { handleSubmit(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
const { textarea: reportDescription } = this.state; const { textarea: reportDescription } = this.state;
const { username, reportUser } = this.props; const { username, reportUser } = this.props;
@@ -124,11 +133,12 @@ class ShowUser extends Component {
<Row className='overflow-fix'> <Row className='overflow-fix'>
<Col sm={6} smOffset={3} xs={12}> <Col sm={6} smOffset={3} xs={12}>
<p> <p>
<Trans email={email} i18nKey='report.notify-1'> <Trans i18nKey='report.notify-1'>
<strong>{{ email }}</strong> <strong>{{ email }}</strong>
</Trans> </Trans>
</p> </p>
<p>{t('report.notify-2')}</p> <p>{t('report.notify-2')}</p>
{/* eslint-disable @typescript-eslint/unbound-method */}
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<FormGroup controlId='report-user-textarea'> <FormGroup controlId='report-user-textarea'>
<ControlLabel>{t('report.what')}</ControlLabel> <ControlLabel>{t('report.what')}</ControlLabel>
@@ -144,6 +154,7 @@ class ShowUser extends Component {
</Button> </Button>
<Spacer /> <Spacer />
</form> </form>
{/* eslint-disable @typescript-eslint/unbound-method */}
</Col> </Col>
</Row> </Row>
</Fragment> </Fragment>
@@ -151,9 +162,10 @@ class ShowUser extends Component {
} }
} }
// @ts-expect-error Config might need to be remedied, or component transformed into F.C.
ShowUser.displayName = 'ShowUser'; ShowUser.displayName = 'ShowUser';
ShowUser.propTypes = propTypes;
export default withTranslation()( export default withTranslation()(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
connect(mapStateToProps, mapDispatchToProps)(ShowUser) connect(mapStateToProps, mapDispatchToProps)(ShowUser)
); );

View File

@@ -4,7 +4,7 @@ import { withPrefix } from 'gatsby';
import FourOhFour from '../components/FourOhFour'; import FourOhFour from '../components/FourOhFour';
/* eslint-disable max-len */ /* eslint-disable max-len */
import ShowProfileOrFourOhFour from '../client-only-routes/ShowProfileOrFourOhFour'; import ShowProfileOrFourOhFour from '../client-only-routes/show-profile-or-four-oh-four';
/* eslint-enable max-len */ /* eslint-enable max-len */
function FourOhFourPage() { function FourOhFourPage() {

View File

@@ -3,7 +3,7 @@ import { Router } from '@reach/router';
import { withPrefix } from 'gatsby'; import { withPrefix } from 'gatsby';
import RedirectHome from '../components/RedirectHome'; import RedirectHome from '../components/RedirectHome';
import ShowCertification from '../client-only-routes/ShowCertification'; import ShowCertification from '../client-only-routes/show-certification';
import './certification.css'; import './certification.css';

View File

@@ -3,7 +3,7 @@ import { Router } from '@reach/router';
import { withPrefix } from 'gatsby'; import { withPrefix } from 'gatsby';
import RedirectHome from '../components/RedirectHome'; import RedirectHome from '../components/RedirectHome';
import ShowSettings from '../client-only-routes/ShowSettings'; import ShowSettings from '../client-only-routes/show-settings';
function Settings() { function Settings() {
return ( return (

View File

@@ -3,7 +3,7 @@ import { Router } from '@reach/router';
import { withPrefix } from 'gatsby'; import { withPrefix } from 'gatsby';
import RedirectHome from '../components/RedirectHome'; import RedirectHome from '../components/RedirectHome';
import ShowUnsubscribed from '../client-only-routes/ShowUnsubscribed'; import ShowUnsubscribed from '../client-only-routes/show-unsubscribed';
function Unsubscribed() { function Unsubscribed() {
return ( return (

View File

@@ -3,7 +3,7 @@ import { Router } from '@reach/router';
import { withPrefix } from 'gatsby'; import { withPrefix } from 'gatsby';
import RedirectHome from '../components/RedirectHome'; import RedirectHome from '../components/RedirectHome';
import ShowUser from '../client-only-routes/ShowUser'; import ShowUser from '../client-only-routes/show-user';
function User() { function User() {
return ( return (

View File

@@ -215,6 +215,7 @@ export const shouldRequestDonationSelector = state => {
export const userByNameSelector = username => state => { export const userByNameSelector = username => state => {
const { user } = state[ns]; const { user } = state[ns];
// TODO: Why return a string or empty objet literal?
return username in user ? user[username] : {}; return username in user ? user[username] : {};
}; };