feat(certs): Claim Certs
This commit is contained in:
@ -34,7 +34,7 @@ export default function bootCertificate(app) {
|
|||||||
const showCert = createShowCert(app);
|
const showCert = createShowCert(app);
|
||||||
const verifyCert = createVerifyCert(certTypeIds, app);
|
const verifyCert = createVerifyCert(certTypeIds, app);
|
||||||
|
|
||||||
api.post('/certificate/verify', ifNoUser401, ifNoSuperBlock404, verifyCert);
|
api.put('/certificate/verify', ifNoUser401, ifNoSuperBlock404, verifyCert);
|
||||||
api.get('/certificate/showCert/:username/:cert', showCert);
|
api.get('/certificate/showCert/:username/:cert', showCert);
|
||||||
|
|
||||||
app.use('/internal', api);
|
app.use('/internal', api);
|
||||||
@ -47,18 +47,18 @@ const noNameMessage = dedent`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const notCertifiedMessage = name => dedent`
|
const notCertifiedMessage = name => dedent`
|
||||||
it looks like you have not completed the necessary steps.
|
It looks like you have not completed the necessary steps.
|
||||||
Please complete the required challenges to claim the
|
Please complete the required projects to claim the
|
||||||
${name}
|
${name} Certification
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const alreadyClaimedMessage = name => dedent`
|
const alreadyClaimedMessage = name => dedent`
|
||||||
It looks like you already have claimed the ${name}
|
It looks like you already have claimed the ${name} Certification
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const successMessage = (username, name) => dedent`
|
const successMessage = (username, name) => dedent`
|
||||||
@${username}, you have successfully claimed
|
@${username}, you have successfully claimed
|
||||||
the ${name}!
|
the ${name} Certification!
|
||||||
Congratulations on behalf of the freeCodeCamp.org team!
|
Congratulations on behalf of the freeCodeCamp.org team!
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -194,6 +194,34 @@ function sendCertifiedEmail(
|
|||||||
return send$(notifyUser).map(() => true);
|
return send$(notifyUser).map(() => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUserIsCertMap(user) {
|
||||||
|
const {
|
||||||
|
isRespWebDesignCert = false,
|
||||||
|
isJsAlgoDataStructCert = false,
|
||||||
|
isFrontEndLibsCert = false,
|
||||||
|
is2018DataVisCert = false,
|
||||||
|
isApisMicroservicesCert = false,
|
||||||
|
isInfosecQaCert = false,
|
||||||
|
isFrontEndCert = false,
|
||||||
|
isBackEndCert = false,
|
||||||
|
isDataVisCert = false,
|
||||||
|
isFullStackCert = false
|
||||||
|
} = user;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRespWebDesignCert,
|
||||||
|
isJsAlgoDataStructCert,
|
||||||
|
isFrontEndLibsCert,
|
||||||
|
is2018DataVisCert,
|
||||||
|
isApisMicroservicesCert,
|
||||||
|
isInfosecQaCert,
|
||||||
|
isFrontEndCert,
|
||||||
|
isBackEndCert,
|
||||||
|
isDataVisCert,
|
||||||
|
isFullStackCert
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createVerifyCert(certTypeIds, app) {
|
function createVerifyCert(certTypeIds, app) {
|
||||||
const { Email } = app.models;
|
const { Email } = app.models;
|
||||||
return function verifyCert(req, res, next) {
|
return function verifyCert(req, res, next) {
|
||||||
@ -264,8 +292,11 @@ function createVerifyCert(certTypeIds, app) {
|
|||||||
})
|
})
|
||||||
.subscribe(message => {
|
.subscribe(message => {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message,
|
response: {
|
||||||
success: message.includes('Congratulations')
|
type: message.includes('Congratulations') ? 'success' : 'info',
|
||||||
|
message
|
||||||
|
},
|
||||||
|
isCertMap: getUserIsCertMap(user)
|
||||||
});
|
});
|
||||||
}, next);
|
}, next);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,8 @@ import { Grid, Button } from '@freecodecamp/react-bootstrap';
|
|||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { signInLoadingSelector, userSelector } from '../redux';
|
import { signInLoadingSelector, userSelector } from '../redux';
|
||||||
import { submitNewAbout, updateUserFlag } from '../redux/settings';
|
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
|
||||||
|
import { createFlashMessage } from '../components/Flash/redux';
|
||||||
|
|
||||||
import Layout from '../components/Layout';
|
import Layout from '../components/Layout';
|
||||||
import Spacer from '../components/helpers/Spacer';
|
import Spacer from '../components/helpers/Spacer';
|
||||||
@ -22,6 +23,7 @@ import Honesty from '../components/settings/Honesty';
|
|||||||
import Certification from '../components/settings/Certification';
|
import Certification from '../components/settings/Certification';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
createFlashMessage: PropTypes.func.isRequired,
|
||||||
showLoading: PropTypes.bool,
|
showLoading: PropTypes.bool,
|
||||||
submitNewAbout: PropTypes.func.isRequired,
|
submitNewAbout: PropTypes.func.isRequired,
|
||||||
toggleNightMode: PropTypes.func.isRequired,
|
toggleNightMode: PropTypes.func.isRequired,
|
||||||
@ -74,7 +76,8 @@ const propTypes = {
|
|||||||
twitter: PropTypes.string,
|
twitter: PropTypes.string,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
website: PropTypes.string
|
website: PropTypes.string
|
||||||
})
|
}),
|
||||||
|
verifyCert: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
@ -89,18 +92,21 @@ const mapStateToProps = createSelector(
|
|||||||
const mapDispatchToProps = dispatch =>
|
const mapDispatchToProps = dispatch =>
|
||||||
bindActionCreators(
|
bindActionCreators(
|
||||||
{
|
{
|
||||||
|
createFlashMessage,
|
||||||
submitNewAbout,
|
submitNewAbout,
|
||||||
toggleNightMode: theme => updateUserFlag({ theme }),
|
toggleNightMode: theme => updateUserFlag({ theme }),
|
||||||
updateInternetSettings: updateUserFlag,
|
updateInternetSettings: updateUserFlag,
|
||||||
updateIsHonest: updateUserFlag,
|
updateIsHonest: updateUserFlag,
|
||||||
updatePortfolio: updateUserFlag,
|
updatePortfolio: updateUserFlag,
|
||||||
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail })
|
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail }),
|
||||||
|
verifyCert
|
||||||
},
|
},
|
||||||
dispatch
|
dispatch
|
||||||
);
|
);
|
||||||
|
|
||||||
function ShowSettings(props) {
|
function ShowSettings(props) {
|
||||||
const {
|
const {
|
||||||
|
createFlashMessage,
|
||||||
submitNewAbout,
|
submitNewAbout,
|
||||||
toggleNightMode,
|
toggleNightMode,
|
||||||
user: {
|
user: {
|
||||||
@ -115,6 +121,7 @@ function ShowSettings(props) {
|
|||||||
isInfosecQaCert,
|
isInfosecQaCert,
|
||||||
isFrontEndLibsCert,
|
isFrontEndLibsCert,
|
||||||
isFullStackCert,
|
isFullStackCert,
|
||||||
|
isRespWebDesignCert,
|
||||||
isEmailVerified,
|
isEmailVerified,
|
||||||
isHonest,
|
isHonest,
|
||||||
sendQuincyEmail,
|
sendQuincyEmail,
|
||||||
@ -135,7 +142,8 @@ function ShowSettings(props) {
|
|||||||
updateQuincyEmail,
|
updateQuincyEmail,
|
||||||
updateInternetSettings,
|
updateInternetSettings,
|
||||||
updatePortfolio,
|
updatePortfolio,
|
||||||
updateIsHonest
|
updateIsHonest,
|
||||||
|
verifyCert
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
@ -212,6 +220,7 @@ function ShowSettings(props) {
|
|||||||
<Spacer />
|
<Spacer />
|
||||||
<Certification
|
<Certification
|
||||||
completedChallenges={completedChallenges}
|
completedChallenges={completedChallenges}
|
||||||
|
createFlashMessage={createFlashMessage}
|
||||||
is2018DataVisCert={is2018DataVisCert}
|
is2018DataVisCert={is2018DataVisCert}
|
||||||
isApisMicroservicesCert={isApisMicroservicesCert}
|
isApisMicroservicesCert={isApisMicroservicesCert}
|
||||||
isBackEndCert={isBackEndCert}
|
isBackEndCert={isBackEndCert}
|
||||||
@ -219,8 +228,12 @@ function ShowSettings(props) {
|
|||||||
isFrontEndCert={isFrontEndCert}
|
isFrontEndCert={isFrontEndCert}
|
||||||
isFrontEndLibsCert={isFrontEndLibsCert}
|
isFrontEndLibsCert={isFrontEndLibsCert}
|
||||||
isFullStackCert={isFullStackCert}
|
isFullStackCert={isFullStackCert}
|
||||||
|
isHonest={isHonest}
|
||||||
isInfosecQaCert={isInfosecQaCert}
|
isInfosecQaCert={isInfosecQaCert}
|
||||||
isJsAlgoDataStructCert={isJsAlgoDataStructCert}
|
isJsAlgoDataStructCert={isJsAlgoDataStructCert}
|
||||||
|
isRespWebDesignCert={isRespWebDesignCert}
|
||||||
|
username={username}
|
||||||
|
verifyCert={verifyCert}
|
||||||
/>
|
/>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{/* <DangerZone /> */}
|
{/* <DangerZone /> */}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { find } from 'lodash';
|
import { find, first } from 'lodash';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
@ -9,14 +9,17 @@ import {
|
|||||||
Modal
|
Modal
|
||||||
} from '@freecodecamp/react-bootstrap';
|
} from '@freecodecamp/react-bootstrap';
|
||||||
import { Link, navigate } from 'gatsby';
|
import { Link, navigate } from 'gatsby';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { projectMap } from '../../resources/certProjectMap';
|
import { projectMap } from '../../resources/certProjectMap';
|
||||||
|
|
||||||
import SectionHeader from './SectionHeader';
|
import SectionHeader from './SectionHeader';
|
||||||
import SolutionViewer from './SolutionViewer';
|
import SolutionViewer from './SolutionViewer';
|
||||||
import { FullWidthRow } from '../helpers';
|
import { FullWidthRow, Spacer } from '../helpers';
|
||||||
import { maybeUrlRE } from '../../utils';
|
import { maybeUrlRE } from '../../utils';
|
||||||
|
|
||||||
|
import './certification.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
completedChallenges: PropTypes.arrayOf(
|
completedChallenges: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
@ -27,10 +30,71 @@ const propTypes = {
|
|||||||
completedDate: PropTypes.number,
|
completedDate: PropTypes.number,
|
||||||
files: PropTypes.array
|
files: PropTypes.array
|
||||||
})
|
})
|
||||||
)
|
),
|
||||||
|
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,
|
||||||
|
username: PropTypes.string,
|
||||||
|
verifyCert: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const certifications = Object.keys(projectMap);
|
const certifications = Object.keys(projectMap);
|
||||||
|
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,
|
||||||
|
isBackEndCert,
|
||||||
|
isDataVisCert,
|
||||||
|
isFrontEndCert,
|
||||||
|
isInfosecQaCert,
|
||||||
|
isFrontEndLibsCert,
|
||||||
|
isFullStackCert,
|
||||||
|
isRespWebDesignCert
|
||||||
|
}) => ({
|
||||||
|
'Responsive Web Design': isRespWebDesignCert,
|
||||||
|
'JavaScript Algorithms and Data Structures': isJsAlgoDataStructCert,
|
||||||
|
'Front End Libraries': isFrontEndLibsCert,
|
||||||
|
'Data Visualization': is2018DataVisCert,
|
||||||
|
"API's and Microservices": isApisMicroservicesCert,
|
||||||
|
'Information Security And Quality Assurance': isInfosecQaCert
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
solutionViewer: {
|
solutionViewer: {
|
||||||
projectTitle: '',
|
projectTitle: '',
|
||||||
@ -51,8 +115,11 @@ class CertificationSettings extends Component {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return navigate(to);
|
return navigate(to);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSolutionModalHide = () => this.setState({ ...initialState });
|
handleSolutionModalHide = () => this.setState({ ...initialState });
|
||||||
|
|
||||||
|
getUserIsCertMap = () => isCertMapSelector(this.props);
|
||||||
|
|
||||||
getProjectSolution = (projectId, projectTitle) => {
|
getProjectSolution = (projectId, projectTitle) => {
|
||||||
const { completedChallenges } = this.props;
|
const { completedChallenges } = this.props;
|
||||||
const completedProject = find(
|
const completedProject = find(
|
||||||
@ -75,6 +142,7 @@ class CertificationSettings extends Component {
|
|||||||
if (files && files.length) {
|
if (files && files.length) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
block={true}
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
className='btn-invert'
|
className='btn-invert'
|
||||||
onClick={onClickHandler}
|
onClick={onClickHandler}
|
||||||
@ -85,34 +153,38 @@ class CertificationSettings extends Component {
|
|||||||
}
|
}
|
||||||
if (githubLink) {
|
if (githubLink) {
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<div className='solutions-dropdown'>
|
||||||
bsStyle='primary'
|
<DropdownButton
|
||||||
className='btn-invert'
|
block={true}
|
||||||
id={`dropdown-for-${projectId}`}
|
|
||||||
title='Show Solutions'
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
href={solution}
|
className='btn-invert'
|
||||||
rel='noopener noreferrer'
|
id={`dropdown-for-${projectId}`}
|
||||||
target='_blank'
|
title='Show Solutions'
|
||||||
>
|
>
|
||||||
Front End
|
<MenuItem
|
||||||
</MenuItem>
|
bsStyle='primary'
|
||||||
<MenuItem
|
href={solution}
|
||||||
bsStyle='primary'
|
rel='noopener noreferrer'
|
||||||
href={githubLink}
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
>
|
||||||
target='_blank'
|
Front End
|
||||||
>
|
</MenuItem>
|
||||||
Back End
|
<MenuItem
|
||||||
</MenuItem>
|
bsStyle='primary'
|
||||||
</DropdownButton>
|
href={githubLink}
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
Back End
|
||||||
|
</MenuItem>
|
||||||
|
</DropdownButton>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (maybeUrlRE.test(solution)) {
|
if (maybeUrlRE.test(solution)) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
block={true}
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
className='btn-invert'
|
className='btn-invert'
|
||||||
href={solution}
|
href={solution}
|
||||||
@ -124,7 +196,12 @@ class CertificationSettings extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Button bsStyle='primary' className='btn-invert' onClick={onClickHandler}>
|
<Button
|
||||||
|
block={true}
|
||||||
|
bsStyle='primary'
|
||||||
|
className='btn-invert'
|
||||||
|
onClick={onClickHandler}
|
||||||
|
>
|
||||||
Show Code
|
Show Code
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -132,6 +209,7 @@ class CertificationSettings extends Component {
|
|||||||
|
|
||||||
renderCertifications = certName => (
|
renderCertifications = certName => (
|
||||||
<FullWidthRow key={certName}>
|
<FullWidthRow key={certName}>
|
||||||
|
<Spacer />
|
||||||
<h3>{certName}</h3>
|
<h3>{certName}</h3>
|
||||||
<Table>
|
<Table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -140,22 +218,57 @@ class CertificationSettings extends Component {
|
|||||||
<th>Solution</th>
|
<th>Solution</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{this.renderProjectsFor(certName)}</tbody>
|
<tbody>
|
||||||
|
{this.renderProjectsFor(certName, this.getUserIsCertMap()[certName])}
|
||||||
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</FullWidthRow>
|
</FullWidthRow>
|
||||||
);
|
);
|
||||||
|
|
||||||
renderProjectsFor = certName =>
|
renderProjectsFor = (certName, isCert) => {
|
||||||
projectMap[certName].map(({ link, title, id }) => (
|
const { username, isHonest, createFlashMessage, verifyCert } = this.props;
|
||||||
<tr className='project-row' key={id}>
|
const { superBlock } = first(projectMap[certName]);
|
||||||
<td className='project-title col-sm-8'>
|
const certLocation = `/certification/${username}/${superBlock}`;
|
||||||
<Link to={link}>{title}</Link>
|
const createClickHandler = superBlock => e => {
|
||||||
</td>
|
e.preventDefault();
|
||||||
<td className='project-solution col-sm-4'>
|
if (isCert) {
|
||||||
{this.getProjectSolution(id, title)}
|
return navigate(certLocation);
|
||||||
</td>
|
}
|
||||||
</tr>
|
return isHonest
|
||||||
));
|
? verifyCert(superBlock)
|
||||||
|
: createFlashMessage({
|
||||||
|
type: 'info',
|
||||||
|
message:
|
||||||
|
'To claim a certification, you must first accept our acedemic ' +
|
||||||
|
'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)}
|
||||||
|
>
|
||||||
|
{isCert ? 'Show Certification' : 'Claim Certification'}
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
#certifcation-settings .project-title {
|
#certifcation-settings .solutions-dropdown,
|
||||||
display: flex;
|
#certifcation-settings .solutions-dropdown .dropdown-menu,
|
||||||
|
#certifcation-settings .solutions-dropdown .dropdown {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#certifcation-settings .project-solution {
|
#certifcation-settings tr {
|
||||||
display: flex;
|
height: 57px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#certifcation-settings .project-row {
|
#certifcation-settings .project-title > a {
|
||||||
display: flex;
|
line-height: 40px;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -174,6 +174,8 @@ export const reducer = handleActions(
|
|||||||
[settingsTypes.updateMyEmailComplete]: (state, { payload }) =>
|
[settingsTypes.updateMyEmailComplete]: (state, { payload }) =>
|
||||||
payload ? spreadThePayloadOnUser(state, payload) : state,
|
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||||
[settingsTypes.updateUserFlagComplete]: (state, { payload }) =>
|
[settingsTypes.updateUserFlagComplete]: (state, { payload }) =>
|
||||||
|
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||||
|
[settingsTypes.verifyCertComplete]: (state, { payload }) =>
|
||||||
payload ? spreadThePayloadOnUser(state, payload) : state
|
payload ? spreadThePayloadOnUser(state, payload) : state
|
||||||
},
|
},
|
||||||
initialState
|
initialState
|
||||||
|
@ -27,7 +27,8 @@ export const types = createTypes(
|
|||||||
...createAsyncTypes('submitNewUsername'),
|
...createAsyncTypes('submitNewUsername'),
|
||||||
...createAsyncTypes('updateMyEmail'),
|
...createAsyncTypes('updateMyEmail'),
|
||||||
...createAsyncTypes('updateUserFlag'),
|
...createAsyncTypes('updateUserFlag'),
|
||||||
...createAsyncTypes('submitProfileUI')
|
...createAsyncTypes('submitProfileUI'),
|
||||||
|
...createAsyncTypes('verifyCert')
|
||||||
],
|
],
|
||||||
ns
|
ns
|
||||||
);
|
);
|
||||||
@ -80,6 +81,13 @@ export const validateUsernameComplete = createAction(
|
|||||||
);
|
);
|
||||||
export const validateUsernameError = createAction(types.validateUsernameError);
|
export const validateUsernameError = createAction(types.validateUsernameError);
|
||||||
|
|
||||||
|
export const verifyCert = createAction(types.verifyCert);
|
||||||
|
export const verifyCertComplete = createAction(
|
||||||
|
types.verifyCertComplete,
|
||||||
|
checkForSuccessPayload
|
||||||
|
);
|
||||||
|
export const verifyCertError = createAction(types.verifyCertError);
|
||||||
|
|
||||||
export const usernameValidationSelector = state => state[ns].usernameValidation;
|
export const usernameValidationSelector = state => state[ns].usernameValidation;
|
||||||
|
|
||||||
export const reducer = handleActions(
|
export const reducer = handleActions(
|
||||||
|
@ -11,14 +11,17 @@ import {
|
|||||||
submitNewUsernameComplete,
|
submitNewUsernameComplete,
|
||||||
submitNewUsernameError,
|
submitNewUsernameError,
|
||||||
submitProfileUIComplete,
|
submitProfileUIComplete,
|
||||||
submitProfileUIError
|
submitProfileUIError,
|
||||||
|
verifyCertComplete,
|
||||||
|
verifyCertError
|
||||||
} from './';
|
} from './';
|
||||||
import {
|
import {
|
||||||
getUsernameExists,
|
getUsernameExists,
|
||||||
putUpdateMyAbout,
|
putUpdateMyAbout,
|
||||||
putUpdateMyProfileUI,
|
putUpdateMyProfileUI,
|
||||||
putUpdateMyUsername,
|
putUpdateMyUsername,
|
||||||
putUpdateUserFlag
|
putUpdateUserFlag,
|
||||||
|
putVerifyCert
|
||||||
} from '../../utils/ajax';
|
} from '../../utils/ajax';
|
||||||
import { createFlashMessage } from '../../components/Flash/redux';
|
import { createFlashMessage } from '../../components/Flash/redux';
|
||||||
|
|
||||||
@ -74,12 +77,25 @@ function* validateUsernameSaga({ payload }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* verifyCertificationSaga({ payload }) {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { response, isCertMap }
|
||||||
|
} = yield call(putVerifyCert, payload);
|
||||||
|
yield put(verifyCertComplete({ ...response, payload: isCertMap }));
|
||||||
|
yield put(createFlashMessage(response));
|
||||||
|
} catch (e) {
|
||||||
|
yield put(verifyCertError(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createSettingsSagas(types) {
|
export function createSettingsSagas(types) {
|
||||||
return [
|
return [
|
||||||
takeEvery(types.updateUserFlag, updateUserFlagSaga),
|
takeEvery(types.updateUserFlag, updateUserFlagSaga),
|
||||||
takeLatest(types.submitNewAbout, submitNewAboutSaga),
|
takeLatest(types.submitNewAbout, submitNewAboutSaga),
|
||||||
takeLatest(types.submitNewUsername, submitNewUsernameSaga),
|
takeLatest(types.submitNewUsername, submitNewUsernameSaga),
|
||||||
takeLatest(types.validateUsername, validateUsernameSaga),
|
takeLatest(types.validateUsername, validateUsernameSaga),
|
||||||
takeLatest(types.submitProfileUI, sumbitProfileUISaga)
|
takeLatest(types.submitProfileUI, sumbitProfileUISaga),
|
||||||
|
takeEvery(types.verifyCert, verifyCertificationSaga)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -16,162 +16,192 @@ export const projectMap = {
|
|||||||
{
|
{
|
||||||
id: 'bd7158d8c442eddfaeb5bd18',
|
id: 'bd7158d8c442eddfaeb5bd18',
|
||||||
title: 'Build a Tribute Page',
|
title: 'Build a Tribute Page',
|
||||||
link: `${responsiveWebBase}/build-a-tribute-page`
|
link: `${responsiveWebBase}/build-a-tribute-page`,
|
||||||
|
superBlock: 'responsive-web-design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d78af367417b2b2512b03',
|
id: '587d78af367417b2b2512b03',
|
||||||
title: 'Build a Survey Form',
|
title: 'Build a Survey Form',
|
||||||
link: `${responsiveWebBase}/build-a-survey-form`
|
link: `${responsiveWebBase}/build-a-survey-form`,
|
||||||
|
superBlock: 'responsive-web-design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d78af367417b2b2512b04',
|
id: '587d78af367417b2b2512b04',
|
||||||
title: 'Build a Product Landing Page',
|
title: 'Build a Product Landing Page',
|
||||||
link: `${responsiveWebBase}/build-a-product-landing-page`
|
link: `${responsiveWebBase}/build-a-product-landing-page`,
|
||||||
|
superBlock: 'responsive-web-design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d78b0367417b2b2512b05',
|
id: '587d78b0367417b2b2512b05',
|
||||||
title: 'Build a Technical Documentation Page',
|
title: 'Build a Technical Documentation Page',
|
||||||
link: `${responsiveWebBase}/build-a-technical-documentation-page`
|
link: `${responsiveWebBase}/build-a-technical-documentation-page`,
|
||||||
|
superBlock: 'responsive-web-design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c242eddfaeb5bd13',
|
id: 'bd7158d8c242eddfaeb5bd13',
|
||||||
title: 'Build a Personal Portfolio Webpage',
|
title: 'Build a Personal Portfolio Webpage',
|
||||||
link: `${responsiveWebBase}/build-a-personal-portfolio-webpage`
|
link: `${responsiveWebBase}/build-a-personal-portfolio-webpage`,
|
||||||
|
superBlock: 'responsive-web-design'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'JavaScript Algorithms and Data Structures': [
|
'JavaScript Algorithms and Data Structures': [
|
||||||
{
|
{
|
||||||
id: 'aaa48de84e1ecc7c742e1124',
|
id: 'aaa48de84e1ecc7c742e1124',
|
||||||
title: 'Palindrome Checker',
|
title: 'Palindrome Checker',
|
||||||
link: `${jsAlgoBase}/palindrome-checker`
|
link: `${jsAlgoBase}/palindrome-checker`,
|
||||||
|
superBlock: 'javascript-algorithms-and-data-structures'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'a7f4d8f2483413a6ce226cac',
|
id: 'a7f4d8f2483413a6ce226cac',
|
||||||
title: 'Roman Numeral Converter',
|
title: 'Roman Numeral Converter',
|
||||||
link: `${jsAlgoBase}/roman-numeral-converter`
|
link: `${jsAlgoBase}/roman-numeral-converter`,
|
||||||
|
superBlock: 'javascript-algorithms-and-data-structures'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '56533eb9ac21ba0edf2244e2',
|
id: '56533eb9ac21ba0edf2244e2',
|
||||||
title: 'Caesars Cipher',
|
title: 'Caesars Cipher',
|
||||||
link: `${jsAlgoBase}/caesars-cipher`
|
link: `${jsAlgoBase}/caesars-cipher`,
|
||||||
|
superBlock: 'javascript-algorithms-and-data-structures'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'aff0395860f5d3034dc0bfc9',
|
id: 'aff0395860f5d3034dc0bfc9',
|
||||||
title: 'Telephone Number Validator',
|
title: 'Telephone Number Validator',
|
||||||
link: `${jsAlgoBase}/telephone-number-validator`
|
link: `${jsAlgoBase}/telephone-number-validator`,
|
||||||
|
superBlock: 'javascript-algorithms-and-data-structures'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'aa2e6f85cab2ab736c9a9b24',
|
id: 'aa2e6f85cab2ab736c9a9b24',
|
||||||
title: 'Cash Register',
|
title: 'Cash Register',
|
||||||
link: `${jsAlgoBase}/cash-register`
|
link: `${jsAlgoBase}/cash-register`,
|
||||||
|
superBlock: 'javascript-algorithms-and-data-structures'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'Front End Libraries': [
|
'Front End Libraries': [
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c442eddfaeb5bd13',
|
id: 'bd7158d8c442eddfaeb5bd13',
|
||||||
title: 'Build a Random Quote Machine',
|
title: 'Build a Random Quote Machine',
|
||||||
link: `${feLibsBase}/build-a-random-quote-machine`
|
link: `${feLibsBase}/build-a-random-quote-machine`,
|
||||||
|
superBlock: 'front-end-libraries'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7157d8c242eddfaeb5bd13',
|
id: 'bd7157d8c242eddfaeb5bd13',
|
||||||
title: 'Build a Markdown Previewer',
|
title: 'Build a Markdown Previewer',
|
||||||
link: `${feLibsBase}/build-a-markdown-previewer`
|
link: `${feLibsBase}/build-a-markdown-previewer`,
|
||||||
|
superBlock: 'front-end-libraries'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d7dbc367417b2b2512bae',
|
id: '587d7dbc367417b2b2512bae',
|
||||||
title: 'Build a Drum Machine',
|
title: 'Build a Drum Machine',
|
||||||
link: `${feLibsBase}/build-a-drum-machine`
|
link: `${feLibsBase}/build-a-drum-machine`,
|
||||||
|
superBlock: 'front-end-libraries'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c442eddfaeb5bd17',
|
id: 'bd7158d8c442eddfaeb5bd17',
|
||||||
title: 'Build a JavaScript Calculator',
|
title: 'Build a JavaScript Calculator',
|
||||||
link: `${feLibsBase}/build-a-javascript-calculator`
|
link: `${feLibsBase}/build-a-javascript-calculator`,
|
||||||
|
superBlock: 'front-end-libraries'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c442eddfaeb5bd0f',
|
id: 'bd7158d8c442eddfaeb5bd0f',
|
||||||
title: 'Build a Pomodoro Clock',
|
title: 'Build a Pomodoro Clock',
|
||||||
link: `${feLibsBase}/build-a-pomodoro-clock`
|
link: `${feLibsBase}/build-a-pomodoro-clock`,
|
||||||
|
superBlock: 'front-end-libraries'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'Data Visualization': [
|
'Data Visualization': [
|
||||||
{
|
{
|
||||||
id: 'bd7168d8c242eddfaeb5bd13',
|
id: 'bd7168d8c242eddfaeb5bd13',
|
||||||
title: 'Visualize Data with a Bar Chart',
|
title: 'Visualize Data with a Bar Chart',
|
||||||
link: `${dataVisBase}/visualize-data-with-a-bar-chart`
|
link: `${dataVisBase}/visualize-data-with-a-bar-chart`,
|
||||||
|
superBlock: 'data-visualization'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7178d8c242eddfaeb5bd13',
|
id: 'bd7178d8c242eddfaeb5bd13',
|
||||||
title: 'Visualize Data with a Scatterplot Graph',
|
title: 'Visualize Data with a Scatterplot Graph',
|
||||||
link: `${dataVisBase}/visualize-data-with-a-scatterplot-graph`
|
link: `${dataVisBase}/visualize-data-with-a-scatterplot-graph`,
|
||||||
|
superBlock: 'data-visualization'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7188d8c242eddfaeb5bd13',
|
id: 'bd7188d8c242eddfaeb5bd13',
|
||||||
title: 'Visualize Data with a Heat Map',
|
title: 'Visualize Data with a Heat Map',
|
||||||
link: `${dataVisBase}/visualize-data-with-a-heat-map`
|
link: `${dataVisBase}/visualize-data-with-a-heat-map`,
|
||||||
|
superBlock: 'data-visualization'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d7fa6367417b2b2512bbf',
|
id: '587d7fa6367417b2b2512bbf',
|
||||||
title: 'Visualize Data with a Choropleth Map',
|
title: 'Visualize Data with a Choropleth Map',
|
||||||
link: `${dataVisBase}/visualize-data-with-a-choropleth-map`
|
link: `${dataVisBase}/visualize-data-with-a-choropleth-map`,
|
||||||
|
superBlock: 'data-visualization'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d7fa6367417b2b2512bc0',
|
id: '587d7fa6367417b2b2512bc0',
|
||||||
title: 'Visualize Data with a Treemap Diagram',
|
title: 'Visualize Data with a Treemap Diagram',
|
||||||
link: `${dataVisBase}/visualize-data-with-a-treemap-diagram`
|
link: `${dataVisBase}/visualize-data-with-a-treemap-diagram`,
|
||||||
|
superBlock: 'data-visualization'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"API's and Microservices": [
|
"API's and Microservices": [
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c443edefaeb5bdef',
|
id: 'bd7158d8c443edefaeb5bdef',
|
||||||
title: 'Timestamp Microservice',
|
title: 'Timestamp Microservice',
|
||||||
link: `${apiMicroBase}/timestamp-microservice`
|
link: `${apiMicroBase}/timestamp-microservice`,
|
||||||
|
superBlock: 'apis-and-microservices'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c443edefaeb5bdff',
|
id: 'bd7158d8c443edefaeb5bdff',
|
||||||
title: 'Request Header Parser Microservice',
|
title: 'Request Header Parser Microservice',
|
||||||
link: `${apiMicroBase}/request-header-parser-microservice`
|
link: `${apiMicroBase}/request-header-parser-microservice`,
|
||||||
|
superBlock: 'apis-and-microservices'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c443edefaeb5bd0e',
|
id: 'bd7158d8c443edefaeb5bd0e',
|
||||||
title: 'URL Shortener Microservice',
|
title: 'URL Shortener Microservice',
|
||||||
link: `${apiMicroBase}/url-shortener-microservice`
|
link: `${apiMicroBase}/url-shortener-microservice`,
|
||||||
|
superBlock: 'apis-and-microservices'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5a8b073d06fa14fcfde687aa',
|
id: '5a8b073d06fa14fcfde687aa',
|
||||||
title: 'Exercise Tracker',
|
title: 'Exercise Tracker',
|
||||||
link: `${apiMicroBase}/exercise-tracker`
|
link: `${apiMicroBase}/exercise-tracker`,
|
||||||
|
superBlock: 'apis-and-microservices'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bd7158d8c443edefaeb5bd0f',
|
id: 'bd7158d8c443edefaeb5bd0f',
|
||||||
title: 'File Metadata Microservice',
|
title: 'File Metadata Microservice',
|
||||||
link: `${apiMicroBase}/file-metadata-microservice`
|
link: `${apiMicroBase}/file-metadata-microservice`,
|
||||||
|
superBlock: 'apis-and-microservices'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'Information Security And Quality Assurance': [
|
'Information Security And Quality Assurance': [
|
||||||
{
|
{
|
||||||
id: '587d8249367417b2b2512c41',
|
id: '587d8249367417b2b2512c41',
|
||||||
title: 'Metric-Imperial Converter',
|
title: 'Metric-Imperial Converter',
|
||||||
link: `${infoSecBase}/metric-imperial-converter`
|
link: `${infoSecBase}/metric-imperial-converter`,
|
||||||
|
superBlock: 'information-security-and-quality-assurance'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d8249367417b2b2512c42',
|
id: '587d8249367417b2b2512c42',
|
||||||
title: 'Issue Tracker',
|
title: 'Issue Tracker',
|
||||||
link: `${infoSecBase}/issue-tracker`
|
link: `${infoSecBase}/issue-tracker`,
|
||||||
|
superBlock: 'information-security-and-quality-assurance'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d824a367417b2b2512c43',
|
id: '587d824a367417b2b2512c43',
|
||||||
title: 'Personal Library',
|
title: 'Personal Library',
|
||||||
link: `${infoSecBase}/personal-library`
|
link: `${infoSecBase}/personal-library`,
|
||||||
|
superBlock: 'information-security-and-quality-assurance'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d824a367417b2b2512c44',
|
id: '587d824a367417b2b2512c44',
|
||||||
title: 'Stock Price Checker',
|
title: 'Stock Price Checker',
|
||||||
link: `${infoSecBase}/stock-price-checker`
|
link: `${infoSecBase}/stock-price-checker`,
|
||||||
|
superBlock: 'information-security-and-quality-assurance'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '587d824a367417b2b2512c45',
|
id: '587d824a367417b2b2512c45',
|
||||||
title: 'Anonymous Message Board',
|
title: 'Anonymous Message Board',
|
||||||
link: `${infoSecBase}/anonymous-message-board`
|
link: `${infoSecBase}/anonymous-message-board`,
|
||||||
|
superBlock: 'information-security-and-quality-assurance'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -64,4 +64,8 @@ export function putUserUpdateEmail(email) {
|
|||||||
return put('/update-my-email', { email });
|
return put('/update-my-email', { email });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function putVerifyCert(superBlock) {
|
||||||
|
return put('/certificate/verify', { superBlock });
|
||||||
|
}
|
||||||
|
|
||||||
/** DELETE **/
|
/** DELETE **/
|
||||||
|
Reference in New Issue
Block a user