feat(legacy-certs): Claim legacy certificates from the settings page
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import { values as _values, isString, findIndex } from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -12,11 +12,17 @@ import JSAlgoAndDSForm from './JSAlgoAndDSForm.jsx';
|
||||
import SectionHeader from './SectionHeader.jsx';
|
||||
import { projectsSelector } from '../../../entities';
|
||||
import { claimCert, updateUserBackend } from '../redux';
|
||||
import { fetchChallenges, userSelector, hardGoTo } from '../../../redux';
|
||||
import {
|
||||
fetchChallenges,
|
||||
userSelector,
|
||||
hardGoTo,
|
||||
createErrorObservable
|
||||
} from '../../../redux';
|
||||
import {
|
||||
buildUserProjectsMap,
|
||||
jsProjectSuperBlock
|
||||
} from '../utils/buildUserProjectsMap';
|
||||
import legacyProjects from '../utils/legacyProjectData';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
@@ -30,12 +36,15 @@ const mapStateToProps = createSelector(
|
||||
isJsAlgoDataStructCert,
|
||||
isApisMicroservicesCert,
|
||||
isInfosecQaCert,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isDataVisCert,
|
||||
username
|
||||
},
|
||||
projects
|
||||
) => ({
|
||||
projects,
|
||||
userProjects: projects
|
||||
userProjects: projects.concat(legacyProjects)
|
||||
.map(block => buildUserProjectsMap(block, challengeMap))
|
||||
.reduce((projects, current) => ({
|
||||
...projects,
|
||||
@@ -49,7 +58,10 @@ const mapStateToProps = createSelector(
|
||||
'Front End Libraries Projects': isFrontEndLibsCert,
|
||||
'Data Visualization Projects': is2018DataVisCert,
|
||||
'API and Microservice Projects': isApisMicroservicesCert,
|
||||
'Information Security and Quality Assurance Projects': isInfosecQaCert
|
||||
'Information Security and Quality Assurance Projects': isInfosecQaCert,
|
||||
'Legacy Front End Projects': isFrontEndCert,
|
||||
'Legacy Back End Projects': isBackEndCert,
|
||||
'Legacy Data Visualization Projects': isDataVisCert
|
||||
},
|
||||
username
|
||||
})
|
||||
@@ -58,6 +70,7 @@ const mapStateToProps = createSelector(
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({
|
||||
claimCert,
|
||||
createError: createErrorObservable,
|
||||
fetchChallenges,
|
||||
hardGoTo,
|
||||
updateUserBackend
|
||||
@@ -67,6 +80,7 @@ function mapDispatchToProps(dispatch) {
|
||||
const propTypes = {
|
||||
blockNameIsCertMap: PropTypes.objectOf(PropTypes.bool),
|
||||
claimCert: PropTypes.func.isRequired,
|
||||
createError: PropTypes.func.isRequired,
|
||||
fetchChallenges: PropTypes.func.isRequired,
|
||||
hardGoTo: PropTypes.func.isRequired,
|
||||
projects: PropTypes.arrayOf(
|
||||
@@ -88,11 +102,15 @@ const propTypes = {
|
||||
username: PropTypes.string
|
||||
};
|
||||
|
||||
const compareSuperBlockWith = id => p => p.superBlock === id;
|
||||
|
||||
class CertificationSettings extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.buildProjectForms = this.buildProjectForms.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.isProjectSectionCompleted = this.isProjectSectionCompleted.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -102,23 +120,154 @@ class CertificationSettings extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
buildProjectForms({
|
||||
projectBlockName,
|
||||
challenges,
|
||||
superBlock
|
||||
}) {
|
||||
const {
|
||||
blockNameIsCertMap,
|
||||
claimCert,
|
||||
hardGoTo,
|
||||
userProjects,
|
||||
username
|
||||
} = this.props;
|
||||
const isCertClaimed = blockNameIsCertMap[projectBlockName];
|
||||
if (superBlock === jsProjectSuperBlock) {
|
||||
return (
|
||||
<JSAlgoAndDSForm
|
||||
challenges={ challenges }
|
||||
claimCert={ claimCert }
|
||||
hardGoTo={ hardGoTo }
|
||||
isCertClaimed={ isCertClaimed }
|
||||
jsProjects={ userProjects[superBlock] }
|
||||
key={ superBlock }
|
||||
projectBlockName={ projectBlockName }
|
||||
superBlock={ superBlock }
|
||||
username={ username }
|
||||
/>
|
||||
);
|
||||
}
|
||||
const options = challenges
|
||||
.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 = challenges
|
||||
.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 === challenges.length;
|
||||
return (
|
||||
<FullWidthRow key={superBlock}>
|
||||
<h3 className='project-heading'>{ projectBlockName }</h3>
|
||||
<Form
|
||||
buttonText={ fullForm ? 'Claim Certificate' : 'Save Progress' }
|
||||
enableSubmit={ fullForm }
|
||||
formFields={ challenges.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>
|
||||
);
|
||||
}
|
||||
|
||||
isProjectSectionCompleted(values) {
|
||||
const { id } = values;
|
||||
const { projects } = this.props;
|
||||
const whereSuperBlockIsId = compareSuperBlockWith(id);
|
||||
|
||||
let pIndex = findIndex(projects, whereSuperBlockIsId);
|
||||
let projectChallenges = [];
|
||||
|
||||
if (pIndex === -1) {
|
||||
// submitted projects might be in a legacy certificate
|
||||
pIndex = findIndex(legacyProjects, whereSuperBlockIsId);
|
||||
projectChallenges = legacyProjects[pIndex].challenges;
|
||||
if (pIndex === -1) {
|
||||
// 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'
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
projectChallenges = projects[pIndex].challenges;
|
||||
}
|
||||
const valuesSaved = _values(this.props.userProjects[id])
|
||||
.filter(Boolean)
|
||||
.filter(isString);
|
||||
// minus 1 due to the form id being in values
|
||||
return (valuesSaved.length - 1) === projectChallenges.length;
|
||||
}
|
||||
|
||||
handleSubmit(values) {
|
||||
const { id } = values;
|
||||
const fullForm = _.values(values)
|
||||
.filter(Boolean)
|
||||
.filter(_.isString)
|
||||
// 5 projects + 1 id prop
|
||||
.length === 6;
|
||||
const valuesSaved = _.values(this.props.userProjects[id])
|
||||
.filter(Boolean)
|
||||
.filter(_.isString)
|
||||
.length === 6;
|
||||
if (fullForm && valuesSaved) {
|
||||
if (this.isProjectSectionCompleted(values)) {
|
||||
return this.props.claimCert(id);
|
||||
}
|
||||
const { projects } = this.props;
|
||||
const pIndex = _.findIndex(projects, p => p.superBlock === id);
|
||||
values.nameToIdMap = projects[pIndex].challengeNameIdMap;
|
||||
const whereSuperBlockIsId = compareSuperBlockWith(id);
|
||||
|
||||
let pIndex = findIndex(projects, whereSuperBlockIsId);
|
||||
let projectNameIdMap = {};
|
||||
|
||||
if (pIndex === -1) {
|
||||
// submitted projects might be in a legacy certificate
|
||||
pIndex = findIndex(legacyProjects, whereSuperBlockIsId);
|
||||
projectNameIdMap = legacyProjects[pIndex].challengeNameIdMap;
|
||||
if (pIndex === -1) {
|
||||
// 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'
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
projectNameIdMap = projects[pIndex].challengeNameIdMap;
|
||||
}
|
||||
values.nameToIdMap = projectNameIdMap;
|
||||
return this.props.updateUserBackend({
|
||||
projects: {
|
||||
[id]: values
|
||||
@@ -128,12 +277,7 @@ class CertificationSettings extends PureComponent {
|
||||
|
||||
render() {
|
||||
const {
|
||||
blockNameIsCertMap,
|
||||
claimCert,
|
||||
hardGoTo,
|
||||
projects,
|
||||
userProjects,
|
||||
username
|
||||
projects
|
||||
} = this.props;
|
||||
if (!projects.length) {
|
||||
return null;
|
||||
@@ -150,88 +294,14 @@ class CertificationSettings extends PureComponent {
|
||||
you can claim it.
|
||||
</p>
|
||||
</FullWidthRow>
|
||||
{
|
||||
projects.map(({
|
||||
projectBlockName,
|
||||
challenges,
|
||||
superBlock
|
||||
}) => {
|
||||
const isCertClaimed = blockNameIsCertMap[projectBlockName];
|
||||
if (superBlock === jsProjectSuperBlock) {
|
||||
return (
|
||||
<JSAlgoAndDSForm
|
||||
challenges={ challenges }
|
||||
claimCert={ claimCert }
|
||||
hardGoTo={ hardGoTo }
|
||||
isCertClaimed={ isCertClaimed }
|
||||
jsProjects={ userProjects[superBlock] }
|
||||
key={ superBlock }
|
||||
projectBlockName={ projectBlockName }
|
||||
superBlock={ superBlock }
|
||||
username={ username }
|
||||
/>
|
||||
);
|
||||
}
|
||||
const options = challenges
|
||||
.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 = challenges
|
||||
.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 === challenges.length;
|
||||
return (
|
||||
<FullWidthRow key={superBlock}>
|
||||
<h3 className='project-heading'>{ projectBlockName }</h3>
|
||||
<Form
|
||||
buttonText={ fullForm ? 'Claim Certificate' : 'Save Progress' }
|
||||
enableSubmit={ fullForm }
|
||||
formFields={ challenges.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>
|
||||
);
|
||||
})
|
||||
{
|
||||
projects.map(this.buildProjectForms)
|
||||
}
|
||||
<SectionHeader>
|
||||
Legacy Certificate Settings
|
||||
</SectionHeader>
|
||||
{
|
||||
legacyProjects.map(this.buildProjectForms)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user