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:
committed by
Mrugesh Mohapatra
parent
4f179ec8e1
commit
40323aef6a
@@ -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);
|
@@ -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
|
@@ -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;
|
@@ -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 */
|
@@ -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);
|
@@ -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;
|
@@ -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)
|
||||||
);
|
);
|
@@ -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() {
|
||||||
|
@@ -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';
|
||||||
|
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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] : {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user