chore: remove verify-can-claim-cert logic (#44574)
* chore: remove verify-can-claim-cert logic * remove extraneous * remove console log before Nich wakes up * add api route back with flash * remove unnecessary logic in completion-epic * change tests for new layout * dynamically use api location * rename file * fix Cypress api location * fix(test): anchor does not have disabled class * fix(tests): change js test to claim from /settings * chore: change status to 410 (gone) * update testing again * oliver is nitpicky Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * make oliver happy Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -12,15 +12,13 @@ import {
|
||||
certTypeTitleMap,
|
||||
certTypeIdMap,
|
||||
certIds,
|
||||
oldDataVizId,
|
||||
superBlockCertTypeMap
|
||||
oldDataVizId
|
||||
} from '../../../../config/certification-settings';
|
||||
import { reportError } from '../middlewares/sentry-error-handler.js';
|
||||
|
||||
import { getChallenges } from '../utils/get-curriculum';
|
||||
import { ifNoUser401 } from '../utils/middleware';
|
||||
import { observeQuery } from '../utils/rx';
|
||||
import { ensureLowerCaseString } from '../../common/models/user';
|
||||
|
||||
const {
|
||||
legacyFrontEndChallengeId,
|
||||
@ -50,7 +48,6 @@ export default function bootCertificate(app) {
|
||||
const certTypeIds = createCertTypeIds(getChallenges());
|
||||
const showCert = createShowCert(app);
|
||||
const verifyCert = createVerifyCert(certTypeIds, app);
|
||||
const verifyCanClaimCert = createVerifyCanClaim(certTypeIds, app);
|
||||
|
||||
api.put('/certificate/verify', ifNoUser401, ifNoSuperBlock404, verifyCert);
|
||||
api.get('/certificate/showCert/:username/:certSlug', showCert);
|
||||
@ -59,6 +56,15 @@ export default function bootCertificate(app) {
|
||||
app.use(api);
|
||||
}
|
||||
|
||||
function verifyCanClaimCert(_req, res) {
|
||||
return res.status(410).json({
|
||||
message: {
|
||||
type: 'info',
|
||||
message: 'Please reload the app, this feature is no longer available.'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getFallbackFullStackDate(completedChallenges, completedDate) {
|
||||
var chalIds = [
|
||||
certTypeIdMap[certTypes.respWebDesign],
|
||||
@ -508,97 +514,3 @@ function createShowCert(app) {
|
||||
}, next);
|
||||
};
|
||||
}
|
||||
|
||||
function createVerifyCanClaim(certTypeIds, app) {
|
||||
const { User } = app.models;
|
||||
|
||||
function findUserByUsername$(username, fields) {
|
||||
return observeQuery(User, 'findOne', {
|
||||
where: { username },
|
||||
fields
|
||||
});
|
||||
}
|
||||
return function verifyCert(req, res, next) {
|
||||
const { superBlock, username } = req.query;
|
||||
log(superBlock);
|
||||
let certType = superBlockCertTypeMap[superBlock];
|
||||
log(certType);
|
||||
|
||||
return findUserByUsername$(ensureLowerCaseString(username), {
|
||||
isFrontEndCert: true,
|
||||
isBackEndCert: true,
|
||||
isFullStackCert: true,
|
||||
isRespWebDesignCert: true,
|
||||
isFrontEndLibsCert: true,
|
||||
isJsAlgoDataStructCert: true,
|
||||
isDataVisCert: true,
|
||||
is2018DataVisCert: true,
|
||||
isApisMicroservicesCert: true,
|
||||
isInfosecQaCert: true,
|
||||
isQaCertV7: true,
|
||||
isInfosecCertV7: true,
|
||||
isSciCompPyCertV7: true,
|
||||
isDataAnalysisPyCertV7: true,
|
||||
isMachineLearningPyCertV7: true,
|
||||
username: true,
|
||||
name: true,
|
||||
isHonest: true,
|
||||
completedChallenges: true
|
||||
}).subscribe(user => {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
message: {
|
||||
type: 'info',
|
||||
message: 'flash.username-not-found',
|
||||
variables: { username }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!certTypeIds[certType]) {
|
||||
return res.status(404).json({
|
||||
message: {
|
||||
type: 'info',
|
||||
// TODO: create a specific 'flash.cert-not-found' message
|
||||
message: 'flash.could-not-find'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Observable.of(certTypeIds[certType])
|
||||
.flatMap(challenge => {
|
||||
const certName = certTypeTitleMap[certType];
|
||||
const { tests = [] } = challenge;
|
||||
let result = 'incomplete-requirements';
|
||||
let status = false;
|
||||
|
||||
const { isHonest, completedChallenges } = user;
|
||||
const isProjectsCompleted = canClaim(tests, completedChallenges);
|
||||
|
||||
if (isHonest && isProjectsCompleted) {
|
||||
status = true;
|
||||
result = 'requirements-met';
|
||||
} else if (isProjectsCompleted) {
|
||||
result = 'projects-completed';
|
||||
} else if (isHonest) {
|
||||
result = 'is-honest';
|
||||
}
|
||||
return Observable.just({
|
||||
type: 'success',
|
||||
message: { status, result },
|
||||
variables: { name: certName }
|
||||
});
|
||||
})
|
||||
.subscribe(message => {
|
||||
return res.status(200).json({
|
||||
response: message,
|
||||
isCertMap: getUserIsCertMap(user),
|
||||
// send back the completed challenges
|
||||
// NOTE: we could just send back the latest challenge, but this
|
||||
// ensures the challenges are synced.
|
||||
completedChallenges: user.completedChallenges
|
||||
});
|
||||
}, next);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -63,7 +63,8 @@
|
||||
"submit-and-go": "Submit and go to next challenge",
|
||||
"go-to-next": "Go to next challenge",
|
||||
"ask-later": "Ask me later",
|
||||
"start-coding": "Start coding!"
|
||||
"start-coding": "Start coding!",
|
||||
"go-to-settings": "Go to settings to claim your certification"
|
||||
},
|
||||
"landing": {
|
||||
"big-heading-1": "Learn to code — for free.",
|
||||
|
@ -189,19 +189,7 @@ export const completedChallengesSelector = state =>
|
||||
export const completionCountSelector = state => state[MainApp].completionCount;
|
||||
export const currentChallengeIdSelector = state =>
|
||||
state[MainApp].currentChallengeId;
|
||||
export const stepsToClaimSelector = state => {
|
||||
const user = userSelector(state);
|
||||
const currentCerts = certificatesByNameSelector(user.username)(
|
||||
state
|
||||
).currentCerts;
|
||||
return {
|
||||
currentCerts: currentCerts,
|
||||
isHonest: user?.isHonest,
|
||||
isShowName: user?.profileUI?.showName,
|
||||
isShowCerts: user?.profileUI?.showCerts,
|
||||
isShowProfile: !user?.profileUI?.isLocked
|
||||
};
|
||||
};
|
||||
|
||||
export const emailSelector = state => userSelector(state).email;
|
||||
export const isAVariantSelector = state => {
|
||||
const email = emailSelector(state);
|
||||
@ -271,6 +259,9 @@ export const userByNameSelector = username => state => {
|
||||
return user[username] ?? initialState.user;
|
||||
};
|
||||
|
||||
export const currentCertsSelector = state =>
|
||||
certificatesByNameSelector(state[MainApp]?.appUsername)(state)?.currentCerts;
|
||||
|
||||
export const certificatesByNameSelector = username => state => {
|
||||
const {
|
||||
isRespWebDesignCert,
|
||||
|
@ -17,11 +17,9 @@ import {
|
||||
isSignedInSelector,
|
||||
submitComplete,
|
||||
updateComplete,
|
||||
updateFailed,
|
||||
usernameSelector
|
||||
updateFailed
|
||||
} from '../../../redux';
|
||||
|
||||
import { getVerifyCanClaimCert } from '../../../utils/ajax';
|
||||
import postUpdate$ from '../utils/postUpdate$';
|
||||
import { actionTypes } from './action-types';
|
||||
import {
|
||||
@ -151,8 +149,7 @@ export default function completionEpic(action$, state$) {
|
||||
switchMap(({ type }) => {
|
||||
const state = state$.value;
|
||||
const meta = challengeMetaSelector(state);
|
||||
const { nextChallengePath, challengeType, superBlock, certification } =
|
||||
meta;
|
||||
const { nextChallengePath, challengeType, superBlock } = meta;
|
||||
const closeChallengeModal = of(closeModal('completion'));
|
||||
|
||||
let submitter = () => of({ type: 'no-user-signed-in' });
|
||||
@ -170,13 +167,7 @@ export default function completionEpic(action$, state$) {
|
||||
}
|
||||
|
||||
const pathToNavigateTo = async () => {
|
||||
return await findPathToNavigateTo(
|
||||
certification,
|
||||
nextChallengePath,
|
||||
superBlock,
|
||||
state,
|
||||
challengeType
|
||||
);
|
||||
return await findPathToNavigateTo(nextChallengePath, superBlock);
|
||||
};
|
||||
|
||||
return submitter(type, state).pipe(
|
||||
@ -188,38 +179,10 @@ export default function completionEpic(action$, state$) {
|
||||
);
|
||||
}
|
||||
|
||||
async function findPathToNavigateTo(
|
||||
certification,
|
||||
nextChallengePath,
|
||||
superBlock,
|
||||
state,
|
||||
challengeType
|
||||
) {
|
||||
let canClaimCert = false;
|
||||
const isProjectSubmission = [
|
||||
challengeTypes.frontEndProject,
|
||||
challengeTypes.backEndProject,
|
||||
challengeTypes.pythonProject
|
||||
].includes(challengeType);
|
||||
if (isProjectSubmission) {
|
||||
const username = usernameSelector(state);
|
||||
try {
|
||||
const response = await getVerifyCanClaimCert(username, certification);
|
||||
if (response.status === 200) {
|
||||
canClaimCert = response.data?.response?.message === 'can-claim-cert';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('failed to verify if user can claim certificate', err);
|
||||
}
|
||||
}
|
||||
let pathToNavigateTo;
|
||||
|
||||
if (nextChallengePath.includes(superBlock) && !canClaimCert) {
|
||||
pathToNavigateTo = nextChallengePath;
|
||||
} else if (canClaimCert) {
|
||||
pathToNavigateTo = `/learn/${superBlock}/#claim-cert-block`;
|
||||
async function findPathToNavigateTo(nextChallengePath, superBlock) {
|
||||
if (nextChallengePath.includes(superBlock)) {
|
||||
return nextChallengePath;
|
||||
} else {
|
||||
pathToNavigateTo = `/learn/${superBlock}/#${superBlock}-projects`;
|
||||
return `/learn/${superBlock}/#${superBlock}-projects`;
|
||||
}
|
||||
return pathToNavigateTo;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
import { navigate } from 'gatsby-link';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, MouseEvent } from 'react';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
@ -13,14 +13,12 @@ import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import {
|
||||
userFetchStateSelector,
|
||||
stepsToClaimSelector,
|
||||
isSignedInSelector
|
||||
isSignedInSelector,
|
||||
currentCertsSelector
|
||||
} from '../../../redux';
|
||||
import { User, Steps } from '../../../redux/prop-types';
|
||||
import { verifyCert } from '../../../redux/settings';
|
||||
import { certMap } from '../../../resources/cert-and-project-map';
|
||||
import { getVerifyCanClaimCert } from '../../../utils/ajax';
|
||||
import CertificationCard from './certification-card';
|
||||
|
||||
interface CertChallengeProps {
|
||||
// TODO: create enum/reuse SuperBlocks enum somehow
|
||||
@ -33,7 +31,7 @@ interface CertChallengeProps {
|
||||
error: null | string;
|
||||
};
|
||||
isSignedIn: boolean;
|
||||
steps: Steps;
|
||||
currentCerts: Steps['currentCerts'];
|
||||
superBlock: SuperBlocks;
|
||||
t: TFunction;
|
||||
title: typeof certMap[number]['title'];
|
||||
@ -48,11 +46,16 @@ const honestyInfoMessage = {
|
||||
|
||||
const mapStateToProps = (state: unknown) => {
|
||||
return createSelector(
|
||||
stepsToClaimSelector,
|
||||
currentCertsSelector,
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
(steps, fetchState: CertChallengeProps['fetchState'], isSignedIn) => ({
|
||||
steps,
|
||||
(
|
||||
currentCerts,
|
||||
fetchState: CertChallengeProps['fetchState'],
|
||||
isSignedIn
|
||||
) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
currentCerts,
|
||||
fetchState,
|
||||
isSignedIn
|
||||
})
|
||||
@ -65,9 +68,8 @@ const mapDispatchToProps = {
|
||||
};
|
||||
|
||||
const CertChallenge = ({
|
||||
certification,
|
||||
createFlashMessage,
|
||||
steps = {},
|
||||
currentCerts,
|
||||
superBlock,
|
||||
t,
|
||||
verifyCert,
|
||||
@ -76,45 +78,8 @@ const CertChallenge = ({
|
||||
isSignedIn,
|
||||
user: { isHonest, username }
|
||||
}: CertChallengeProps): JSX.Element => {
|
||||
const [canClaimCert, setCanClaimCert] = useState(false);
|
||||
const [certVerificationMessage, setCertVerificationMessage] = useState('');
|
||||
const [isCertified, setIsCertified] = useState(false);
|
||||
const [userLoaded, setUserLoaded] = useState(false);
|
||||
const [verificationComplete, setVerificationComplete] = useState(false);
|
||||
const [stepState, setStepState] = useState({
|
||||
numberOfSteps: 0,
|
||||
completedCount: 0
|
||||
});
|
||||
const [hasCompletedRequiredSteps, setHasCompletedRequiredSteps] =
|
||||
useState(false);
|
||||
const [isProjectsCompleted, setIsProjectsCompleted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (username) {
|
||||
void (async () => {
|
||||
try {
|
||||
const data = await getVerifyCanClaimCert(username, certification);
|
||||
if (data?.message) {
|
||||
setCanClaimCert(false);
|
||||
createFlashMessage(data.message);
|
||||
} else {
|
||||
const { status, result } = data?.response?.message;
|
||||
setCanClaimCert(status);
|
||||
setCertVerificationMessage(result);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
createFlashMessage({
|
||||
type: 'danger',
|
||||
message: FlashMessages.ReallyWeird
|
||||
});
|
||||
} finally {
|
||||
setVerificationComplete(true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [username]);
|
||||
|
||||
// @ts-expect-error Typescript is confused
|
||||
const certSlug = certMap.find(x => x.title === title).certSlug;
|
||||
@ -133,40 +98,19 @@ const CertChallenge = ({
|
||||
|
||||
useEffect(() => {
|
||||
setIsCertified(
|
||||
steps?.currentCerts?.find(
|
||||
currentCerts?.find(
|
||||
(cert: { certSlug: string }) =>
|
||||
certSlugTypeMapTyped[cert.certSlug] ===
|
||||
superBlockCertTypeMapTyped[superBlock]
|
||||
)?.show ?? false
|
||||
);
|
||||
|
||||
const projectsCompleted =
|
||||
canClaimCert || certVerificationMessage === 'projects-completed';
|
||||
const projectsCompletedNumber = projectsCompleted ? 1 : 0;
|
||||
const completedCount =
|
||||
Object.values(steps).filter(
|
||||
stepVal => typeof stepVal === 'boolean' && stepVal
|
||||
).length + projectsCompletedNumber;
|
||||
const numberOfSteps = Object.keys(steps).length;
|
||||
setHasCompletedRequiredSteps(completedCount === numberOfSteps);
|
||||
setStepState({ numberOfSteps, completedCount });
|
||||
setIsProjectsCompleted(projectsCompleted);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [steps, canClaimCert, certVerificationMessage]);
|
||||
}, [currentCerts]);
|
||||
|
||||
const certLocation = `/certification/${username}/${certSlug}`;
|
||||
const i18nSuperBlock = t(`intro:${superBlock}.title`);
|
||||
const i18nCertText = t(`intro:misc-text.certification`, {
|
||||
cert: i18nSuperBlock
|
||||
});
|
||||
|
||||
const showCertificationCard =
|
||||
userLoaded &&
|
||||
isSignedIn &&
|
||||
(!isCertified || (!hasCompletedRequiredSteps && verificationComplete));
|
||||
|
||||
const createClickHandler =
|
||||
(certSlug: string | undefined) => (e: { preventDefault: () => void }) => {
|
||||
(certSlug: string | undefined) => (e: MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
if (isCertified) {
|
||||
return navigate(certLocation);
|
||||
@ -177,33 +121,19 @@ const CertChallenge = ({
|
||||
};
|
||||
return (
|
||||
<div className='block'>
|
||||
{showCertificationCard && (
|
||||
<CertificationCard
|
||||
i18nCertText={i18nCertText}
|
||||
isProjectsCompleted={isProjectsCompleted}
|
||||
steps={steps}
|
||||
stepState={stepState}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
)}
|
||||
<>
|
||||
{isSignedIn && (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='cert-btn'
|
||||
disabled={
|
||||
!canClaimCert || (isCertified && !hasCompletedRequiredSteps)
|
||||
}
|
||||
href={certLocation}
|
||||
onClick={createClickHandler(certSlug)}
|
||||
href={isCertified ? certLocation : `/settings#certification-settings`}
|
||||
onClick={() => (isCertified ? createClickHandler(certSlug) : false)}
|
||||
>
|
||||
{isCertified && userLoaded
|
||||
? t('buttons.show-cert')
|
||||
: t('buttons.claim-cert')}
|
||||
: t('buttons.go-to-settings')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,92 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ScrollableAnchor from 'react-scrollable-anchor';
|
||||
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||
import Caret from '../../../assets/icons/caret';
|
||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import { Steps } from '../../../redux/prop-types';
|
||||
import ClaimCertSteps from './claim-cert-steps';
|
||||
|
||||
interface CertificationCardProps {
|
||||
i18nCertText: string;
|
||||
isProjectsCompleted: boolean;
|
||||
stepState: {
|
||||
numberOfSteps: number;
|
||||
completedCount: number;
|
||||
};
|
||||
steps: Steps;
|
||||
superBlock: SuperBlocks;
|
||||
}
|
||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||
|
||||
const CertificationCard = ({
|
||||
isProjectsCompleted,
|
||||
superBlock,
|
||||
i18nCertText,
|
||||
stepState: { completedCount, numberOfSteps },
|
||||
steps
|
||||
}: CertificationCardProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
|
||||
const handleBlockClick = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
const {
|
||||
expand: expandText,
|
||||
collapse: collapseText
|
||||
}: {
|
||||
expand: string;
|
||||
collapse: string;
|
||||
} = t('intro:misc-text');
|
||||
return (
|
||||
<ScrollableAnchor id='claim-cert-block'>
|
||||
<div className={`block ${isExpanded ? 'open' : ''}`}>
|
||||
<div className='block-title-wrapper'>
|
||||
<a className='block-link' href='#claim-cert-block'>
|
||||
<h3 className='big-block-title'>
|
||||
{t('certification-card.title')}
|
||||
<span className='block-link-icon'>#</span>
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div className='block-description'>
|
||||
{t('certification-card.intro', { i18nCertText })}
|
||||
</div>
|
||||
<button
|
||||
aria-expanded={isExpanded}
|
||||
className='map-title'
|
||||
onClick={handleBlockClick}
|
||||
>
|
||||
<Caret />
|
||||
<h4 className='course-title'>
|
||||
{`${isExpanded ? collapseText : expandText}`}
|
||||
</h4>
|
||||
<div className='map-title-completed course-title'>
|
||||
{completedCount === numberOfSteps ? (
|
||||
<GreenPass style={mapIconStyle} />
|
||||
) : (
|
||||
<GreenNotCompleted style={mapIconStyle} />
|
||||
)}
|
||||
<span className='map-completed-count'>{`${completedCount}/${numberOfSteps}`}</span>
|
||||
</div>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<ClaimCertSteps
|
||||
i18nCertText={i18nCertText}
|
||||
isProjectsCompleted={isProjectsCompleted}
|
||||
steps={steps}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ScrollableAnchor>
|
||||
);
|
||||
};
|
||||
|
||||
CertificationCard.displayName = 'CertStatus';
|
||||
|
||||
export default CertificationCard;
|
@ -1,88 +0,0 @@
|
||||
import { Link } from 'gatsby';
|
||||
import React from 'react';
|
||||
import { withTranslation, useTranslation } from 'react-i18next';
|
||||
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import { Steps } from '../../../redux/prop-types';
|
||||
|
||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||
|
||||
interface ClaimCertStepsProps {
|
||||
i18nCertText: string;
|
||||
isProjectsCompleted: boolean;
|
||||
steps: Steps;
|
||||
superBlock: SuperBlocks;
|
||||
}
|
||||
|
||||
const ClaimCertSteps = ({
|
||||
isProjectsCompleted,
|
||||
i18nCertText,
|
||||
steps,
|
||||
superBlock
|
||||
}: ClaimCertStepsProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const renderCheckMark = (isCompleted: boolean) => {
|
||||
return isCompleted ? (
|
||||
<GreenPass style={mapIconStyle} />
|
||||
) : (
|
||||
<GreenNotCompleted style={mapIconStyle} />
|
||||
);
|
||||
};
|
||||
|
||||
const settingsLink = '/settings#privacy-settings';
|
||||
const honestyPolicyAnchor = '/settings#honesty-policy';
|
||||
const {
|
||||
isHonest = false,
|
||||
isShowName = false,
|
||||
isShowCerts = false,
|
||||
isShowProfile = false
|
||||
} = steps;
|
||||
|
||||
return (
|
||||
<ul className='map-challenges-ul' data-cy='claim-cert-steps'>
|
||||
<li className='map-challenge-title map-challenge-wrap'>
|
||||
<Link to={honestyPolicyAnchor}>
|
||||
<span className='badge map-badge'>{renderCheckMark(isHonest)}</span>
|
||||
{t('certification-card.accept-honesty')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className='map-challenge-title map-challenge-wrap'>
|
||||
<a href={`#${superBlock}-projects`}>
|
||||
<span className='badge map-badge'>
|
||||
{renderCheckMark(isProjectsCompleted)}
|
||||
</span>
|
||||
{t('certification-card.complete-project', {
|
||||
i18nCertText
|
||||
})}
|
||||
</a>
|
||||
</li>
|
||||
<li className='map-challenge-title map-challenge-wrap'>
|
||||
<Link to={settingsLink}>
|
||||
<span className='badge map-badge'>
|
||||
{renderCheckMark(isShowProfile)}
|
||||
</span>
|
||||
{t('certification-card.set-profile-public')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className='map-challenge-title map-challenge-wrap'>
|
||||
<Link to={settingsLink}>
|
||||
<span className='badge map-badge'>
|
||||
{renderCheckMark(isShowCerts)}
|
||||
</span>
|
||||
{t('certification-card.set-certs-public')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className='map-challenge-title map-challenge-wrap'>
|
||||
<Link to={settingsLink}>
|
||||
<span className='badge map-badge'>{renderCheckMark(isShowName)}</span>
|
||||
{t('certification-card.set-name')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
ClaimCertSteps.displayName = 'ClaimCertSteps';
|
||||
|
||||
export default withTranslation()(ClaimCertSteps);
|
@ -1,10 +1,8 @@
|
||||
import cookies from 'browser-cookies';
|
||||
import envData from '../../../config/env.json';
|
||||
import { FlashMessageArg } from '../components/Flash/redux';
|
||||
|
||||
import type {
|
||||
ChallengeFile,
|
||||
ClaimedCertifications,
|
||||
CompletedChallenge,
|
||||
User
|
||||
} from '../redux/prop-types';
|
||||
@ -25,6 +23,8 @@ function getCSRFToken() {
|
||||
return token ?? '';
|
||||
}
|
||||
|
||||
// TODO: Might want to handle flash messages as close to the request as possible
|
||||
// to make use of the Response object (message, status, etc)
|
||||
async function get<T>(path: string): Promise<T> {
|
||||
return fetch(`${base}${path}`, defaultOptions).then<T>(res => res.json());
|
||||
}
|
||||
@ -160,31 +160,6 @@ export function getUsernameExists(username: string): Promise<boolean> {
|
||||
return get(`/api/users/exists?username=${username}`);
|
||||
}
|
||||
|
||||
export interface GetVerifyCanClaimCert {
|
||||
response: {
|
||||
type: string;
|
||||
message: {
|
||||
status: boolean;
|
||||
result: string;
|
||||
};
|
||||
variables: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
isCertMap: ClaimedCertifications;
|
||||
completedChallenges: CompletedChallenge[];
|
||||
message?: FlashMessageArg;
|
||||
}
|
||||
|
||||
export function getVerifyCanClaimCert(
|
||||
username: string,
|
||||
certification: string
|
||||
): Promise<GetVerifyCanClaimCert> {
|
||||
return get(
|
||||
`/certificate/verify-can-claim-cert?username=${username}&superBlock=${certification}`
|
||||
);
|
||||
}
|
||||
|
||||
/** POST **/
|
||||
|
||||
interface Donation {
|
||||
|
@ -78,7 +78,8 @@ describe('project submission', () => {
|
||||
// We need to wait for everything to finish loading and hydrating, so we
|
||||
// use this text as a proxy for that.
|
||||
const textInNextPage = projectTitles.slice(1);
|
||||
textInNextPage.push('Claim Your Certification');
|
||||
// The following text exists on the donation modal
|
||||
textInNextPage.push('Nicely done');
|
||||
|
||||
projectsInOrder.forEach(
|
||||
({ block, superBlock, dashedName, solutions }, i) => {
|
||||
@ -118,8 +119,10 @@ describe('project submission', () => {
|
||||
// Claim and view solutions on certification page
|
||||
|
||||
cy.toggleAll();
|
||||
cy.visit('/learn/javascript-algorithms-and-data-structures');
|
||||
cy.contains('Claim Certification').click();
|
||||
cy.visit('/settings');
|
||||
cy.get(
|
||||
`a[href="/certification/developmentuser/${projectsInOrder[0]?.superBlock}"]`
|
||||
).click();
|
||||
cy.contains('Show Certification').click();
|
||||
|
||||
projectTitles.forEach(title => {
|
||||
|
@ -34,19 +34,9 @@ describe('Responsive Web Design Superblock', () => {
|
||||
cy.visit('/learn/responsive-web-design');
|
||||
});
|
||||
describe('Before submitting projects', () => {
|
||||
it('should have a card with href "claim-cert-block"', () => {
|
||||
cy.get('a[href="#claim-cert-block"]').scrollIntoView();
|
||||
cy.get('a[href="#claim-cert-block"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('should have an anchor element with the text "Claim Certification", and class "disabled"', () => {
|
||||
cy.get('a.disabled').should('be.visible');
|
||||
cy.get('a.disabled').should('have.text', 'Claim Certification');
|
||||
});
|
||||
|
||||
it('should have an unordered list with class "map-challenges-ul" containing 5 items', () => {
|
||||
cy.get('[data-cy=claim-cert-steps]').should('be.visible');
|
||||
cy.get('[data-cy=claim-cert-steps]').children().should('have.length', 5);
|
||||
it('should navigate to "/settings#certification-settings" when clicking the "Go to settings to claim your certification" anchor', () => {
|
||||
cy.contains('Go to settings to claim your certification').click();
|
||||
cy.url().should('include', '/settings#certification-settings');
|
||||
});
|
||||
});
|
||||
describe('After submitting all 5 projects', () => {
|
||||
@ -64,7 +54,7 @@ describe('Responsive Web Design Superblock', () => {
|
||||
cy.contains("I've completed this challenge")
|
||||
.should('not.be.disabled')
|
||||
.click();
|
||||
cy.intercept('http://localhost:3000/project-completed').as(
|
||||
cy.intercept(`${Cypress.env('API_LOCATION')}/project-completed`).as(
|
||||
'challengeCompleted'
|
||||
);
|
||||
cy.contains('Submit and go to next challenge').click();
|
||||
@ -74,26 +64,21 @@ describe('Responsive Web Design Superblock', () => {
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.not.eq(url);
|
||||
});
|
||||
cy.visit('/settings');
|
||||
cy.get(
|
||||
`[href="/certification/developmentuser/${projects.superBlock}"]`
|
||||
).click();
|
||||
cy.visit(`/learn/${projects.superBlock}/`);
|
||||
});
|
||||
});
|
||||
it('should be possible to claim and view certifications from the superBlock page', () => {
|
||||
it('should be possible to view certifications from the superBlock page', () => {
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(`/learn/${projects.superBlock}/`);
|
||||
});
|
||||
cy.get('.donation-modal').should('be.visible');
|
||||
cy.contains('Ask me later').click();
|
||||
cy.get('.donation-modal').should('not.exist');
|
||||
// directed to claim-cert-block section
|
||||
cy.url().should('include', '#claim-cert-block');
|
||||
// make sure that the window has not snapped to the top (a weird bug that
|
||||
// we never figured out and so could randomly reappear)
|
||||
cy.window().its('scrollY').should('not.equal', 0);
|
||||
cy.contains('Claim Your Certification');
|
||||
cy.contains('Claim Certification').should('not.be.disabled').click();
|
||||
cy.contains('Show Certification').click();
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/certification/developmentuser/responsive-web-design'
|
||||
`/certification/developmentuser/${projects.superBlock}`
|
||||
);
|
||||
});
|
||||
});
|
@ -28,5 +28,6 @@ module.exports = (on, config) => {
|
||||
|
||||
// Allows us to test the new curriculum before it's released:
|
||||
config.env.SHOW_UPCOMING_CHANGES = process.env.SHOW_UPCOMING_CHANGES;
|
||||
config.env.API_LOCATION = process.env.API_LOCATION;
|
||||
return config;
|
||||
};
|
||||
|
@ -33,7 +33,7 @@
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => {});
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.visit('http://localhost:3000/signin');
|
||||
cy.visit(`${Cypress.env('API_LOCATION')}/signin`);
|
||||
cy.contains('Welcome back');
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user