feat(certs): Convert certification views to gatsby
This commit is contained in:
committed by
mrugesh mohapatra
parent
dc00eb8555
commit
77a4452437
@ -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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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: {
|
||||||
|
8
client/package-lock.json
generated
8
client/package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
182
client/src/client-only-routes/ShowCertification.js
Normal file
182
client/src/client-only-routes/ShowCertification.js
Normal 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
|
||||||
|
<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);
|
12
client/src/client-only-routes/show-certification.css
Normal file
12
client/src/client-only-routes/show-certification.css
Normal 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;
|
||||||
|
}
|
8
client/src/components/RedirectHome.js
Normal file
8
client/src/components/RedirectHome.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { navigate } from 'gatsby';
|
||||||
|
|
||||||
|
const Redirecthome = () => {
|
||||||
|
navigate('/');
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Redirecthome;
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
27
client/src/redux/show-cert-saga.js
Normal file
27
client/src/redux/show-cert-saga.js
Normal 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)];
|
||||||
|
}
|
@ -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 **/
|
||||||
|
8
client/src/utils/reallyWeirdErrorMessage.js
Normal file
8
client/src/utils/reallyWeirdErrorMessage.js
Normal 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>."
|
||||||
|
};
|
4
client/src/utils/standardErrorMessage.js
Normal file
4
client/src/utils/standardErrorMessage.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
type: 'danger',
|
||||||
|
message: 'Something went wrong, please check and try again'
|
||||||
|
};
|
@ -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
|
12
client/utils/validCertNames.js
Normal file
12
client/utils/validCertNames.js
Normal 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'
|
||||||
|
];
|
Reference in New Issue
Block a user