305 lines
8.1 KiB
JavaScript
305 lines
8.1 KiB
JavaScript
import React, { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import _ from 'lodash';
|
|
import { createSelector } from 'reselect';
|
|
import { bindActionCreators } from 'redux';
|
|
import { connect } from 'react-redux';
|
|
import { Button } from 'react-bootstrap';
|
|
|
|
import { FullWidthRow } from '../../../helperComponents';
|
|
import { Form } from '../formHelpers';
|
|
import JSAlgoAndDSForm from './JSAlgoAndDSForm.jsx';
|
|
import SectionHeader from './SectionHeader.jsx';
|
|
import { projectsSelector } from '../../../entities';
|
|
import { claimCert, updateUserBackend } from '../redux';
|
|
import {
|
|
fetchChallenges,
|
|
userSelector,
|
|
hardGoTo,
|
|
createErrorObservable
|
|
} from '../../../redux';
|
|
import {
|
|
buildUserProjectsMap,
|
|
jsProjectSuperBlock
|
|
} from '../utils/buildUserProjectsMap';
|
|
|
|
const mapStateToProps = createSelector(
|
|
userSelector,
|
|
projectsSelector,
|
|
(
|
|
{
|
|
challengeMap,
|
|
isRespWebDesignCert,
|
|
is2018DataVisCert,
|
|
isFrontEndLibsCert,
|
|
isJsAlgoDataStructCert,
|
|
isApisMicroservicesCert,
|
|
isInfosecQaCert,
|
|
isFrontEndCert,
|
|
isBackEndCert,
|
|
isDataVisCert,
|
|
username
|
|
},
|
|
projects
|
|
) => ({
|
|
allProjects: projects,
|
|
legacyProjects: projects.filter(p => p.superBlock.includes('legacy')),
|
|
modernProjects: projects.filter(p => !p.superBlock.includes('legacy')),
|
|
userProjects: projects
|
|
.map(block => buildUserProjectsMap(block, challengeMap))
|
|
.reduce((projects, current) => ({
|
|
...projects,
|
|
...current
|
|
}), {}),
|
|
blockNameIsCertMap: {
|
|
'Applied Responsive Web Design Projects': isRespWebDesignCert,
|
|
/* eslint-disable max-len */
|
|
'JavaScript Algorithms and Data Structures Projects': isJsAlgoDataStructCert,
|
|
/* eslint-enable max-len */
|
|
'Front End Libraries Projects': isFrontEndLibsCert,
|
|
'Data Visualization Projects': is2018DataVisCert,
|
|
'API and Microservice Projects': isApisMicroservicesCert,
|
|
'Information Security and Quality Assurance Projects': isInfosecQaCert,
|
|
'Legacy Front End Projects': isFrontEndCert,
|
|
'Legacy Back End Projects': isBackEndCert,
|
|
'Legacy Data Visualization Projects': isDataVisCert
|
|
},
|
|
username
|
|
})
|
|
);
|
|
|
|
function mapDispatchToProps(dispatch) {
|
|
return bindActionCreators({
|
|
claimCert,
|
|
createError: createErrorObservable,
|
|
fetchChallenges,
|
|
hardGoTo,
|
|
updateUserBackend
|
|
}, dispatch);
|
|
}
|
|
|
|
const projectsTypes = PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
projectBlockName: PropTypes.string,
|
|
challenges: PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
dashedName: PropTypes.string,
|
|
id: PropTypes.string,
|
|
title: PropTypes.string
|
|
})
|
|
)
|
|
}),
|
|
);
|
|
|
|
const propTypes = {
|
|
allProjects: projectsTypes,
|
|
blockNameIsCertMap: PropTypes.objectOf(PropTypes.bool),
|
|
claimCert: PropTypes.func.isRequired,
|
|
createError: PropTypes.func.isRequired,
|
|
fetchChallenges: PropTypes.func.isRequired,
|
|
hardGoTo: PropTypes.func.isRequired,
|
|
legacyProjects: projectsTypes,
|
|
modernProjects: projectsTypes,
|
|
superBlock: PropTypes.string,
|
|
updateUserBackend: PropTypes.func.isRequired,
|
|
userProjects: PropTypes.objectOf(
|
|
PropTypes.objectOf(PropTypes.oneOfType(
|
|
[
|
|
PropTypes.string,
|
|
PropTypes.object
|
|
]
|
|
))
|
|
),
|
|
username: PropTypes.string
|
|
};
|
|
|
|
class CertificationSettings extends PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.buildProjectForms = this.buildProjectForms.bind(this);
|
|
this.handleSubmit = this.handleSubmit.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
const { modernProjects } = this.props;
|
|
if (!modernProjects.length) {
|
|
this.props.fetchChallenges();
|
|
}
|
|
}
|
|
|
|
buildProjectForms({
|
|
projectBlockName,
|
|
challenges,
|
|
superBlock
|
|
}) {
|
|
const {
|
|
blockNameIsCertMap,
|
|
claimCert,
|
|
hardGoTo,
|
|
userProjects,
|
|
username
|
|
} = this.props;
|
|
const isCertClaimed = blockNameIsCertMap[projectBlockName];
|
|
const challengeTitles = challenges
|
|
.map(challenge => challenge.title || 'Unknown Challenge');
|
|
if (superBlock === jsProjectSuperBlock) {
|
|
return (
|
|
<JSAlgoAndDSForm
|
|
challenges={ challengeTitles }
|
|
claimCert={ claimCert }
|
|
hardGoTo={ hardGoTo }
|
|
isCertClaimed={ isCertClaimed }
|
|
jsProjects={ userProjects[superBlock] }
|
|
key={ superBlock }
|
|
projectBlockName={ projectBlockName }
|
|
superBlock={ superBlock }
|
|
username={ username }
|
|
/>
|
|
);
|
|
}
|
|
const options = challengeTitles
|
|
.reduce((options, current) => {
|
|
options.types[current] = 'url';
|
|
return options;
|
|
}, { types: {} });
|
|
|
|
options.types.id = 'hidden';
|
|
options.placeholder = false;
|
|
|
|
const userValues = userProjects[superBlock] || {};
|
|
|
|
if (!userValues.id) {
|
|
userValues.id = superBlock;
|
|
}
|
|
|
|
const initialValues = challengeTitles
|
|
.reduce((accu, current) => ({
|
|
...accu,
|
|
[current]: ''
|
|
}), {});
|
|
|
|
const completedProjects = _.values(userValues)
|
|
.filter(Boolean)
|
|
.filter(_.isString)
|
|
// minus 1 to account for the id
|
|
.length - 1;
|
|
|
|
const fullForm = completedProjects === challengeTitles.length;
|
|
return (
|
|
<FullWidthRow key={superBlock}>
|
|
<h3 className='project-heading'>{ projectBlockName }</h3>
|
|
<Form
|
|
buttonText={ fullForm ? 'Claim Certificate' : 'Save Progress' }
|
|
enableSubmit={ fullForm }
|
|
formFields={ challengeTitles.concat([ 'id' ]) }
|
|
hideButton={isCertClaimed}
|
|
id={ superBlock }
|
|
initialValues={{
|
|
...initialValues,
|
|
...userValues
|
|
}}
|
|
options={ options }
|
|
submit={ this.handleSubmit }
|
|
/>
|
|
{
|
|
isCertClaimed ?
|
|
<Button
|
|
block={ true }
|
|
bsSize='lg'
|
|
bsStyle='primary'
|
|
href={ `/c/${username}/${superBlock}`}
|
|
>
|
|
Show Certificate
|
|
</Button> :
|
|
null
|
|
}
|
|
<hr />
|
|
</FullWidthRow>
|
|
);
|
|
}
|
|
|
|
handleSubmit(values) {
|
|
const { id } = values;
|
|
const { allProjects } = this.props;
|
|
let project = _.find(allProjects, { superBlock: id });
|
|
if (!project) {
|
|
// the submitted projects do not belong to current/legacy certificates
|
|
return this.props.createError(
|
|
new Error(
|
|
'Submitted projects do not belong to either current or ' +
|
|
'legacy certificates'
|
|
)
|
|
);
|
|
}
|
|
const valuesSaved = _.values(this.props.userProjects[id])
|
|
.filter(Boolean)
|
|
.filter(_.isString);
|
|
|
|
// minus 1 due to the form id being in values
|
|
const isProjectSectionComplete =
|
|
(valuesSaved.length - 1) === project.challenges.length;
|
|
|
|
if (isProjectSectionComplete) {
|
|
return this.props.claimCert(id);
|
|
}
|
|
const valuesToIds = project.challenges
|
|
.reduce((valuesMap, current) => {
|
|
const solution = values[current.title];
|
|
if (solution) {
|
|
return {
|
|
...valuesMap,
|
|
[current.id]: solution
|
|
};
|
|
}
|
|
return valuesMap;
|
|
}, {});
|
|
return this.props.updateUserBackend({
|
|
projects: {
|
|
[id]: valuesToIds
|
|
}
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
modernProjects,
|
|
legacyProjects
|
|
} = this.props;
|
|
if (!modernProjects.length) {
|
|
return null;
|
|
}
|
|
return (
|
|
<div>
|
|
<SectionHeader>
|
|
Certification Settings
|
|
</SectionHeader>
|
|
<FullWidthRow>
|
|
<p>
|
|
Add links to the live demos of your projects as you finish them.
|
|
Then, once you have added all 5 projects required for a certificate,
|
|
you can claim it.
|
|
</p>
|
|
</FullWidthRow>
|
|
{
|
|
modernProjects.map(this.buildProjectForms)
|
|
}
|
|
<SectionHeader>
|
|
Legacy Certificate Settings
|
|
</SectionHeader>
|
|
{
|
|
legacyProjects.map(this.buildProjectForms)
|
|
}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
CertificationSettings.displayName = 'CertificationSettings';
|
|
CertificationSettings.propTypes = propTypes;
|
|
|
|
export default connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps
|
|
)(CertificationSettings);
|