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
|
isAVariantSelector
|
||||||
} from '../redux';
|
} from '../redux';
|
||||||
|
|
||||||
interface ExecuteGaArg {
|
export interface ExecuteGaArg {
|
||||||
type: string;
|
type: string;
|
||||||
data: {
|
data: {
|
||||||
category: string;
|
category: string;
|
||||||
|
@ -129,6 +129,20 @@ export interface VideoLocaleIds {
|
|||||||
portuguese?: string;
|
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 = {
|
export type ChallengeNode = {
|
||||||
challenge: {
|
challenge: {
|
||||||
block: string;
|
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 ScrollableAnchor from 'react-scrollable-anchor';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||||
|
|
||||||
import envData from '../../../../../config/env.json';
|
import envData from '../../../../../config/env.json';
|
||||||
import { isAuditedCert } from '../../../../../utils/is-audited';
|
import { isAuditedCert } from '../../../../../utils/is-audited';
|
||||||
@ -15,7 +16,7 @@ import { completedChallengesSelector, executeGA } from '../../../redux';
|
|||||||
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
||||||
import { playTone } from '../../../utils/tone';
|
import { playTone } from '../../../utils/tone';
|
||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import Challenges from './Challenges';
|
import Challenges from './challenges';
|
||||||
|
|
||||||
const { curriculumLocale } = envData;
|
const { curriculumLocale } = envData;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ interface BlockProps {
|
|||||||
completedChallengeIds: string[];
|
completedChallengeIds: string[];
|
||||||
executeGA: typeof executeGA;
|
executeGA: typeof executeGA;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
superBlock: string;
|
superBlock: SuperBlocks;
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
toggleBlock: typeof toggleBlock;
|
toggleBlock: typeof toggleBlock;
|
||||||
}
|
}
|
||||||
@ -167,6 +168,7 @@ export class Block extends Component<BlockProps> {
|
|||||||
<Challenges
|
<Challenges
|
||||||
challengesWithCompleted={challengesWithCompleted}
|
challengesWithCompleted={challengesWithCompleted}
|
||||||
isProjectBlock={isProjectBlock}
|
isProjectBlock={isProjectBlock}
|
||||||
|
superBlock={superBlock}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableAnchor>
|
</ScrollableAnchor>
|
||||||
@ -214,6 +216,7 @@ export class Block extends Component<BlockProps> {
|
|||||||
<Challenges
|
<Challenges
|
||||||
challengesWithCompleted={challengesWithCompleted}
|
challengesWithCompleted={challengesWithCompleted}
|
||||||
isProjectBlock={isProjectBlock}
|
isProjectBlock={isProjectBlock}
|
||||||
|
superBlock={superBlock}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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;
|
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 {
|
.block-description {
|
||||||
padding: 18px 0;
|
padding: 18px 0;
|
||||||
}
|
}
|
||||||
@ -237,6 +252,10 @@ button.map-title {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.challenge-completed {
|
||||||
|
background: var(--highlight-background);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
.super-block-intro-page p {
|
.super-block-intro-page p {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
Reference in New Issue
Block a user