changed fetchMapUi epic to add extra param - initialNode added util method to open path in the map by node name changed action handler for fetchMapUi.complete to open initialNode changed map component to set scroll on component mount and update added attribute to challenge component to find challenge node by name extracted createEventMetaCreator into separate file to break circular dependencies Closes #16248
148 lines
3.4 KiB
JavaScript
148 lines
3.4 KiB
JavaScript
import React, { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { connect } from 'react-redux';
|
|
import { createSelector } from 'reselect';
|
|
import classnames from 'classnames';
|
|
import debug from 'debug';
|
|
|
|
import { clickOnChallenge } from './redux';
|
|
import { userSelector } from '../redux';
|
|
import { challengeMapSelector } from '../entities';
|
|
import { Link } from '../Router';
|
|
import { onRouteChallenges } from '../routes/Challenges/redux';
|
|
|
|
const propTypes = {
|
|
block: PropTypes.string,
|
|
challenge: PropTypes.object,
|
|
clickOnChallenge: PropTypes.func.isRequired,
|
|
dashedName: PropTypes.string,
|
|
isComingSoon: PropTypes.bool,
|
|
isCompleted: PropTypes.bool,
|
|
isDev: PropTypes.bool,
|
|
isLocked: PropTypes.bool,
|
|
title: PropTypes.string
|
|
};
|
|
const mapDispatchToProps = { clickOnChallenge };
|
|
|
|
function makeMapStateToProps(_, { dashedName }) {
|
|
return createSelector(
|
|
userSelector,
|
|
challengeMapSelector,
|
|
(
|
|
{ challengeMap: userChallengeMap },
|
|
challengeMap
|
|
) => {
|
|
const {
|
|
id,
|
|
title,
|
|
block,
|
|
isLocked,
|
|
isComingSoon
|
|
} = challengeMap[dashedName] || {};
|
|
const isCompleted = userChallengeMap ? !!userChallengeMap[id] : false;
|
|
return {
|
|
dashedName,
|
|
isCompleted,
|
|
title,
|
|
block,
|
|
isLocked,
|
|
isComingSoon,
|
|
isDev: debug.enabled('fcc:*')
|
|
};
|
|
}
|
|
);
|
|
}
|
|
|
|
export class Challenge extends PureComponent {
|
|
renderCompleted(isCompleted, isLocked) {
|
|
if (isLocked || !isCompleted) {
|
|
return null;
|
|
}
|
|
return <span className='sr-only'>completed</span>;
|
|
}
|
|
|
|
renderComingSoon(isComingSoon) {
|
|
if (!isComingSoon) {
|
|
return null;
|
|
}
|
|
return (
|
|
<span className='text-info small'>
|
|
   
|
|
<strong>
|
|
<em>Coming Soon</em>
|
|
</strong>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
renderLocked(title, isComingSoon, className) {
|
|
return (
|
|
<p
|
|
className={ className }
|
|
key={ title }
|
|
>
|
|
{ title }
|
|
{ this.renderComingSoon(isComingSoon) }
|
|
</p>
|
|
);
|
|
}
|
|
|
|
|
|
render() {
|
|
const {
|
|
block,
|
|
clickOnChallenge,
|
|
dashedName,
|
|
isComingSoon,
|
|
isCompleted,
|
|
isDev,
|
|
isLocked,
|
|
title
|
|
} = this.props;
|
|
if (!title) {
|
|
return null;
|
|
}
|
|
const challengeClassName = classnames({
|
|
'text-primary': true,
|
|
'padded-ionic-icon': true,
|
|
'map-challenge-title': true,
|
|
'ion-checkmark-circled faded': !(isLocked || isComingSoon) && isCompleted,
|
|
'ion-ios-circle-outline': !(isLocked || isComingSoon) && !isCompleted,
|
|
'ion-locked': isLocked || isComingSoon,
|
|
disabled: isLocked || (!isDev && isComingSoon)
|
|
});
|
|
if (isLocked || (!isDev && isComingSoon)) {
|
|
return this.renderLocked(
|
|
title,
|
|
isComingSoon,
|
|
challengeClassName
|
|
);
|
|
}
|
|
return (
|
|
<div
|
|
className={ challengeClassName }
|
|
data-challenge={dashedName}
|
|
key={ title }
|
|
>
|
|
<Link
|
|
onClick={ clickOnChallenge }
|
|
to={ onRouteChallenges({ dashedName, block }) }
|
|
>
|
|
<span >
|
|
{ title }
|
|
{ this.renderCompleted(isCompleted, isLocked) }
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
Challenge.propTypes = propTypes;
|
|
Challenge.dispalyName = 'Challenge';
|
|
|
|
export default connect(
|
|
makeMapStateToProps,
|
|
mapDispatchToProps
|
|
)(Challenge);
|