feat(certs): Convert certification views to gatsby

This commit is contained in:
Bouncey
2018-09-04 14:54:23 +01:00
committed by mrugesh mohapatra
parent dc00eb8555
commit 77a4452437
30 changed files with 627 additions and 917 deletions

View File

@ -1,23 +1,18 @@
import _ from 'lodash'; import _ from 'lodash';
import loopback from 'loopback'; import loopback from 'loopback';
import moment from 'moment-timezone';
import path from 'path'; import path from 'path';
import dedent from 'dedent'; import dedent from 'dedent';
import { Observable } from 'rx'; import { Observable } from 'rx';
import debug from 'debug'; import debug from 'debug';
import { isEmail } from 'validator'; import { isEmail } from 'validator';
import format from 'date-fns/format';
import { import { ifNoUser401 } from '../utils/middleware';
ifNoUser401
} from '../utils/middleware';
import { observeQuery } from '../utils/rx'; import { observeQuery } from '../utils/rx';
import { import {
legacyFrontEndChallengeId, legacyFrontEndChallengeId,
legacyBackEndChallengeId, legacyBackEndChallengeId,
legacyDataVisId, legacyDataVisId,
respWebDesignId, respWebDesignId,
frontEndLibsId, frontEndLibsId,
jsAlgoDataStructId, jsAlgoDataStructId,
@ -28,26 +23,83 @@ import {
} from '../utils/constantStrings.json'; } from '../utils/constantStrings.json';
import certTypes from '../utils/certTypes.json'; import certTypes from '../utils/certTypes.json';
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap'; import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
import { import { completeCommitment$ } from '../utils/commit';
completeCommitment$
} from '../utils/commit';
const log = debug('fcc:certification'); const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
__dirname, export default function bootCertificate(app) {
'..', const api = app.loopback.Router();
'views',
'emails', const certTypeIds = createCertTypeIds(app);
'certified.ejs' const showCert = createShowCert(app);
)); const verifyCert = createVerifyCert(certTypeIds, app);
api.post('/certificate/verify', ifNoUser401, ifNoSuperBlock404, verifyCert);
api.get('/certificate/showCert/:username/:cert', showCert);
app.use('/internal', api);
}
const noNameMessage = dedent`
We need your name so we can put it on your certification.
Add your name to your account settings and click the save button.
Then we can issue your certification.
`;
const notCertifiedMessage = name => dedent`
it looks like you have not completed the necessary steps.
Please complete the required challenges to claim the
${name}
`;
const alreadyClaimedMessage = name => dedent`
It looks like you already have claimed the ${name}
`;
const successMessage = (username, name) => dedent`
@${username}, you have successfully claimed
the ${name}!
Congratulations on behalf of the freeCodeCamp.org team!
`;
function ifNoSuperBlock404(req, res, next) {
const { superBlock } = req.body;
if (superBlock && superBlocks.includes(superBlock)) {
return next();
}
return res.status(404).end();
}
const renderCertifedEmail = loopback.template(
path.join(__dirname, '..', 'views', 'emails', 'certified.ejs')
);
function createCertTypeIds(app) {
const { Challenge } = app.models;
return {
// legacy
[certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(legacyDataVisId, Challenge),
// modern
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.dataVis2018]: getIdsForCert$(dataVis2018Id, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge),
[certTypes.fullStack]: getIdsForCert$(fullStackId, Challenge)
};
}
function isCertified(ids, completedChallenges = []) { function isCertified(ids, completedChallenges = []) {
return _.every( return _.every(ids, ({ id }) =>
ids, _.find(completedChallenges, ({ id: completedId }) => completedId === id)
({ id }) => _.find(
completedChallenges,
({ id: completedId }) => completedId === id
)
); );
} }
@ -64,51 +116,43 @@ const certIds = {
[certTypes.fullStack]: fullStackId [certTypes.fullStack]: fullStackId
}; };
const certViews = {
[certTypes.frontEnd]: 'certificate/legacy/front-end.jade',
[certTypes.backEnd]: 'certificate/legacy/back-end.jade',
[certTypes.dataVis]: 'certificate/legacy/data-visualization.jade',
[certTypes.respWebDesign]: 'certificate/responsive-web-design.jade',
[certTypes.frontEndLibs]: 'certificate/front-end-libraries.jade',
[certTypes.jsAlgoDataStruct]:
'certificate/javascript-algorithms-and-data-structures.jade',
[certTypes.dataVis2018]: 'certificate/data-visualization.jade',
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
[certTypes.infosecQa]:
'certificate/information-security-and-quality-assurance.jade',
[certTypes.fullStack]: 'certificate/full-stack.jade'
};
const certText = { const certText = {
[certTypes.frontEnd]: 'Legacy Front End', [certTypes.frontEnd]: 'Legacy Front End',
[certTypes.backEnd]: 'Legacy Back End', [certTypes.backEnd]: 'Legacy Back End',
[certTypes.dataVis]: 'Legacy Data Visualization', [certTypes.dataVis]: 'Legacy Data Visualization',
[certTypes.fullStack]: 'Legacy Full Stack', [certTypes.fullStack]: 'Full Stack',
[certTypes.respWebDesign]: 'Responsive Web Design', [certTypes.respWebDesign]: 'Responsive Web Design',
[certTypes.frontEndLibs]: 'Front End Libraries', [certTypes.frontEndLibs]: 'Front End Libraries',
[certTypes.jsAlgoDataStruct]: [certTypes.jsAlgoDataStruct]: 'JavaScript Algorithms and Data Structures',
'JavaScript Algorithms and Data Structures',
[certTypes.dataVis2018]: 'Data Visualization', [certTypes.dataVis2018]: 'Data Visualization',
[certTypes.apisMicroservices]: 'APIs and Microservices', [certTypes.apisMicroservices]: 'APIs and Microservices',
[certTypes.infosecQa]: 'Information Security and Quality Assurance' [certTypes.infosecQa]: 'Information Security and Quality Assurance'
}; };
const completionHours = {
[certTypes.frontEnd]: 400,
[certTypes.backEnd]: 400,
[certTypes.dataVis]: 400,
[certTypes.fullStack]: 1800,
[certTypes.respWebDesign]: 300,
[certTypes.frontEndLibs]: 300,
[certTypes.jsAlgoDataStruct]: 300,
[certTypes.dataVis2018]: 300,
[certTypes.apisMicroservices]: 300,
[certTypes.infosecQa]: 300
};
function getIdsForCert$(id, Challenge) { function getIdsForCert$(id, Challenge) {
return observeQuery( return observeQuery(Challenge, 'findById', id, {
Challenge, id: true,
'findById', tests: true,
id, name: true,
{ challengeType: true
id: true, }).shareReplay();
tests: true,
name: true,
challengeType: true
}
)
.shareReplay();
} }
const superBlocks = Object.keys(superBlockCertTypeMap);
function sendCertifiedEmail( function sendCertifiedEmail(
{ {
email = '', email = '',
@ -150,111 +194,18 @@ function sendCertifiedEmail(
return send$(notifyUser).map(() => true); return send$(notifyUser).map(() => true);
} }
export default function certificate(app) { function createVerifyCert(certTypeIds, app) {
const router = app.loopback.Router(); const { Email } = app.models;
const { Email, Challenge, User } = app.models; return function verifyCert(req, res, next) {
const {
function findUserByUsername$(username, fields) { body: { superBlock },
return observeQuery( user
User, } = req;
'findOne',
{
where: { username },
fields
}
);
}
const certTypeIds = {
// legacy
[certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(legacyDataVisId, Challenge),
// modern
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.dataVis2018]: getIdsForCert$(dataVis2018Id, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge),
[certTypes.fullStack]: getIdsForCert$(fullStackId, Challenge)
};
const superBlocks = Object.keys(superBlockCertTypeMap);
router.get(
'/:username/front-end-certification',
(req, res) => res.redirect(
`/certification/${req.params.username}/legacy-front-end`
)
);
router.get(
'/:username/data-visualization-certification',
(req, res) => res.redirect(
`/certification/${req.params.username}/legacy-data-visualization`
)
);
router.get(
'/:username/back-end-certification',
(req, res) => res.redirect(
`/certification/${req.params.username}/legacy-back-end`
)
);
router.get(
'/:username/full-stack-certification',
(req, res) => res.redirect(
`/certification/${req.params.username}/full-stack`
)
);
router.post(
'/certificate/verify',
ifNoUser401,
ifNoSuperBlock404,
verifyCert
);
router.get(
'/certification/:username/:cert',
showCert
);
app.use(router);
const noNameMessage = dedent`
We need your name so we can put it on your certification.
Add your name to your account settings and click the save button.
Then we can issue your certification.
`;
const notCertifiedMessage = name => dedent`
it looks like you have not completed the necessary steps.
Please complete the required challenges to claim the
${name}
`;
const alreadyClaimedMessage = name => dedent`
It looks like you already have claimed the ${name}
`;
const successMessage = (username, name) => dedent`
@${username}, you have successfully claimed
the ${name}!
Congratulations on behalf of the freeCodeCamp team!
`;
function verifyCert(req, res, next) {
const { body: { superBlock }, user } = req;
log(superBlock); log(superBlock);
let certType = superBlockCertTypeMap[superBlock]; let certType = superBlockCertTypeMap[superBlock];
log(certType); log(certType);
return user.getCompletedChallenges$() return user
.getCompletedChallenges$()
.flatMap(() => certTypeIds[certType]) .flatMap(() => certTypeIds[certType])
.flatMap(challenge => { .flatMap(challenge => {
const certName = certText[certType]; const certName = certText[certType];
@ -263,31 +214,28 @@ export default function certificate(app) {
} }
let updateData = { let updateData = {
$set: { [certType]: true
[certType]: true
}
}; };
if (challenge) { if (challenge) {
const { const { id, tests, challengeType } = challenge;
id, if (
tests, !user[certType] &&
challengeType !isCertified(tests, user.completedChallenges)
} = challenge; ) {
if (!user[certType] &&
!isCertified(tests, user.completedChallenges)) {
return Observable.just(notCertifiedMessage(certName)); return Observable.just(notCertifiedMessage(certName));
} }
updateData['$push'] = { updateData = {
completedChallenges: { ...updateData,
id, completedChallenges: [
completedDate: new Date(), ...user.completedChallenges,
challengeType {
} id,
completedDate: new Date(),
challengeType
}
]
}; };
user.completedChallenges[
user.completedChallenges.length - 1
] = { id, completedDate: new Date() };
} }
if (!user.name) { if (!user.name) {
@ -306,145 +254,164 @@ export default function certificate(app) {
// if not it noop // if not it noop
sendCertifiedEmail(user, Email.send$), sendCertifiedEmail(user, Email.send$),
({ count }, pledgeOrMessage) => ({ count, pledgeOrMessage }) ({ count }, pledgeOrMessage) => ({ count, pledgeOrMessage })
) ).map(({ count, pledgeOrMessage }) => {
.map( if (typeof pledgeOrMessage === 'string') {
({ count, pledgeOrMessage }) => { log(pledgeOrMessage);
if (typeof pledgeOrMessage === 'string') { }
log(pledgeOrMessage); log(`${count} documents updated`);
} return successMessage(user.username, certName);
log(`${count} documents updated`); });
return successMessage(user.username, certName); })
} .subscribe(message => {
); return res.status(200).json({
}) message,
.subscribe( success: message.includes('Congratulations')
(message) => { });
return res.status(200).json({ }, next);
message, };
success: message.includes('Congratulations') }
});
}, function createShowCert(app) {
next const { User } = app.models;
);
function findUserByUsername$(username, fields) {
return observeQuery(User, 'findOne', {
where: { username },
fields
});
} }
function ifNoSuperBlock404(req, res, next) { return function showCert(req, res, next) {
const { superBlock } = req.body;
if (superBlock && superBlocks.includes(superBlock)) {
return next();
}
return res.status(404).end();
}
function showCert(req, res, next) {
let { username, cert } = req.params; let { username, cert } = req.params;
username = username.toLowerCase(); username = username.toLowerCase();
const certType = superBlockCertTypeMap[cert]; const certType = superBlockCertTypeMap[cert];
const certId = certIds[certType]; const certId = certIds[certType];
return findUserByUsername$( const certTitle = certText[certType];
username, const completionTime = completionHours[certType] || 300;
{ return findUserByUsername$(username, {
isCheater: true, isCheater: true,
isFrontEndCert: true, isFrontEndCert: true,
isBackEndCert: true, isBackEndCert: true,
isFullStackCert: true, isFullStackCert: true,
isRespWebDesignCert: true, isRespWebDesignCert: true,
isFrontEndLibsCert: true, isFrontEndLibsCert: true,
isJsAlgoDataStructCert: true, isJsAlgoDataStructCert: true,
isDataVisCert: true, isDataVisCert: true,
is2018DataVisCert: true, is2018DataVisCert: true,
isApisMicroservicesCert: true, isApisMicroservicesCert: true,
isInfosecQaCert: true, isInfosecQaCert: true,
isHonest: true, isHonest: true,
username: true, username: true,
name: true, name: true,
completedChallenges: true, completedChallenges: true,
profileUI: true profileUI: true
}).subscribe(user => {
if (!user) {
return res.json({
messages: [
{
type: 'info',
message: `We could not find a user with the username "${username}"`
}
]
});
} }
) const { isLocked, showCerts } = user.profileUI;
.subscribe(
user => {
if (!user) {
req.flash(
'danger',
`We couldn't find a user with the username ${username}`
);
return res.redirect('/');
}
const { isLocked, showCerts } = user.profileUI;
const profile = `/portfolio/${user.username}`;
if (!user.name) { if (!user.name) {
req.flash( return res.json({
'danger', messages: [
dedent` {
type: 'info',
message: dedent`
This user needs to add their name to their account This user needs to add their name to their account
in order for others to be able to view their certification. in order for others to be able to view their certification.
` `
); }
return res.redirect(profile); ]
} });
}
if (user.isCheater) { if (user.isCheater) {
return res.redirect(profile); return res.json({
} messages: [
{
type: 'info',
message:
'This user is not eligible for freeCodeCamp.org ' +
'certifications at this time'
}
]
});
}
if (isLocked) { if (isLocked) {
req.flash( return res.json({
'danger', messages: [
dedent` {
type: 'info',
message: dedent`
${username} has chosen to make their profile ${username} has chosen to make their profile
private. They will need to make their profile public private. They will need to make their profile public
in order for others to be able to view their certification. in order for others to be able to view their certification.
` `
); }
return res.redirect('/'); ]
} });
}
if (!showCerts) { if (!showCerts) {
req.flash( return res.json({
'danger', messages: [
dedent` {
type: 'info',
message: dedent`
${username} has chosen to make their certifications ${username} has chosen to make their certifications
private. They will need to make their certifications public private. They will need to make their certifications public
in order for others to be able to view them. in order for others to be able to view them.
` `
); }
return res.redirect('/'); ]
} });
}
if (!user.isHonest) { if (!user.isHonest) {
req.flash( return res.json({
'danger', messages: [
dedent` {
type: 'info',
message: dedent`
${username} has not yet agreed to our Academic Honesty Pledge. ${username} has not yet agreed to our Academic Honesty Pledge.
` `
);
return res.redirect(profile);
}
if (user[certType]) {
const { completedChallenges = [] } = user;
const { completedDate = new Date() } = _.find(
completedChallenges, ({ id }) => certId === id
) || {};
return res.render(
certViews[certType],
{
username: user.username,
date: moment(new Date(completedDate)).format('MMMM D, YYYY'),
name: user.name
} }
); ]
} });
req.flash( }
'danger',
`Looks like user ${username} is not ${certText[certType]} certified` if (user[certType]) {
); const { completedChallenges = [] } = user;
return res.redirect(profile); const { completedDate = new Date() } =
}, _.find(completedChallenges, ({ id }) => certId === id) || {};
next
); const { username, name } = user;
} return res.json({
certTitle,
username,
name,
date: format(new Date(completedDate), 'MMMM D, YYYY'),
completionTime
});
}
return res.json({
messages: [
{
type: 'info',
message: `It looks like user ${username} is not ${
certText[certType]
} certified`
}
]
});
}, next);
};
} }

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Advanced Frontend
h4 1 of 3 legacy freeCodeCamp certification, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/advanced-front-end

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong APIs and Microservices
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/apis-and-microservices

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Data Visualization
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/data-visualization

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Front End Libraries
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/front-end-libraries

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Full Stack
h4 Developer Certification, representing approximately 1800 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/responsive-web-design

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Information Security and Quality Assurance
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/information-security-and-quality-assurance

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong JavaScript Algorithms and Data Structures
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/javascript-algorithms-and-data-structures

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include ../styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Back End Development
h4 Certification, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/legacy-back-end

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include ../styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Data Visualization Projects
h4 Developer Certification, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/legacy-data-visualization

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include ../styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Front End Development
h4 Certification, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/legacy-front-end

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include ../styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Legacy Full Stack Development Program
h4 All three of the legacy freeCodeCamp certifications, representing approximately 1,200 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/legacy-full-stack

View File

@ -1,32 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued 
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Responsive Web Design
h4 Developer Certification, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certification at: https://www.freecodecamp.org/certification/#{username}/responsive-web-design

View File

@ -1,203 +0,0 @@
style.
@font-face {
font-family: "Sax Mono";
src: url("/fonts/saxmono.ttf") format("truetype");
}
* {
margin: 0;
padding: 0;
}
.container {
max-width: 1500px;
width: 100%;
padding: 30px;
border: darkgreen 15px solid;
border-radius: 3px;
}
.row {
margin: 0;
}
.col-sm-12 {
padding: 0;
}
.certificate-wrapper {
top: 0;
position: relative;
font-family: "Sax Mono", monospace;
}
header {
width: 100%;
height: 140px;
/*background-image: url('https://i.imgur.com/1FK6aaN.png');
background-repeat: no-repeat;
background-position: top;
background-size: cover;*/
background-color: darkgreen;
}
.logo {
display: flex;
align-items: center;
height: 140px;
margin-left: 100px;
}
.logo img {
max-width: 500px;
width: 100%;
}
.issue-date {
line-height: 140px;
font-size: 20px;
text-align: right;
margin-right: 100px;
color: #fff;
}
.information {
margin-top: -20px;
height: 380px;
text-align: center;
background-color: #efefef;
}
.information-container {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0px 100px;
}
p {
margin: 0;
}
h3 {
font-size: 30px;
}
h4 {
margin-top: 25px;
font-size: 20px;
}
h1 {
font-size: 40px;
color: #006400;
}
.signatures {
text-align: center;
margin: 0 auto;
background-color: #efefef;
}
.signatures img {
max-width: 300px;
width: 100%;
margin: 0 auto;
}
.signatures p {
font-size: 18px;
padding-top: 10px;
}
.verify {
padding: 30px 0;
font-size: 15px;
text-align: center;
word-wrap: break-word;
background-color: #efefef;
}
/*adds vertical alignment when the height is bigger than required to display everything*/
@media screen and (min-height: 700px) {
body {
display: flex;
flex-direction: column;
justify-content: center;
height: 100vh;
}
}
/*mobile media queries*/
@media screen and (max-width: 992px) {
header {
height: 160px;
}
.logo {
margin-left: 0;
padding: 20px;
justify-content: center;
height: 80px;
}
.logo img {
margin-top: 20px;
}
.issue-date {
margin-top: 10px;
margin-right: 0;
text-align: center;
line-height: 0px;
word-wrap: break-word;
}
.issue-date strong {
display: block;
margin-top: 15px;
line-height: 25px;
}
.information {
height: 300px;
}
.information-container {
margin: 0px 15px;
text-align: center;
word-wrap: break-word;
}
h3 {
font-size: 25px;
}
h1 {
font-size: 28px;
}
}
@media screen and (max-width: 675px) {
.container {
padding: 0;
border: 0;
}
header {
height: 190px;
}
h3 {
font-size: 15px;
}
h1 {
font-size: 17px;
}
h4 {
font-size: 15px;
margin-top: 20px;
}
}

View File

@ -8,6 +8,10 @@ module.exports = {
}, },
plugins: [ plugins: [
'gatsby-plugin-react-helmet', 'gatsby-plugin-react-helmet',
{
resolve: 'gatsby-plugin-create-client-paths',
options: { prefixes: ['/certification/*'] }
},
{ {
resolve: 'gatsby-plugin-manifest', resolve: 'gatsby-plugin-manifest',
options: { options: {

View File

@ -5954,6 +5954,14 @@
} }
} }
}, },
"gatsby-plugin-create-client-paths": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/gatsby-plugin-create-client-paths/-/gatsby-plugin-create-client-paths-2.0.0-rc.1.tgz",
"integrity": "sha512-rWnqFww3pcEiGLg5d/5l/mS1GA13VhN/5Y8p7TC/5Ff9owaZd4nWeNdtyImdFnWZNROFEJwkLsB8pFJtzyEPuw==",
"requires": {
"@babel/runtime": "7.0.0"
}
},
"gatsby-plugin-manifest": { "gatsby-plugin-manifest": {
"version": "2.0.2-rc.1", "version": "2.0.2-rc.1",
"resolved": "https://registry.npmjs.org/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.0.2-rc.1.tgz", "resolved": "https://registry.npmjs.org/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.0.2-rc.1.tgz",

View File

@ -10,9 +10,11 @@
"@fortawesome/free-regular-svg-icons": "^5.2.0", "@fortawesome/free-regular-svg-icons": "^5.2.0",
"@fortawesome/free-solid-svg-icons": "^5.2.0", "@fortawesome/free-solid-svg-icons": "^5.2.0",
"@fortawesome/react-fontawesome": "0.0.20", "@fortawesome/react-fontawesome": "0.0.20",
"@reach/router": "^1.1.1",
"axios": "^0.18.0", "axios": "^0.18.0",
"gatsby": "^2.0.0-rc.1", "gatsby": "^2.0.0-rc.1",
"gatsby-link": "^2.0.0-rc.1", "gatsby-link": "^2.0.0-rc.1",
"gatsby-plugin-create-client-paths": "^2.0.0-rc.1",
"gatsby-plugin-manifest": "next", "gatsby-plugin-manifest": "next",
"gatsby-plugin-react-helmet": "next", "gatsby-plugin-react-helmet": "next",
"gatsby-plugin-sitemap": "^2.0.0-rc.1", "gatsby-plugin-sitemap": "^2.0.0-rc.1",

View File

@ -0,0 +1,182 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Grid, Row, Col, Image } from 'react-bootstrap';
import {
showCertSelector,
showCertFetchStateSelector,
showCert
} from '../redux';
import validCertNames from '../../utils/validCertNames';
import { createFlashMessage } from '../components/Flash/redux';
import standardErrorMessage from '../utils/standardErrorMessage';
import reallyWeirdErrorMessage from '../utils/reallyWeirdErrorMessage';
import RedirectHome from '../components/RedirectHome';
import { Loader } from '../components/helpers';
import './show-certification.css';
const propTypes = {
cert: PropTypes.shape({
username: PropTypes.string,
name: PropTypes.string,
certName: PropTypes.string,
date: PropTypes.string
}),
certDashedName: PropTypes.string,
certName: PropTypes.string,
certTitle: PropTypes.string,
createFlashMessage: PropTypes.func.isRequired,
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
issueDate: PropTypes.string,
showCert: PropTypes.func.isRequired,
userFullName: PropTypes.string,
username: PropTypes.string,
validCertName: PropTypes.bool
};
const mapStateToProps = (state, { certName }) => {
const validCertName = validCertNames.some(name => name === certName);
return createSelector(
showCertSelector,
showCertFetchStateSelector,
(cert, fetchState) => ({
cert,
fetchState,
validCertName
})
);
};
const mapDispatchToProps = dispatch =>
bindActionCreators({ createFlashMessage, showCert }, dispatch);
class ShowCertification extends Component {
componentDidMount() {
const { username, certName, validCertName, showCert } = this.props;
if (validCertName) {
return showCert({ username, certName });
}
return null;
}
render() {
const {
cert,
fetchState,
validCertName,
createFlashMessage,
certName
} = this.props;
if (!validCertName) {
createFlashMessage(standardErrorMessage);
return <RedirectHome />;
}
const { pending, complete, errored } = fetchState;
if (pending) {
return (
<div className='loader-wrapper'>
<Loader />
</div>
);
}
if (!pending && errored) {
createFlashMessage(standardErrorMessage);
return <RedirectHome />;
}
if (!pending && !complete && !errored) {
createFlashMessage(reallyWeirdErrorMessage);
return <RedirectHome />;
}
const {
date: issueDate,
name: userFullName,
username,
certTitle,
completionTime
} = cert;
return (
<Grid className='certificate-wrapper certification-namespace'>
<Row>
<header>
<Col md={5} sm={12}>
<div className='logo'>
<Image
alt="freeCodeCamp.org's Logo"
responsive={true}
src={
'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo' +
'.svg'
}
/>
</div>
</Col>
<Col md={7} sm={12}>
<div className='issue-date'>
Issued&nbsp;
<strong>{issueDate}</strong>
</div>
</Col>
</header>
<section className='information'>
<div className='information-container'>
<h3>This certifies that</h3>
<h1>
<strong>{userFullName}</strong>
</h1>
<h3>has successfully completed the freeCodeCamp.org</h3>
<h1>
<strong>{certTitle} Certification</strong>
</h1>
<h4>
Developer Certification, representing approximately{' '}
{completionTime} hours of coursework
</h4>
</div>
</section>
<footer>
<div className='row signatures'>
<Image
alt="Quincy Larson's Signature"
src='https://i.imgur.com/OJFVJKg.png'
/>
<p>
<strong>Quincy Larson</strong>
</p>
<p>Executive Director, freeCodeCamp.org</p>
</div>
<Row>
<p className='verify'>
Verify this certification at:
https://www.freecodecamp.org/certification/
{username}/{certName}
</p>
</Row>
</footer>
</Row>
</Grid>
);
}
}
ShowCertification.displayName = 'ShowCertification';
ShowCertification.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShowCertification);

View File

@ -0,0 +1,12 @@
.loader-wrapper {
height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.loader-wrapper .sk-spinner {
transform: scale(8);
color: #006400;
}

View File

@ -0,0 +1,8 @@
import { navigate } from 'gatsby';
const Redirecthome = () => {
navigate('/');
return null;
};
export default Redirecthome;

View File

@ -1,20 +1,9 @@
import React from 'react'; import React from 'react';
import Helmet from 'react-helmet'; import Spinner from 'react-spinkit';
function Loader() { function Loader() {
return ( return (
<div className='full-size'> <Spinner name='ball-clip-rotate-multiple'/>
<Helmet>
<link href='/css/loader.css' rel='stylesheet' />
</Helmet>
<div className='loader full-size'>
<div className='ball-scale-ripple-multiple'>
<div/>
<div/>
<div/>
</div>
</div>
</div>
); );
} }

View File

@ -7,8 +7,10 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.certification-namespace h1 {
.certification-namespace .container { margin: 12px 0;
}
.certification-namespace.container {
max-width: 1500px; max-width: 1500px;
width: 100%; width: 100%;
padding: 30px; padding: 30px;
@ -24,8 +26,8 @@
padding: 0; padding: 0;
} }
.certification-namespace .certificate-wrapper { .certification-namespace.certificate-wrapper {
top: 0; top: calc(100vh / 20);
position: relative; position: relative;
font-family: 'Sax Mono', monospace; font-family: 'Sax Mono', monospace;
} }
@ -33,11 +35,8 @@
.certification-namespace header { .certification-namespace header {
width: 100%; width: 100%;
height: 140px; height: 140px;
/*background-image: url('https://i.imgur.com/1FK6aaN.png');
background-repeat: no-repeat;
background-position: top;
background-size: cover;*/
background-color: darkgreen; background-color: darkgreen;
position: relative;
} }
.certification-namespace .logo { .certification-namespace .logo {
@ -178,7 +177,7 @@
} }
@media screen and (max-width: 675px) { @media screen and (max-width: 675px) {
.certification-namespace .container { .certification-namespaces.issue-date {
padding: 0; padding: 0;
border: 0; border: 0;
} }

View File

@ -1,10 +1,20 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Router } from '@reach/router';
import Redirecthome from '../components/RedirectHome';
import ShowCertification from '../client-only-routes/ShowCertification';
import './certification.css'; import './certification.css';
class Certification extends Component { class Certification extends Component {
render() { render() {
return <h2>Certs</h2>; return (
<Router>
<ShowCertification path='/certification/:username/:certName' />
<Redirecthome default={true} />
</Router>
);
} }
} }

View File

@ -4,19 +4,28 @@ import { createTypes, createAsyncTypes } from '../utils/createTypes';
import { createFetchUserSaga } from './fetch-user-saga'; import { createFetchUserSaga } from './fetch-user-saga';
import { createAcceptTermsSaga } from './accept-terms-saga'; import { createAcceptTermsSaga } from './accept-terms-saga';
import { createAppMountSaga } from './app-mount-saga'; import { createAppMountSaga } from './app-mount-saga';
import { createShowCertSaga } from './show-cert-saga';
import { createUpdateMyEmailSaga } from './update-email-saga'; import { createUpdateMyEmailSaga } from './update-email-saga';
const ns = 'app'; const ns = 'app';
const defaultFetchState = {
pending: true,
complete: false,
errored: false,
error: null
};
const initialState = { const initialState = {
appUsername: '', appUsername: '',
fetchState: { showCert: {},
pending: true, showCertFetchState: {
complete: false, ...defaultFetchState
errored: false,
error: null
}, },
user: {} user: {},
userFetchState: {
...defaultFetchState
}
}; };
const types = createTypes( const types = createTypes(
@ -24,6 +33,7 @@ const types = createTypes(
'appMount', 'appMount',
...createAsyncTypes('fetchUser'), ...createAsyncTypes('fetchUser'),
...createAsyncTypes('acceptTerms'), ...createAsyncTypes('acceptTerms'),
...createAsyncTypes('showCert'),
...createAsyncTypes('updateMyEmail') ...createAsyncTypes('updateMyEmail')
], ],
ns ns
@ -33,7 +43,8 @@ export const sagas = [
...createAcceptTermsSaga(types), ...createAcceptTermsSaga(types),
...createAppMountSaga(types), ...createAppMountSaga(types),
...createFetchUserSaga(types), ...createFetchUserSaga(types),
...createUpdateMyEmailSaga(types) ...createUpdateMyEmailSaga(types),
...createShowCertSaga(types)
]; ];
export const appMount = createAction(types.appMount); export const appMount = createAction(types.appMount);
@ -46,12 +57,24 @@ export const fetchUser = createAction(types.fetchUser);
export const fetchUserComplete = createAction(types.fetchUserComplete); export const fetchUserComplete = createAction(types.fetchUserComplete);
export const fetchUserError = createAction(types.fetchUserError); export const fetchUserError = createAction(types.fetchUserError);
export const showCert = createAction(types.showCert);
export const showCertComplete = createAction(types.showCertComplete);
export const showCertError = createAction(types.showCertError);
export const updateMyEmail = createAction(types.updateMyEmail); export const updateMyEmail = createAction(types.updateMyEmail);
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete); export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
export const updateMyEmailError = createAction(types.updateMyEmailError); export const updateMyEmailError = createAction(types.updateMyEmailError);
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length; export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
export const userFetchStateSelector = state => state[ns].fetchState;
export const showCertSelector = state => state[ns].showCert;
export const showCertFetchStateSelector = state => state[ns].showCertFetchState;
export const userByNameSelector = username => state => {
const { user } = state[ns];
return username in user ? user[username] : {};
};
export const userFetchStateSelector = state => state[ns].userFetchState;
export const usernameSelector = state => state[ns].appUsername; export const usernameSelector = state => state[ns].appUsername;
export const userSelector = state => state[ns].user; export const userSelector = state => state[ns].user;
@ -59,18 +82,13 @@ export const reducer = handleActions(
{ {
[types.fetchUser]: state => ({ [types.fetchUser]: state => ({
...state, ...state,
fetchState: { userFetchState: { ...defaultFetchState }
pending: true,
complete: false,
errored: false,
error: null
}
}), }),
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({ [types.fetchUserComplete]: (state, { payload: { user, username } }) => ({
...state, ...state,
user, user,
appUsername: username, appUsername: username,
fetchState: { userFetchState: {
pending: false, pending: false,
complete: true, complete: true,
errored: false, errored: false,
@ -79,7 +97,32 @@ export const reducer = handleActions(
}), }),
[types.fetchUserError]: (state, { payload }) => ({ [types.fetchUserError]: (state, { payload }) => ({
...state, ...state,
fetchState: { userFetchState: {
pending: false,
complete: false,
errored: true,
error: payload
}
}),
[types.showCert]: state => ({
...state,
showCert: {},
showCertFetchState: { ...defaultFetchState }
}),
[types.showCertComplete]: (state, { payload }) => ({
...state,
showCert: payload,
showCertFetchState: {
pending: false,
complete: true,
errored: false,
error: null
}
}),
[types.showCertError]: (state, { payload }) => ({
...state,
showCert: {},
showCertFetchState: {
pending: false, pending: false,
complete: false, complete: false,
errored: true, errored: true,

View File

@ -0,0 +1,27 @@
import { put, takeEvery, call } from 'redux-saga/effects';
import { navigateTo } from 'gatsby';
import { createFlashMessage } from '../components/Flash/redux';
import { getShowCert } from '../utils/ajax';
import { showCertComplete, showCertError } from '.';
function* getShowCertSaga({ payload: { username, certName: cert } }) {
try {
const { data: response } = yield call(getShowCert, username, cert);
const { messages } = response;
if (messages && messages.length) {
for (let i = 0; i < messages.length; i++) {
yield put(createFlashMessage(messages[i]));
}
yield call(navigateTo, '/');
return;
}
yield put(showCertComplete(response));
} catch (e) {
yield put(showCertError(e));
}
}
export function createShowCertSaga(types) {
return [takeEvery(types.showCert, getShowCertSaga)];
}

View File

@ -14,10 +14,24 @@ function put(path, body) {
return axios.put(`${base}${path}`, body); return axios.put(`${base}${path}`, body);
} }
// function del(path) {
// return axios.delete(`${base}${path}`);
// }
/** GET **/
export function getSessionUser() { export function getSessionUser() {
return get('/user/get-session-user'); return get('/user/get-session-user');
} }
export function getShowCert(username, cert) {
return get(`/certificate/showCert/${username}/${cert}`);
}
/** POST **/
/** PUT **/
export function putUserAcceptsTerms(quincyEmails) { export function putUserAcceptsTerms(quincyEmails) {
return put('/update-privacy-terms', { quincyEmails }); return put('/update-privacy-terms', { quincyEmails });
} }
@ -25,3 +39,5 @@ export function putUserAcceptsTerms(quincyEmails) {
export function putUserUpdateEmail(email) { export function putUserUpdateEmail(email) {
return put('/update-my-email', { email }); return put('/update-my-email', { email });
} }
/** DELETE **/

View File

@ -0,0 +1,8 @@
export default {
type: 'danger',
message:
'Something really weird happened, if it happens again, please consider ' +
"raising an issue on <a href='https://github.com/freeCodeCamp" +
"/freeCodeCamp/issues/new' target='_blank' rel='nofollow " +
"noreferrer'>GitHub</a>."
};

View File

@ -0,0 +1,4 @@
export default {
type: 'danger',
message: 'Something went wrong, please check and try again'
};

View File

@ -4,4 +4,10 @@
/login /signin 301 /login /signin 301
/deprecated-signin /signin 301 /deprecated-signin /signin 301
/logout /signout 301 /logout /signout 301
/passwordless-change /confirm-email 301 /passwordless-change /confirm-email 301
# certification redirects
/:username/front-end-certification /certification/:username/legacy-front-end 301
/:username/data-visualization-certification /certification/:username/legacy-data-visualization 301
/:username/back-end-certification /certification/:username/legacy-back-end 301
/:username/full-stack-certificatio /certification/:username/full-stack 301

View File

@ -0,0 +1,12 @@
export default [
'responsive-web-design',
'javascript-algorithms-and-data-structures',
'front-end-libraries',
'data-visualization',
'apis-and-microservices',
'information-security-and-quality-assurance',
'full-stack',
'legacy-front-end',
'legacy-back-end',
'legacy-data-visualization'
];