feat: add grid map (#44557)
* feat: add map grid * fix: lint/prettier/ts Co-authored-by: Tom <20648924+moT01@users.noreply.github.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -26,7 +26,7 @@ import {
|
||||
isAVariantSelector
|
||||
} from '../redux';
|
||||
|
||||
interface ExecuteGaArg {
|
||||
export interface ExecuteGaArg {
|
||||
type: string;
|
||||
data: {
|
||||
category: string;
|
||||
|
@ -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;
|
||||
|
@ -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 ? (
|
||||
<GreenPass style={mapIconStyle} />
|
||||
) : (
|
||||
<GreenNotCompleted style={mapIconStyle} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { challengesWithCompleted, isProjectBlock } = this.props;
|
||||
|
||||
return (
|
||||
<ul className='map-challenges-ul'>
|
||||
{[...challengesWithCompleted].map(challenge => (
|
||||
<li
|
||||
className={`map-challenge-title ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
}`}
|
||||
id={challenge.dashedName}
|
||||
key={'map-challenge' + challenge.fields.slug}
|
||||
>
|
||||
{!isProjectBlock ? (
|
||||
<Link
|
||||
onClick={this.handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
<span className='badge map-badge'>
|
||||
{this.renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
{challenge.title}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
onClick={this.handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
<span className='badge map-badge map-project-checkmark'>
|
||||
{this.renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Challenges.displayName = 'Challenges';
|
||||
Challenges.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation()(Challenges));
|
@ -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<BlockProps> {
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
isProjectBlock={isProjectBlock}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableAnchor>
|
||||
@ -214,6 +216,7 @@ export class Block extends Component<BlockProps> {
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
isProjectBlock={isProjectBlock}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
124
client/src/templates/Introduction/components/challenges.tsx
Normal file
124
client/src/templates/Introduction/components/challenges.tsx
Normal file
@ -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 ? (
|
||||
<GreenPass style={mapIconStyle} />
|
||||
) : (
|
||||
<GreenNotCompleted style={mapIconStyle} />
|
||||
);
|
||||
|
||||
const isGridMap = superBlock === SuperBlocks.RespWebDesignNew;
|
||||
|
||||
return isGridMap ? (
|
||||
<ul className={`map-challenges-ul map-challenges-grid `}>
|
||||
{challengesWithCompleted.map((challenge, i) => (
|
||||
<li
|
||||
className={`map-challenge-title map-challenge-title-grid ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
}`}
|
||||
id={challenge.dashedName}
|
||||
key={`map-challenge ${challenge.fields.slug}`}
|
||||
>
|
||||
{!isProjectBlock ? (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
className={`map-grid-item ${
|
||||
challenge.isCompleted ? 'challenge-completed' : ''
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
<span className=' badge map-badge map-project-checkmark'>
|
||||
{renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<ul className={`map-challenges-ul`}>
|
||||
{challengesWithCompleted.map(challenge => (
|
||||
<li
|
||||
className={`map-challenge-title ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
}`}
|
||||
id={challenge.dashedName}
|
||||
key={'map-challenge' + challenge.fields.slug}
|
||||
>
|
||||
{!isProjectBlock ? (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
<span className='badge map-badge'>
|
||||
{renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
{challenge.title}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
<span className='badge map-badge map-project-checkmark'>
|
||||
{renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
Challenges.displayName = 'Challenges';
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTranslation()(Challenges));
|
@ -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;
|
||||
|
Reference in New Issue
Block a user