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 (
-
- {[...challengesWithCompleted].map(challenge => (
- -
- {!isProjectBlock ? (
-
-
- {this.renderCheckMark(challenge.isCompleted)}
-
- {challenge.title}
-
- ) : (
-
- {challenge.title}
-
- {this.renderCheckMark(challenge.isCompleted)}
-
-
- )}
-
- ))}
-
- );
- }
-}
-
-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;