feat(certs): Claim Certs
This commit is contained in:
@ -34,7 +34,7 @@ export default function bootCertificate(app) {
|
||||
const showCert = createShowCert(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);
|
||||
|
||||
app.use('/internal', api);
|
||||
@ -47,18 +47,18 @@ const noNameMessage = dedent`
|
||||
`;
|
||||
|
||||
const notCertifiedMessage = name => dedent`
|
||||
it looks like you have not completed the necessary steps.
|
||||
Please complete the required challenges to claim the
|
||||
${name}
|
||||
It looks like you have not completed the necessary steps.
|
||||
Please complete the required projects to claim the
|
||||
${name} Certification
|
||||
`;
|
||||
|
||||
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`
|
||||
@${username}, you have successfully claimed
|
||||
the ${name}!
|
||||
the ${name} Certification!
|
||||
Congratulations on behalf of the freeCodeCamp.org team!
|
||||
`;
|
||||
|
||||
@ -194,6 +194,34 @@ function sendCertifiedEmail(
|
||||
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) {
|
||||
const { Email } = app.models;
|
||||
return function verifyCert(req, res, next) {
|
||||
@ -264,8 +292,11 @@ function createVerifyCert(certTypeIds, app) {
|
||||
})
|
||||
.subscribe(message => {
|
||||
return res.status(200).json({
|
||||
message,
|
||||
success: message.includes('Congratulations')
|
||||
response: {
|
||||
type: message.includes('Congratulations') ? 'success' : 'info',
|
||||
message
|
||||
},
|
||||
isCertMap: getUserIsCertMap(user)
|
||||
});
|
||||
}, next);
|
||||
};
|
||||
|
@ -7,7 +7,8 @@ import { Grid, Button } from '@freecodecamp/react-bootstrap';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
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 Spacer from '../components/helpers/Spacer';
|
||||
@ -22,6 +23,7 @@ import Honesty from '../components/settings/Honesty';
|
||||
import Certification from '../components/settings/Certification';
|
||||
|
||||
const propTypes = {
|
||||
createFlashMessage: PropTypes.func.isRequired,
|
||||
showLoading: PropTypes.bool,
|
||||
submitNewAbout: PropTypes.func.isRequired,
|
||||
toggleNightMode: PropTypes.func.isRequired,
|
||||
@ -74,7 +76,8 @@ const propTypes = {
|
||||
twitter: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
website: PropTypes.string
|
||||
})
|
||||
}),
|
||||
verifyCert: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
@ -89,18 +92,21 @@ const mapStateToProps = createSelector(
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
createFlashMessage,
|
||||
submitNewAbout,
|
||||
toggleNightMode: theme => updateUserFlag({ theme }),
|
||||
updateInternetSettings: updateUserFlag,
|
||||
updateIsHonest: updateUserFlag,
|
||||
updatePortfolio: updateUserFlag,
|
||||
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail })
|
||||
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail }),
|
||||
verifyCert
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
function ShowSettings(props) {
|
||||
const {
|
||||
createFlashMessage,
|
||||
submitNewAbout,
|
||||
toggleNightMode,
|
||||
user: {
|
||||
@ -115,6 +121,7 @@ function ShowSettings(props) {
|
||||
isInfosecQaCert,
|
||||
isFrontEndLibsCert,
|
||||
isFullStackCert,
|
||||
isRespWebDesignCert,
|
||||
isEmailVerified,
|
||||
isHonest,
|
||||
sendQuincyEmail,
|
||||
@ -135,7 +142,8 @@ function ShowSettings(props) {
|
||||
updateQuincyEmail,
|
||||
updateInternetSettings,
|
||||
updatePortfolio,
|
||||
updateIsHonest
|
||||
updateIsHonest,
|
||||
verifyCert
|
||||
} = props;
|
||||
|
||||
if (showLoading) {
|
||||
@ -212,6 +220,7 @@ function ShowSettings(props) {
|
||||
<Spacer />
|
||||
<Certification
|
||||
completedChallenges={completedChallenges}
|
||||
createFlashMessage={createFlashMessage}
|
||||
is2018DataVisCert={is2018DataVisCert}
|
||||
isApisMicroservicesCert={isApisMicroservicesCert}
|
||||
isBackEndCert={isBackEndCert}
|
||||
@ -219,8 +228,12 @@ function ShowSettings(props) {
|
||||
isFrontEndCert={isFrontEndCert}
|
||||
isFrontEndLibsCert={isFrontEndLibsCert}
|
||||
isFullStackCert={isFullStackCert}
|
||||
isHonest={isHonest}
|
||||
isInfosecQaCert={isInfosecQaCert}
|
||||
isJsAlgoDataStructCert={isJsAlgoDataStructCert}
|
||||
isRespWebDesignCert={isRespWebDesignCert}
|
||||
username={username}
|
||||
verifyCert={verifyCert}
|
||||
/>
|
||||
<Spacer />
|
||||
{/* <DangerZone /> */}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { find } from 'lodash';
|
||||
import { find, first } from 'lodash';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
@ -9,14 +9,17 @@ import {
|
||||
Modal
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { Link, navigate } from 'gatsby';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { projectMap } from '../../resources/certProjectMap';
|
||||
|
||||
import SectionHeader from './SectionHeader';
|
||||
import SolutionViewer from './SolutionViewer';
|
||||
import { FullWidthRow } from '../helpers';
|
||||
import { FullWidthRow, Spacer } from '../helpers';
|
||||
import { maybeUrlRE } from '../../utils';
|
||||
|
||||
import './certification.css';
|
||||
|
||||
const propTypes = {
|
||||
completedChallenges: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
@ -27,10 +30,71 @@ const propTypes = {
|
||||
completedDate: PropTypes.number,
|
||||
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 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 = {
|
||||
solutionViewer: {
|
||||
projectTitle: '',
|
||||
@ -51,8 +115,11 @@ class CertificationSettings extends Component {
|
||||
e.preventDefault();
|
||||
return navigate(to);
|
||||
};
|
||||
|
||||
handleSolutionModalHide = () => this.setState({ ...initialState });
|
||||
|
||||
getUserIsCertMap = () => isCertMapSelector(this.props);
|
||||
|
||||
getProjectSolution = (projectId, projectTitle) => {
|
||||
const { completedChallenges } = this.props;
|
||||
const completedProject = find(
|
||||
@ -75,6 +142,7 @@ class CertificationSettings extends Component {
|
||||
if (files && files.length) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
onClick={onClickHandler}
|
||||
@ -85,7 +153,9 @@ class CertificationSettings extends Component {
|
||||
}
|
||||
if (githubLink) {
|
||||
return (
|
||||
<div className='solutions-dropdown'>
|
||||
<DropdownButton
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`dropdown-for-${projectId}`}
|
||||
@ -108,11 +178,13 @@ class CertificationSettings extends Component {
|
||||
Back End
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (maybeUrlRE.test(solution)) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
href={solution}
|
||||
@ -124,7 +196,12 @@ class CertificationSettings extends Component {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button bsStyle='primary' className='btn-invert' onClick={onClickHandler}>
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
Show Code
|
||||
</Button>
|
||||
);
|
||||
@ -132,6 +209,7 @@ class CertificationSettings extends Component {
|
||||
|
||||
renderCertifications = certName => (
|
||||
<FullWidthRow key={certName}>
|
||||
<Spacer />
|
||||
<h3>{certName}</h3>
|
||||
<Table>
|
||||
<thead>
|
||||
@ -140,13 +218,33 @@ class CertificationSettings extends Component {
|
||||
<th>Solution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{this.renderProjectsFor(certName)}</tbody>
|
||||
<tbody>
|
||||
{this.renderProjectsFor(certName, this.getUserIsCertMap()[certName])}
|
||||
</tbody>
|
||||
</Table>
|
||||
</FullWidthRow>
|
||||
);
|
||||
|
||||
renderProjectsFor = certName =>
|
||||
projectMap[certName].map(({ link, title, id }) => (
|
||||
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 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>
|
||||
@ -155,7 +253,22 @@ class CertificationSettings extends Component {
|
||||
{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() {
|
||||
const {
|
||||
|
@ -1,13 +1,13 @@
|
||||
#certifcation-settings .project-title {
|
||||
display: flex;
|
||||
#certifcation-settings .solutions-dropdown,
|
||||
#certifcation-settings .solutions-dropdown .dropdown-menu,
|
||||
#certifcation-settings .solutions-dropdown .dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#certifcation-settings .project-solution {
|
||||
display: flex;
|
||||
|
||||
#certifcation-settings tr {
|
||||
height: 57px;
|
||||
}
|
||||
|
||||
#certifcation-settings .project-row {
|
||||
display: flex;
|
||||
|
||||
#certifcation-settings .project-title > a {
|
||||
line-height: 40px;
|
||||
}
|
@ -174,6 +174,8 @@ export const reducer = handleActions(
|
||||
[settingsTypes.updateMyEmailComplete]: (state, { payload }) =>
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||
[settingsTypes.updateUserFlagComplete]: (state, { payload }) =>
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||
[settingsTypes.verifyCertComplete]: (state, { payload }) =>
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state
|
||||
},
|
||||
initialState
|
||||
|
@ -27,7 +27,8 @@ export const types = createTypes(
|
||||
...createAsyncTypes('submitNewUsername'),
|
||||
...createAsyncTypes('updateMyEmail'),
|
||||
...createAsyncTypes('updateUserFlag'),
|
||||
...createAsyncTypes('submitProfileUI')
|
||||
...createAsyncTypes('submitProfileUI'),
|
||||
...createAsyncTypes('verifyCert')
|
||||
],
|
||||
ns
|
||||
);
|
||||
@ -80,6 +81,13 @@ export const validateUsernameComplete = createAction(
|
||||
);
|
||||
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 reducer = handleActions(
|
||||
|
@ -11,14 +11,17 @@ import {
|
||||
submitNewUsernameComplete,
|
||||
submitNewUsernameError,
|
||||
submitProfileUIComplete,
|
||||
submitProfileUIError
|
||||
submitProfileUIError,
|
||||
verifyCertComplete,
|
||||
verifyCertError
|
||||
} from './';
|
||||
import {
|
||||
getUsernameExists,
|
||||
putUpdateMyAbout,
|
||||
putUpdateMyProfileUI,
|
||||
putUpdateMyUsername,
|
||||
putUpdateUserFlag
|
||||
putUpdateUserFlag,
|
||||
putVerifyCert
|
||||
} from '../../utils/ajax';
|
||||
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) {
|
||||
return [
|
||||
takeEvery(types.updateUserFlag, updateUserFlagSaga),
|
||||
takeLatest(types.submitNewAbout, submitNewAboutSaga),
|
||||
takeLatest(types.submitNewUsername, submitNewUsernameSaga),
|
||||
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',
|
||||
title: 'Build a Tribute Page',
|
||||
link: `${responsiveWebBase}/build-a-tribute-page`
|
||||
link: `${responsiveWebBase}/build-a-tribute-page`,
|
||||
superBlock: 'responsive-web-design'
|
||||
},
|
||||
{
|
||||
id: '587d78af367417b2b2512b03',
|
||||
title: 'Build a Survey Form',
|
||||
link: `${responsiveWebBase}/build-a-survey-form`
|
||||
link: `${responsiveWebBase}/build-a-survey-form`,
|
||||
superBlock: 'responsive-web-design'
|
||||
},
|
||||
{
|
||||
id: '587d78af367417b2b2512b04',
|
||||
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',
|
||||
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',
|
||||
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': [
|
||||
{
|
||||
id: 'aaa48de84e1ecc7c742e1124',
|
||||
title: 'Palindrome Checker',
|
||||
link: `${jsAlgoBase}/palindrome-checker`
|
||||
link: `${jsAlgoBase}/palindrome-checker`,
|
||||
superBlock: 'javascript-algorithms-and-data-structures'
|
||||
},
|
||||
{
|
||||
id: 'a7f4d8f2483413a6ce226cac',
|
||||
title: 'Roman Numeral Converter',
|
||||
link: `${jsAlgoBase}/roman-numeral-converter`
|
||||
link: `${jsAlgoBase}/roman-numeral-converter`,
|
||||
superBlock: 'javascript-algorithms-and-data-structures'
|
||||
},
|
||||
{
|
||||
id: '56533eb9ac21ba0edf2244e2',
|
||||
title: 'Caesars Cipher',
|
||||
link: `${jsAlgoBase}/caesars-cipher`
|
||||
link: `${jsAlgoBase}/caesars-cipher`,
|
||||
superBlock: 'javascript-algorithms-and-data-structures'
|
||||
},
|
||||
{
|
||||
id: 'aff0395860f5d3034dc0bfc9',
|
||||
title: 'Telephone Number Validator',
|
||||
link: `${jsAlgoBase}/telephone-number-validator`
|
||||
link: `${jsAlgoBase}/telephone-number-validator`,
|
||||
superBlock: 'javascript-algorithms-and-data-structures'
|
||||
},
|
||||
{
|
||||
id: 'aa2e6f85cab2ab736c9a9b24',
|
||||
title: 'Cash Register',
|
||||
link: `${jsAlgoBase}/cash-register`
|
||||
link: `${jsAlgoBase}/cash-register`,
|
||||
superBlock: 'javascript-algorithms-and-data-structures'
|
||||
}
|
||||
],
|
||||
'Front End Libraries': [
|
||||
{
|
||||
id: 'bd7158d8c442eddfaeb5bd13',
|
||||
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',
|
||||
title: 'Build a Markdown Previewer',
|
||||
link: `${feLibsBase}/build-a-markdown-previewer`
|
||||
link: `${feLibsBase}/build-a-markdown-previewer`,
|
||||
superBlock: 'front-end-libraries'
|
||||
},
|
||||
{
|
||||
id: '587d7dbc367417b2b2512bae',
|
||||
title: 'Build a Drum Machine',
|
||||
link: `${feLibsBase}/build-a-drum-machine`
|
||||
link: `${feLibsBase}/build-a-drum-machine`,
|
||||
superBlock: 'front-end-libraries'
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c442eddfaeb5bd17',
|
||||
title: 'Build a JavaScript Calculator',
|
||||
link: `${feLibsBase}/build-a-javascript-calculator`
|
||||
link: `${feLibsBase}/build-a-javascript-calculator`,
|
||||
superBlock: 'front-end-libraries'
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c442eddfaeb5bd0f',
|
||||
title: 'Build a Pomodoro Clock',
|
||||
link: `${feLibsBase}/build-a-pomodoro-clock`
|
||||
link: `${feLibsBase}/build-a-pomodoro-clock`,
|
||||
superBlock: 'front-end-libraries'
|
||||
}
|
||||
],
|
||||
'Data Visualization': [
|
||||
{
|
||||
id: 'bd7168d8c242eddfaeb5bd13',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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": [
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bdef',
|
||||
title: 'Timestamp Microservice',
|
||||
link: `${apiMicroBase}/timestamp-microservice`
|
||||
link: `${apiMicroBase}/timestamp-microservice`,
|
||||
superBlock: 'apis-and-microservices'
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bdff',
|
||||
title: 'Request Header Parser Microservice',
|
||||
link: `${apiMicroBase}/request-header-parser-microservice`
|
||||
link: `${apiMicroBase}/request-header-parser-microservice`,
|
||||
superBlock: 'apis-and-microservices'
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bd0e',
|
||||
title: 'URL Shortener Microservice',
|
||||
link: `${apiMicroBase}/url-shortener-microservice`
|
||||
link: `${apiMicroBase}/url-shortener-microservice`,
|
||||
superBlock: 'apis-and-microservices'
|
||||
},
|
||||
{
|
||||
id: '5a8b073d06fa14fcfde687aa',
|
||||
title: 'Exercise Tracker',
|
||||
link: `${apiMicroBase}/exercise-tracker`
|
||||
link: `${apiMicroBase}/exercise-tracker`,
|
||||
superBlock: 'apis-and-microservices'
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bd0f',
|
||||
title: 'File Metadata Microservice',
|
||||
link: `${apiMicroBase}/file-metadata-microservice`
|
||||
link: `${apiMicroBase}/file-metadata-microservice`,
|
||||
superBlock: 'apis-and-microservices'
|
||||
}
|
||||
],
|
||||
'Information Security And Quality Assurance': [
|
||||
{
|
||||
id: '587d8249367417b2b2512c41',
|
||||
title: 'Metric-Imperial Converter',
|
||||
link: `${infoSecBase}/metric-imperial-converter`
|
||||
link: `${infoSecBase}/metric-imperial-converter`,
|
||||
superBlock: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
id: '587d8249367417b2b2512c42',
|
||||
title: 'Issue Tracker',
|
||||
link: `${infoSecBase}/issue-tracker`
|
||||
link: `${infoSecBase}/issue-tracker`,
|
||||
superBlock: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
id: '587d824a367417b2b2512c43',
|
||||
title: 'Personal Library',
|
||||
link: `${infoSecBase}/personal-library`
|
||||
link: `${infoSecBase}/personal-library`,
|
||||
superBlock: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
id: '587d824a367417b2b2512c44',
|
||||
title: 'Stock Price Checker',
|
||||
link: `${infoSecBase}/stock-price-checker`
|
||||
link: `${infoSecBase}/stock-price-checker`,
|
||||
superBlock: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
id: '587d824a367417b2b2512c45',
|
||||
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 });
|
||||
}
|
||||
|
||||
export function putVerifyCert(superBlock) {
|
||||
return put('/certificate/verify', { superBlock });
|
||||
}
|
||||
|
||||
/** DELETE **/
|
||||
|
Reference in New Issue
Block a user