Files
freeCodeCamp/client/src/components/settings/Certification.js

420 lines
11 KiB
JavaScript
Raw Normal View History

2018-09-24 18:45:11 +01:00
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
2018-09-25 12:51:17 +01:00
import { find, first } from 'lodash';
2018-09-24 18:45:11 +01:00
import {
Table,
Button,
DropdownButton,
MenuItem,
Modal
} from '@freecodecamp/react-bootstrap';
import { Link, navigate } from 'gatsby';
2018-09-25 12:51:17 +01:00
import { createSelector } from 'reselect';
2018-09-24 18:45:11 +01:00
import { updateLegacyCertificate } from '../../redux/settings';
2019-03-19 13:20:30 +03:00
import { projectMap, legacyProjectMap } from '../../resources/certProjectMap';
2018-09-24 18:45:11 +01:00
import SectionHeader from './SectionHeader';
import SolutionViewer from './SolutionViewer';
2018-09-25 12:51:17 +01:00
import { FullWidthRow, Spacer } from '../helpers';
2019-03-20 16:46:50 +03:00
import { Form } from '../formHelpers';
2018-09-24 18:45:11 +01:00
import { maybeUrlRE } from '../../utils';
2018-09-25 12:51:17 +01:00
import './certification.css';
const mapDispatchToProps = dispatch =>
bindActionCreators({ updateLegacyCertificate }, dispatch);
2018-09-24 18:45:11 +01:00
const propTypes = {
completedChallenges: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
solution: PropTypes.string,
githubLink: PropTypes.string,
challengeType: PropTypes.number,
completedDate: PropTypes.number,
files: PropTypes.array
})
2018-09-25 12:51:17 +01:00
),
createFlashMessage: PropTypes.func.isRequired,
is2018DataVisCert: PropTypes.bool,
isApisMicroservicesCert: PropTypes.bool,
isBackEndCert: PropTypes.bool,
isDataVisCert: PropTypes.bool,
isFrontEndCert: PropTypes.bool,
isFrontEndLibsCert: PropTypes.bool,
isFullStackCert: PropTypes.bool,
isHonest: PropTypes.bool,
isInfosecQaCert: PropTypes.bool,
isJsAlgoDataStructCert: PropTypes.bool,
isRespWebDesignCert: PropTypes.bool,
updateLegacyCertificate: PropTypes.func.isRequired,
2018-09-25 12:51:17 +01:00
username: PropTypes.string,
verifyCert: PropTypes.func.isRequired
2018-09-24 18:45:11 +01:00
};
const certifications = Object.keys(projectMap);
2019-03-19 13:20:30 +03:00
const legacyCertifications = Object.keys(legacyProjectMap);
2018-09-25 12:51:17 +01:00
const isCertSelector = ({
is2018DataVisCert,
isApisMicroservicesCert,
isJsAlgoDataStructCert,
isBackEndCert,
isDataVisCert,
isFrontEndCert,
isInfosecQaCert,
isFrontEndLibsCert,
isFullStackCert,
isRespWebDesignCert
}) => ({
is2018DataVisCert,
isApisMicroservicesCert,
isJsAlgoDataStructCert,
isBackEndCert,
isDataVisCert,
isFrontEndCert,
isInfosecQaCert,
isFrontEndLibsCert,
isFullStackCert,
isRespWebDesignCert
});
const isCertMapSelector = createSelector(
isCertSelector,
({
is2018DataVisCert,
isApisMicroservicesCert,
isJsAlgoDataStructCert,
isInfosecQaCert,
isFrontEndLibsCert,
2019-03-19 13:20:30 +03:00
isRespWebDesignCert,
isDataVisCert,
isFrontEndCert,
isBackEndCert
2018-09-25 12:51:17 +01:00
}) => ({
'Responsive Web Design': isRespWebDesignCert,
'JavaScript Algorithms and Data Structures': isJsAlgoDataStructCert,
'Front End Libraries': isFrontEndLibsCert,
'Data Visualization': is2018DataVisCert,
"API's and Microservices": isApisMicroservicesCert,
2019-03-19 13:20:30 +03:00
'Information Security And Quality Assurance': isInfosecQaCert,
'Legacy Front End': isFrontEndCert,
'Legacy Data Visualization': isDataVisCert,
'Legacy Back End': isBackEndCert
2018-09-25 12:51:17 +01:00
})
);
2018-09-24 18:45:11 +01:00
const initialState = {
solutionViewer: {
projectTitle: '',
files: null,
solution: null,
isOpen: false
}
};
class CertificationSettings extends Component {
constructor(props) {
super(props);
this.state = { ...initialState };
2019-03-20 16:46:50 +03:00
this.handleSubmit = this.handleSubmit.bind(this);
2018-09-24 18:45:11 +01:00
}
createHandleLinkButtonClick = to => e => {
e.preventDefault();
return navigate(to);
};
2018-09-25 12:51:17 +01:00
2018-09-24 18:45:11 +01:00
handleSolutionModalHide = () => this.setState({ ...initialState });
2018-09-25 12:51:17 +01:00
getUserIsCertMap = () => isCertMapSelector(this.props);
2018-09-24 18:45:11 +01:00
getProjectSolution = (projectId, projectTitle) => {
const { completedChallenges } = this.props;
const completedProject = find(
completedChallenges,
({ id }) => projectId === id
);
if (!completedProject) {
return null;
}
const { solution, githubLink, files } = completedProject;
const onClickHandler = () =>
this.setState({
solutionViewer: {
projectTitle,
files,
solution,
isOpen: true
}
});
if (files && files.length) {
return (
<Button
2018-09-25 12:51:17 +01:00
block={true}
2018-09-24 18:45:11 +01:00
bsStyle='primary'
className='btn-invert'
onClick={onClickHandler}
>
2018-09-24 18:45:11 +01:00
Show Code
</Button>
);
}
if (githubLink) {
return (
2018-09-25 12:51:17 +01:00
<div className='solutions-dropdown'>
<DropdownButton
block={true}
2018-09-24 18:45:11 +01:00
bsStyle='primary'
2018-09-25 12:51:17 +01:00
className='btn-invert'
id={`dropdown-for-${projectId}`}
title='Show Solutions'
>
2018-09-25 12:51:17 +01:00
<MenuItem
bsStyle='primary'
href={solution}
rel='noopener noreferrer'
target='_blank'
>
2018-09-25 12:51:17 +01:00
Front End
</MenuItem>
<MenuItem
bsStyle='primary'
href={githubLink}
rel='noopener noreferrer'
target='_blank'
>
2018-09-25 12:51:17 +01:00
Back End
</MenuItem>
</DropdownButton>
</div>
2018-09-24 18:45:11 +01:00
);
}
if (maybeUrlRE.test(solution)) {
return (
<Button
2018-09-25 12:51:17 +01:00
block={true}
2018-09-24 18:45:11 +01:00
bsStyle='primary'
className='btn-invert'
href={solution}
rel='noopener noreferrer'
target='_blank'
>
2018-09-24 18:45:11 +01:00
Show Solution
</Button>
);
}
return (
2018-09-25 12:51:17 +01:00
<Button
block={true}
bsStyle='primary'
className='btn-invert'
onClick={onClickHandler}
>
2018-09-24 18:45:11 +01:00
Show Code
</Button>
);
};
renderCertifications = certName => (
<FullWidthRow key={certName}>
2018-09-25 12:51:17 +01:00
<Spacer />
2018-09-24 18:45:11 +01:00
<h3>{certName}</h3>
<Table>
<thead>
<tr>
<th>Project Name</th>
<th>Solution</th>
</tr>
</thead>
2018-09-25 12:51:17 +01:00
<tbody>
{this.renderProjectsFor(certName, this.getUserIsCertMap()[certName])}
</tbody>
2018-09-24 18:45:11 +01:00
</Table>
</FullWidthRow>
);
2018-09-25 12:51:17 +01:00
renderProjectsFor = (certName, isCert) => {
const { username, isHonest, createFlashMessage, verifyCert } = this.props;
const { superBlock } = first(projectMap[certName]);
const certLocation = `/certification/${username}/${superBlock}`;
const createClickHandler = superBlock => e => {
e.preventDefault();
if (isCert) {
return navigate(certLocation);
}
return isHonest
? verifyCert(superBlock)
: createFlashMessage({
type: 'info',
message:
'To claim a certification, you must first accept our academic ' +
2018-09-25 12:51:17 +01:00
'honesty policy'
});
};
return projectMap[certName]
.map(({ link, title, id }) => (
<tr className='project-row' key={id}>
<td className='project-title col-sm-8'>
<Link to={link}>{title}</Link>
</td>
<td className='project-solution col-sm-4'>
{this.getProjectSolution(id, title)}
</td>
</tr>
))
.concat([
<tr key={`cert-${superBlock}-button`}>
<td colSpan={2}>
<Button
block={true}
bsStyle='primary'
href={certLocation}
onClick={createClickHandler(superBlock)}
>
2018-09-25 12:51:17 +01:00
{isCert ? 'Show Certification' : 'Claim Certification'}
</Button>
</td>
</tr>
]);
};
2018-09-24 18:45:11 +01:00
2019-03-20 16:46:50 +03:00
// legacy projects rendering
handleSubmit(values) {
const { updateLegacyCertificate } = this.props;
updateLegacyCertificate(values);
2019-03-20 16:46:50 +03:00
}
renderLegacyCertifications = certName => {
const { username, isHonest, createFlashMessage, verifyCert } = this.props;
const { superBlock } = first(legacyProjectMap[certName]);
const certLocation = `/certification/${username}/${superBlock}`;
2019-03-20 16:46:50 +03:00
const challengeTitles = legacyProjectMap[certName].map(item => item.title);
const { completedChallenges } = this.props;
const isCertClaimed = this.getUserIsCertMap()[certName];
2019-03-20 16:46:50 +03:00
const initialObject = {};
let filledforms = 0;
2019-03-20 16:46:50 +03:00
legacyProjectMap[certName].forEach(element => {
let completedProject = find(completedChallenges, function(challenge) {
return challenge['id'] === element['id'];
});
if (!completedProject) {
initialObject[element.title] = '';
} else {
initialObject[element.title] = completedProject.solution;
filledforms++;
2019-03-20 16:46:50 +03:00
}
});
2019-03-22 11:02:00 +03:00
const options = challengeTitles.reduce(
(options, current) => {
options.types[current] = 'url';
return options;
},
{ types: {} }
);
const fullForm = filledforms === challengeTitles.length;
2019-03-19 13:20:30 +03:00
const createClickHandler = superBlock => e => {
e.preventDefault();
if (isCertClaimed) {
2019-03-19 13:20:30 +03:00
return navigate(certLocation);
}
return isHonest
? verifyCert(superBlock)
: createFlashMessage({
type: 'info',
message:
'To claim a certification, you must first accept our academic ' +
'honesty policy'
});
};
2019-03-20 16:46:50 +03:00
const buttonStyle = {
marginBottom: '1.45rem'
};
return (
<FullWidthRow key={certName}>
<Spacer />
<h3>{certName}</h3>
<Form
buttonText={fullForm ? 'Claim Certification' : 'Save Progress'}
enableSubmit={fullForm}
formFields={challengeTitles}
hideButton={isCertClaimed}
id={certName}
initialValues={{
...initialObject
}}
2019-03-22 11:02:00 +03:00
options={options}
submit={this.handleSubmit}
/>
{isCertClaimed ? (
<div className={'col-xs-12'}>
2019-03-19 13:20:30 +03:00
<Button
bsSize='sm'
2019-03-19 13:20:30 +03:00
bsStyle='primary'
className={'col-xs-12'}
2019-03-19 13:20:30 +03:00
href={certLocation}
onClick={createClickHandler(superBlock)}
style={buttonStyle}
2019-03-19 13:20:30 +03:00
target='_blank'
>
Show Certification
</Button>
</div>
) : null}
</FullWidthRow>
2019-03-19 13:20:30 +03:00
);
};
2018-09-24 18:45:11 +01:00
render() {
const {
solutionViewer: { files, solution, isOpen, projectTitle }
} = this.state;
return (
<section id='certifcation-settings'>
<SectionHeader>Certifications</SectionHeader>
{certifications.map(this.renderCertifications)}
2019-03-19 13:20:30 +03:00
<SectionHeader>Legacy Certifications</SectionHeader>
{legacyCertifications.map(this.renderLegacyCertifications)}
2018-09-24 18:45:11 +01:00
{isOpen ? (
<Modal
aria-labelledby='solution-viewer-modal-title'
bsSize='large'
onHide={this.handleSolutionModalHide}
show={isOpen}
>
2018-09-24 18:45:11 +01:00
<Modal.Header className='this-one?' closeButton={true}>
<Modal.Title id='solution-viewer-modal-title'>
Solution for {projectTitle}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<SolutionViewer files={files} solution={solution} />
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleSolutionModalHide}>Close</Button>
</Modal.Footer>
</Modal>
) : null}
</section>
);
}
}
CertificationSettings.displayName = 'CertificationSettings';
CertificationSettings.propTypes = propTypes;
export default connect(
null,
mapDispatchToProps
)(CertificationSettings);