diff --git a/client/src/pages/donate.tsx b/client/src/pages/donate.tsx index 683f8b9976..1751c0859e 100644 --- a/client/src/pages/donate.tsx +++ b/client/src/pages/donate.tsx @@ -26,7 +26,7 @@ import { isAVariantSelector } from '../redux'; -interface ExecuteGaArg { +export interface ExecuteGaArg { type: string; data: { category: string; diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 9f167b05bf..e2fb334068 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -129,6 +129,20 @@ export interface VideoLocaleIds { portuguese?: string; } +export type ChallengeWithCompletedNode = { + block: string; + challengeType: number; + dashedName: string; + fields: { + slug: string; + }; + id: string; + isCompleted: boolean; + order: number; + superBlock: SuperBlocks; + title: string; +}; + export type ChallengeNode = { challenge: { block: string; diff --git a/client/src/templates/Introduction/components/Challenges.js b/client/src/templates/Introduction/components/Challenges.js deleted file mode 100644 index 532ac0f6c9..0000000000 --- a/client/src/templates/Introduction/components/Challenges.js +++ /dev/null @@ -1,104 +0,0 @@ -import { Link } from 'gatsby'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { withTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { createSelector } from 'reselect'; - -import GreenNotCompleted from '../../../assets/icons/green-not-completed'; -import GreenPass from '../../../assets/icons/green-pass'; -import { completedChallengesSelector, executeGA } from '../../../redux'; - -const mapStateToProps = state => { - return createSelector(completedChallengesSelector, completedChallenges => ({ - completedChallenges: completedChallenges.map(({ id }) => id) - }))(state); -}; - -const mapDispatchToProps = dispatch => - bindActionCreators({ executeGA }, dispatch); - -const propTypes = { - challengesWithCompleted: PropTypes.array, - executeGA: PropTypes.func, - isProjectBlock: PropTypes.bool -}; - -const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' }; - -export class Challenges extends Component { - constructor(...props) { - super(...props); - - this.handleChallengeClick = this.handleChallengeClick.bind(this); - } - - handleChallengeClick(slug) { - return () => { - return this.props.executeGA({ - type: 'event', - data: { - category: 'Map Challenge Click', - action: slug - } - }); - }; - } - - renderCheckMark(isCompleted) { - return isCompleted ? ( - - ) : ( - - ); - } - - render() { - const { challengesWithCompleted, isProjectBlock } = this.props; - - return ( - - ); - } -} - -Challenges.displayName = 'Challenges'; -Challenges.propTypes = propTypes; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(withTranslation()(Challenges)); diff --git a/client/src/templates/Introduction/components/block.tsx b/client/src/templates/Introduction/components/block.tsx index 20b52cb67e..863f47a982 100644 --- a/client/src/templates/Introduction/components/block.tsx +++ b/client/src/templates/Introduction/components/block.tsx @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import ScrollableAnchor from 'react-scrollable-anchor'; import { bindActionCreators, Dispatch } from 'redux'; import { createSelector } from 'reselect'; +import { SuperBlocks } from '../../../../../config/certification-settings'; import envData from '../../../../../config/env.json'; import { isAuditedCert } from '../../../../../utils/is-audited'; @@ -15,7 +16,7 @@ import { completedChallengesSelector, executeGA } from '../../../redux'; import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types'; import { playTone } from '../../../utils/tone'; import { makeExpandedBlockSelector, toggleBlock } from '../redux'; -import Challenges from './Challenges'; +import Challenges from './challenges'; const { curriculumLocale } = envData; @@ -44,7 +45,7 @@ interface BlockProps { completedChallengeIds: string[]; executeGA: typeof executeGA; isExpanded: boolean; - superBlock: string; + superBlock: SuperBlocks; t: TFunction; toggleBlock: typeof toggleBlock; } @@ -167,6 +168,7 @@ export class Block extends Component { @@ -214,6 +216,7 @@ export class Block extends Component { )} diff --git a/client/src/templates/Introduction/components/challenges.tsx b/client/src/templates/Introduction/components/challenges.tsx new file mode 100644 index 0000000000..6a5fb25d61 --- /dev/null +++ b/client/src/templates/Introduction/components/challenges.tsx @@ -0,0 +1,124 @@ +import { Link } from 'gatsby'; +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import type { Dispatch } from 'redux'; + +import GreenNotCompleted from '../../../assets/icons/green-not-completed'; +import GreenPass from '../../../assets/icons/green-pass'; +import { executeGA } from '../../../redux'; +import { SuperBlocks } from '../../../../../config/certification-settings'; +import { ExecuteGaArg } from '../../../pages/donate'; +import { ChallengeWithCompletedNode } from '../../../redux/prop-types'; + +const mapDispatchToProps = (dispatch: Dispatch) => + bindActionCreators({ executeGA }, dispatch); + +interface Challenges { + challengesWithCompleted: ChallengeWithCompletedNode[]; + executeGA: (payload: ExecuteGaArg) => void; + isProjectBlock: boolean; + superBlock: SuperBlocks; +} + +const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' }; + +function Challenges({ + challengesWithCompleted, + executeGA, + isProjectBlock, + superBlock +}: Challenges): JSX.Element { + const handleChallengeClick = (slug: string) => + executeGA({ + type: 'event', + data: { + category: 'Map Challenge Click', + action: slug + } + }); + + const renderCheckMark = (isCompleted: boolean) => + isCompleted ? ( + + ) : ( + + ); + + const isGridMap = superBlock === SuperBlocks.RespWebDesignNew; + + return isGridMap ? ( +
    + {challengesWithCompleted.map((challenge, i) => ( +
  • + {!isProjectBlock ? ( + handleChallengeClick(challenge.fields.slug)} + to={challenge.fields.slug} + className={`map-grid-item ${ + challenge.isCompleted ? 'challenge-completed' : '' + }`} + > + {i + 1} + + ) : ( + handleChallengeClick(challenge.fields.slug)} + to={challenge.fields.slug} + > + {challenge.title} + + {renderCheckMark(challenge.isCompleted)} + + + )} +
  • + ))} +
+ ) : ( +
    + {challengesWithCompleted.map(challenge => ( +
  • + {!isProjectBlock ? ( + handleChallengeClick(challenge.fields.slug)} + to={challenge.fields.slug} + > + + {renderCheckMark(challenge.isCompleted)} + + {challenge.title} + + ) : ( + handleChallengeClick(challenge.fields.slug)} + to={challenge.fields.slug} + > + {challenge.title} + + {renderCheckMark(challenge.isCompleted)} + + + )} +
  • + ))} +
+ ); +} + +Challenges.displayName = 'Challenges'; + +export default connect(null, mapDispatchToProps)(withTranslation()(Challenges)); diff --git a/client/src/templates/Introduction/intro.css b/client/src/templates/Introduction/intro.css index 19f63f8773..79477563a9 100644 --- a/client/src/templates/Introduction/intro.css +++ b/client/src/templates/Introduction/intro.css @@ -229,6 +229,21 @@ button.map-title { padding: 10px 15px; } +.map-challenges-grid { + display: flex; + flex-wrap: wrap; +} +.map-challenge-title-grid { + flex: 0 1 60px; +} +.map-challenges-grid .map-project-wrap { + flex: 1 1 100px; +} +.map-challenge-title-grid a.map-grid-item { + margin: 5px 0px; + justify-content: center; +} + .block-description { padding: 18px 0; } @@ -237,6 +252,10 @@ button.map-title { margin-bottom: 0; } +.challenge-completed { + background: var(--highlight-background); +} + @media screen and (max-width: 500px) { .super-block-intro-page p { font-size: 1rem;