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:
Ahmad Abdolsaheb
2021-12-23 19:29:52 +03:00
committed by GitHub
parent f540c839ed
commit cb201a8e8b
6 changed files with 163 additions and 107 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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));

View File

@ -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>

View 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));

View File

@ -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;