mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-13 06:04:13 -04:00
refactor(client): migrate certification to ts (#49708)
* refactor(client): migrate certification to ts * fix: reduce certMap into projectMap etc. * fix: rename certMap -> fullCertMap * fix: cert-and-project-map strict typing * close but not close enough * fix: combine project and legacy project maps * chore: add type assertion safety comment * fix: add correct types to test mocks * fix: another test * remove settings-button.test.js The test did not handle any changes, and only served as a failing snapshot when projects are added/updated * refactor: use exported map types Co-authored by: Muhammed Mustafa <muhammed@freecodecamp.org> * refactor: use exported map types Co-authored-by: Muhammed Mustafa <muhammed@freecodecamp.org> --------- Co-authored-by: Muhammed Mustafa <muhammed@freecodecamp.org>
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
||||
usernameSelector
|
||||
} from '../redux/selectors';
|
||||
import { UserFetchState, User } from '../redux/prop-types';
|
||||
import { certMap } from '../resources/cert-and-project-map';
|
||||
import { fullCertMap } from '../resources/cert-and-project-map';
|
||||
import certificateMissingMessage from '../utils/certificate-missing-message';
|
||||
import reallyWeirdErrorMessage from '../utils/really-weird-error-message';
|
||||
import standardErrorMessage from '../utils/standard-error-message';
|
||||
@@ -79,7 +79,7 @@ interface ShowCertificationProps {
|
||||
const requestedUserSelector = (state: unknown, { username = '' }) =>
|
||||
userByNameSelector(username.toLowerCase())(state) as User;
|
||||
|
||||
const validCertSlugs = certMap.map(cert => cert.certSlug);
|
||||
const validCertSlugs = fullCertMap.map(cert => cert.certSlug);
|
||||
|
||||
const mapStateToProps = (state: unknown, props: ShowCertificationProps) => {
|
||||
const isValidCert = validCertSlugs.some(slug => slug === props.certSlug);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
completedChallengesInBlockSelector,
|
||||
completedPercentageSelector
|
||||
} from '../../templates/Challenges/redux/selectors';
|
||||
import { certMap } from '../../resources/cert-and-project-map';
|
||||
import { certMapWithoutFullStack } from '../../resources/cert-and-project-map';
|
||||
import ProgressBarInner from './progress-bar-inner';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
@@ -55,10 +55,8 @@ function ProgressBar({
|
||||
t
|
||||
}: ProgressBarProps): JSX.Element {
|
||||
const blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
|
||||
const isCertificationProject = certMap.some(cert => {
|
||||
if ('projects' in cert) {
|
||||
return cert.projects.some((project: { id: string }) => project.id === id);
|
||||
}
|
||||
const isCertificationProject = certMapWithoutFullStack.some(cert => {
|
||||
return cert.projects.some((project: { id: string }) => project.id === id);
|
||||
});
|
||||
|
||||
const totalChallengesInBlock = currentBlockIds?.length ?? 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,11 @@ import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from '../../redux/create-store';
|
||||
import { Ext } from '../../redux/prop-types';
|
||||
import { verifyCert } from '../../redux/settings/actions';
|
||||
import { createFlashMessage } from '../Flash/redux';
|
||||
|
||||
import { CertificationSettings } from './certification';
|
||||
import CertificationSettings from './certification';
|
||||
|
||||
jest.mock('../../analytics');
|
||||
|
||||
@@ -106,127 +109,186 @@ const defaultTestProps = {
|
||||
completedChallenges: [
|
||||
{
|
||||
id: 'bd7156d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7155d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7154d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7153d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7168d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7178d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7188d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7198d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7108d8c242eddfaeb5bd13',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bdef',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bdff',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bd0e',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bdee',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443edefaeb5bd0f',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443eddfaeb5bdef',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443eddfaeb5bdff',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443eddfaeb5bd0e',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443eddfaeb5bd0f',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7158d8c443eddfaeb5bdee',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e444147903586ffb414c94c',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e444147903586ffb414c94d',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e444147903586ffb414c94e',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e444147903586ffb414c94f',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e44414f903586ffb414c950',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e46f7e5ac417301a38fb928',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e46f7e5ac417301a38fb929',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e46f7f8ac417301a38fb92a',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e46f802ac417301a38fb92b',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e4f5c4b570f7e3a4949899f',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: 'bd7157d8c242eddfaeb5bd13',
|
||||
completedDate: 1554272923799,
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
challengeFiles: []
|
||||
}
|
||||
],
|
||||
createFlashMessage: () => {},
|
||||
createFlashMessage: createFlashMessage,
|
||||
is2018DataVisCert: false,
|
||||
isApisMicroservicesCert: false,
|
||||
isBackEndCert: false,
|
||||
@@ -246,13 +308,14 @@ const defaultTestProps = {
|
||||
isRelationalDatabaseCertV8: false,
|
||||
isCollegeAlgebraPyCertV8: false,
|
||||
username: 'developmentuser',
|
||||
verifyCert: () => {},
|
||||
errors: {},
|
||||
submit: () => {}
|
||||
verifyCert: verifyCert,
|
||||
isEmailVerified: false
|
||||
// errors: {},
|
||||
// submit: () => {}
|
||||
};
|
||||
|
||||
const contents = 'This is not JS';
|
||||
const ext = 'js';
|
||||
const ext: Ext = 'js';
|
||||
const fileKey = 'indexjs';
|
||||
const name = 'index';
|
||||
const path = 'index.js';
|
||||
@@ -262,15 +325,20 @@ const propsForOnlySolution = {
|
||||
completedChallenges: [
|
||||
{
|
||||
id: '5e46f802ac417301a38fb92b',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp'
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e4f5c4b570f7e3a4949899f',
|
||||
solution: 'https://github.com/freeCodeCamp/freeCodeCamp1',
|
||||
githubLink: 'https://github.com/freeCodeCamp/freeCodeCamp2'
|
||||
githubLink: 'https://github.com/freeCodeCamp/freeCodeCamp2',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: []
|
||||
},
|
||||
{
|
||||
id: '5e46f7f8ac417301a38fb92a',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: [
|
||||
{
|
||||
contents,
|
||||
@@ -289,6 +357,7 @@ const propsForMultifileProject = {
|
||||
completedChallenges: [
|
||||
{
|
||||
id: '587d78af367417b2b2512b03',
|
||||
completedDate: 123456789,
|
||||
challengeFiles: [
|
||||
{
|
||||
contents,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Table, Button } from '@freecodecamp/react-bootstrap';
|
||||
import { Link, navigate } from 'gatsby';
|
||||
import { find, first } from 'lodash-es';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { find } from 'lodash-es';
|
||||
import React, { MouseEvent, useState } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { createSelector } from 'reselect';
|
||||
import ScrollableAnchor, { configureAnchors } from 'react-scrollable-anchor';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -13,60 +13,41 @@ import ProjectPreviewModal from '../../templates/Challenges/components/project-p
|
||||
import { openModal } from '../../templates/Challenges/redux/actions';
|
||||
import {
|
||||
projectMap,
|
||||
legacyProjectMap
|
||||
legacyProjectMap,
|
||||
fullProjectMap,
|
||||
ProjectMap,
|
||||
LegacyProjectMap
|
||||
} from '../../resources/cert-and-project-map';
|
||||
import { FlashMessages } from '../Flash/redux/flash-messages';
|
||||
import ProjectModal from '../SolutionViewer/project-modal';
|
||||
import { FullWidthRow, Spacer } from '../helpers';
|
||||
import { SolutionDisplayWidget } from '../solution-display-widget';
|
||||
import SectionHeader from './section-header';
|
||||
import { certSlugTypeMap } from '../../../../config/certification-settings';
|
||||
|
||||
import './certification.css';
|
||||
import {
|
||||
ClaimedCertifications,
|
||||
CompletedChallenge,
|
||||
User
|
||||
} from '../../redux/prop-types';
|
||||
import { createFlashMessage } from '../Flash/redux';
|
||||
import { verifyCert } from '../../redux/settings/actions';
|
||||
import SectionHeader from './section-header';
|
||||
|
||||
configureAnchors({ offset: -40, scrollDuration: 0 });
|
||||
|
||||
const propTypes = {
|
||||
completedChallenges: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
solution: PropTypes.string,
|
||||
githubLink: PropTypes.string,
|
||||
challengeType: PropTypes.number,
|
||||
completedDate: PropTypes.number,
|
||||
challengeFiles: PropTypes.array
|
||||
})
|
||||
),
|
||||
createFlashMessage: PropTypes.func.isRequired,
|
||||
is2018DataVisCert: PropTypes.bool,
|
||||
isApisMicroservicesCert: PropTypes.bool,
|
||||
isBackEndCert: PropTypes.bool,
|
||||
isDataAnalysisPyCertV7: PropTypes.bool,
|
||||
isDataVisCert: PropTypes.bool,
|
||||
isCollegeAlgebraPyCertV8: PropTypes.bool,
|
||||
isFrontEndCert: PropTypes.bool,
|
||||
isFrontEndLibsCert: PropTypes.bool,
|
||||
isFullStackCert: PropTypes.bool,
|
||||
isHonest: PropTypes.bool,
|
||||
isInfosecCertV7: PropTypes.bool,
|
||||
isInfosecQaCert: PropTypes.bool,
|
||||
isJsAlgoDataStructCert: PropTypes.bool,
|
||||
isMachineLearningPyCertV7: PropTypes.bool,
|
||||
isQaCertV7: PropTypes.bool,
|
||||
isRelationalDatabaseCertV8: PropTypes.bool,
|
||||
isRespWebDesignCert: PropTypes.bool,
|
||||
isSciCompPyCertV7: PropTypes.bool,
|
||||
openModal: PropTypes.func,
|
||||
t: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
verifyCert: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
openModal
|
||||
};
|
||||
|
||||
const certifications = Object.keys(projectMap);
|
||||
const legacyCertifications = Object.keys(legacyProjectMap);
|
||||
// Safety: projectMap definitely has projectMap keys,
|
||||
// and we are only interested in these keys
|
||||
const certifications = Object.keys(projectMap) as Array<keyof ProjectMap>;
|
||||
// Safety: legacyProjectMap definitely has legacyProjectMap keys,
|
||||
// and we are only interested in these keys
|
||||
const legacyCertifications = Object.keys(legacyProjectMap) as Array<
|
||||
keyof LegacyProjectMap
|
||||
>;
|
||||
const isCertSelector = ({
|
||||
is2018DataVisCert,
|
||||
isApisMicroservicesCert,
|
||||
@@ -85,7 +66,7 @@ const isCertSelector = ({
|
||||
isMachineLearningPyCertV7,
|
||||
isRelationalDatabaseCertV8,
|
||||
isCollegeAlgebraPyCertV8
|
||||
}) => ({
|
||||
}: ClaimedCertifications) => ({
|
||||
is2018DataVisCert,
|
||||
isApisMicroservicesCert,
|
||||
isJsAlgoDataStructCert,
|
||||
@@ -149,33 +130,37 @@ const honestyInfoMessage = {
|
||||
message: FlashMessages.HonestFirst
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
solutionViewer: {
|
||||
projectTitle: '',
|
||||
challengeFiles: null,
|
||||
solution: null,
|
||||
isOpen: false
|
||||
}
|
||||
};
|
||||
type CertificationSettingsProps = {
|
||||
createFlashMessage: typeof createFlashMessage;
|
||||
t: TFunction;
|
||||
verifyCert: typeof verifyCert;
|
||||
openModal: typeof openModal;
|
||||
} & ClaimedCertifications &
|
||||
Pick<User, 'completedChallenges' | 'isHonest' | 'username'>;
|
||||
|
||||
export class CertificationSettings extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { ...initialState };
|
||||
function CertificationSettings(props: CertificationSettingsProps) {
|
||||
const [projectTitle, setProjectTitle] = useState('');
|
||||
const [challengeFiles, setChallengeFiles] = useState<
|
||||
CompletedChallenge['challengeFiles'] | null
|
||||
>(null);
|
||||
const [challengeData, setChallengeData] = useState<CompletedChallenge | null>(
|
||||
null
|
||||
);
|
||||
const [solution, setSolution] = useState<string | null>();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
function initialiseState() {
|
||||
setProjectTitle('');
|
||||
setChallengeFiles(null);
|
||||
setSolution(null);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
createHandleLinkButtonClick = to => e => {
|
||||
e.preventDefault();
|
||||
return navigate(to);
|
||||
};
|
||||
const handleSolutionModalHide = () => initialiseState();
|
||||
|
||||
handleSolutionModalHide = () => this.setState({ ...initialState });
|
||||
const getUserIsCertMap = () => isCertMapSelector(props);
|
||||
|
||||
getUserIsCertMap = () => isCertMapSelector(this.props);
|
||||
|
||||
getProjectSolution = (projectId, projectTitle) => {
|
||||
const { completedChallenges, openModal } = this.props;
|
||||
const getProjectSolution = (projectId: string, projectTitle: string) => {
|
||||
const { completedChallenges, openModal } = props;
|
||||
const completedProject = find(
|
||||
completedChallenges,
|
||||
({ id }) => projectId === id
|
||||
@@ -185,16 +170,14 @@ export class CertificationSettings extends Component {
|
||||
}
|
||||
|
||||
const { solution, challengeFiles } = completedProject;
|
||||
const showUserCode = () =>
|
||||
this.setState({
|
||||
solutionViewer: {
|
||||
projectTitle,
|
||||
challengeFiles,
|
||||
solution,
|
||||
isOpen: true
|
||||
}
|
||||
});
|
||||
const showUserCode = () => {
|
||||
setProjectTitle(projectTitle);
|
||||
setChallengeFiles(challengeFiles);
|
||||
setSolution(solution);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
// Type == ChallengeFile or CompletedChallenge?
|
||||
const challengeData = completedProject
|
||||
? {
|
||||
...completedProject,
|
||||
@@ -205,12 +188,8 @@ export class CertificationSettings extends Component {
|
||||
: null;
|
||||
|
||||
const showProjectPreview = () => {
|
||||
this.setState({
|
||||
projectViewer: {
|
||||
previewTitle: projectTitle,
|
||||
challengeData
|
||||
}
|
||||
});
|
||||
setProjectTitle(projectTitle);
|
||||
setChallengeData(challengeData);
|
||||
openModal('projectPreview');
|
||||
};
|
||||
|
||||
@@ -226,9 +205,10 @@ export class CertificationSettings extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
renderCertifications = (certName, projectsMap) => {
|
||||
const { t } = this.props;
|
||||
const { certSlug } = first(projectsMap[certName]);
|
||||
type CertName = keyof ProjectMap | keyof LegacyProjectMap;
|
||||
function renderCertifications(certName: CertName) {
|
||||
const { t } = props;
|
||||
const { certSlug } = fullProjectMap[certName][0];
|
||||
return (
|
||||
<FullWidthRow key={certName}>
|
||||
<Spacer size='medium' />
|
||||
@@ -243,22 +223,26 @@ export class CertificationSettings extends Component {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderProjectsFor(
|
||||
{renderProjectsFor({
|
||||
certName,
|
||||
this.getUserIsCertMap()[certName],
|
||||
projectsMap
|
||||
)}
|
||||
isCert: getUserIsCertMap()[certName]
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</FullWidthRow>
|
||||
);
|
||||
};
|
||||
renderProjectsFor = (certName, isCert, projectsMap) => {
|
||||
const { username, isHonest, createFlashMessage, t, verifyCert } =
|
||||
this.props;
|
||||
const { certSlug } = first(projectsMap[certName]);
|
||||
}
|
||||
function renderProjectsFor({
|
||||
certName,
|
||||
isCert
|
||||
}: {
|
||||
certName: CertName;
|
||||
isCert: boolean;
|
||||
}) {
|
||||
const { username, isHonest, createFlashMessage, t, verifyCert } = props;
|
||||
const { certSlug } = fullProjectMap[certName][0];
|
||||
const certLocation = `/certification/${username}/${certSlug}`;
|
||||
const createClickHandler = certSlug => e => {
|
||||
const clickHandler = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (isCert) {
|
||||
return navigate(certLocation);
|
||||
@@ -267,7 +251,7 @@ export class CertificationSettings extends Component {
|
||||
? verifyCert(certSlug)
|
||||
: createFlashMessage(honestyInfoMessage);
|
||||
};
|
||||
return projectsMap[certName]
|
||||
return fullProjectMap[certName]
|
||||
.map(({ link, title, id }) => (
|
||||
<tr className='project-row' key={id}>
|
||||
<td className='project-title col-sm-8 col-xs-8'>
|
||||
@@ -276,7 +260,7 @@ export class CertificationSettings extends Component {
|
||||
</Link>
|
||||
</td>
|
||||
<td className='project-solution col-sm-4 col-xs-4'>
|
||||
{this.getProjectSolution(id, title)}
|
||||
{getProjectSolution(id, title)}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
@@ -289,7 +273,7 @@ export class CertificationSettings extends Component {
|
||||
className={'col-xs-12'}
|
||||
href={certLocation}
|
||||
data-cy={`btn-for-${certSlug}`}
|
||||
onClick={createClickHandler(certSlug)}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{isCert ? t('buttons.show-cert') : t('buttons.claim-cert')}{' '}
|
||||
<span className='sr-only'>{certName}</span>
|
||||
@@ -297,9 +281,9 @@ export class CertificationSettings extends Component {
|
||||
</td>
|
||||
</tr>
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
renderLegacyFullStack = () => {
|
||||
const renderLegacyFullStack = () => {
|
||||
const {
|
||||
isFullStackCert,
|
||||
username,
|
||||
@@ -313,7 +297,7 @@ export class CertificationSettings extends Component {
|
||||
isJsAlgoDataStructCert,
|
||||
isRespWebDesignCert,
|
||||
t
|
||||
} = this.props;
|
||||
} = props;
|
||||
|
||||
const fullStackClaimable =
|
||||
is2018DataVisCert &&
|
||||
@@ -334,15 +318,17 @@ export class CertificationSettings extends Component {
|
||||
fontSize: '18px'
|
||||
};
|
||||
|
||||
const createClickHandler = certSlug => e => {
|
||||
e.preventDefault();
|
||||
if (isFullStackCert) {
|
||||
return navigate(certLocation);
|
||||
}
|
||||
return isHonest
|
||||
? verifyCert(certSlug)
|
||||
: createFlashMessage(honestyInfoMessage);
|
||||
};
|
||||
const createClickHandler =
|
||||
(certSlug: keyof typeof certSlugTypeMap) =>
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (isFullStackCert) {
|
||||
return navigate(certLocation);
|
||||
}
|
||||
return isHonest
|
||||
? verifyCert(certSlug)
|
||||
: createFlashMessage(honestyInfoMessage);
|
||||
};
|
||||
return (
|
||||
<FullWidthRow key={certSlug}>
|
||||
<Spacer size='medium' />
|
||||
@@ -408,39 +394,37 @@ export class CertificationSettings extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { solutionViewer, projectViewer } = this.state;
|
||||
const { t } = this.props;
|
||||
const { t } = props;
|
||||
|
||||
return (
|
||||
<ScrollableAnchor id='certification-settings'>
|
||||
<section className='certification-settings'>
|
||||
<SectionHeader>{t('settings.headings.certs')}</SectionHeader>
|
||||
{certifications.map(certName =>
|
||||
this.renderCertifications(certName, projectMap)
|
||||
)}
|
||||
<SectionHeader>{t('settings.headings.legacy-certs')}</SectionHeader>
|
||||
{this.renderLegacyFullStack()}
|
||||
{legacyCertifications.map(certName =>
|
||||
this.renderCertifications(certName, legacyProjectMap)
|
||||
)}
|
||||
<ProjectModal
|
||||
{...solutionViewer}
|
||||
handleSolutionModalHide={this.handleSolutionModalHide}
|
||||
/>
|
||||
<ProjectPreviewModal
|
||||
{...projectViewer}
|
||||
closeText={t('buttons.close')}
|
||||
showProjectPreview={true}
|
||||
/>
|
||||
</section>
|
||||
</ScrollableAnchor>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScrollableAnchor id='certification-settings'>
|
||||
<section className='certification-settings'>
|
||||
<SectionHeader>{t('settings.headings.certs')}</SectionHeader>
|
||||
{certifications.map(certName => renderCertifications(certName))}
|
||||
<SectionHeader>{t('settings.headings.legacy-certs')}</SectionHeader>
|
||||
{renderLegacyFullStack()}
|
||||
{legacyCertifications.map(certName => renderCertifications(certName))}
|
||||
<ProjectModal
|
||||
{...{
|
||||
projectTitle,
|
||||
challengeFiles,
|
||||
solution: solution ?? undefined,
|
||||
isOpen
|
||||
}}
|
||||
handleSolutionModalHide={handleSolutionModalHide}
|
||||
/>
|
||||
<ProjectPreviewModal
|
||||
challengeData={challengeData}
|
||||
previewTitle={projectTitle}
|
||||
closeText={t('buttons.close')}
|
||||
showProjectPreview={true}
|
||||
/>
|
||||
</section>
|
||||
</ScrollableAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
CertificationSettings.displayName = 'CertificationSettings';
|
||||
CertificationSettings.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
@@ -1,48 +0,0 @@
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import {
|
||||
legacyProjectMap,
|
||||
projectMap
|
||||
} from '../../resources/cert-and-project-map';
|
||||
import { CertificationSettings } from './certification';
|
||||
|
||||
const props = { t: val => val };
|
||||
|
||||
const certificationSettings = new CertificationSettings(props);
|
||||
|
||||
const { renderCertifications } = certificationSettings;
|
||||
|
||||
beforeAll(() => {
|
||||
projectMap['JavaScript Algorithms and Data Structures'] = projectMap[
|
||||
'JavaScript Algorithms and Data Structures'
|
||||
].map(v => ({
|
||||
...v,
|
||||
link: 'javascript'
|
||||
}));
|
||||
});
|
||||
|
||||
it('should check legacy certification button consistency', () => {
|
||||
const legacyCertifications = Object.keys(legacyProjectMap);
|
||||
|
||||
const tree = renderer
|
||||
.create(
|
||||
legacyCertifications.map(certName =>
|
||||
renderCertifications(certName, legacyProjectMap)
|
||||
)
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should check certification button consistency', () => {
|
||||
const certifications = Object.keys(projectMap);
|
||||
|
||||
const tree = renderer
|
||||
.create(
|
||||
certifications.map(certName => renderCertifications(certName, projectMap))
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HandlerProps } from 'react-reflex';
|
||||
import { SuperBlocks } from '../../../config/certification-settings';
|
||||
import { Themes } from '../components/settings/theme';
|
||||
import { certMap } from '../resources/cert-and-project-map';
|
||||
import { fullCertMap } from '../resources/cert-and-project-map';
|
||||
|
||||
export type Steps = {
|
||||
isHonest?: boolean;
|
||||
@@ -26,7 +26,7 @@ export type MarkdownRemark = {
|
||||
superBlock: SuperBlocks;
|
||||
// TODO: make enum like superBlock
|
||||
certification: string;
|
||||
title: (typeof certMap)[number]['title'];
|
||||
title: (typeof fullCertMap)[number]['title'];
|
||||
};
|
||||
headings: [
|
||||
{
|
||||
@@ -235,7 +235,7 @@ export type ProfileUI = {
|
||||
showTimeLine: boolean;
|
||||
};
|
||||
|
||||
type ClaimedCertifications = {
|
||||
export type ClaimedCertifications = {
|
||||
is2018DataVisCert: boolean;
|
||||
isApisMicroservicesCert: boolean;
|
||||
isBackEndCert: boolean;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
certTypes
|
||||
} from '../../../../config/certification-settings';
|
||||
import { createFlashMessage } from '../../components/Flash/redux';
|
||||
import { certMap } from '../../resources/cert-and-project-map';
|
||||
import { fullCertMap } from '../../resources/cert-and-project-map';
|
||||
import {
|
||||
getUsernameExists,
|
||||
putUpdateMyAbout,
|
||||
@@ -171,7 +171,7 @@ function* validateUsernameSaga({ payload }) {
|
||||
|
||||
function* verifyCertificationSaga({ payload }) {
|
||||
// check redux if can claim cert before calling backend
|
||||
const currentCert = certMap.find(cert => cert.certSlug === payload);
|
||||
const currentCert = fullCertMap.find(cert => cert.certSlug === payload);
|
||||
const completedChallenges = yield select(completedChallengesSelector);
|
||||
const certTitle = currentCert?.title || payload;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const legacyInfosecQaInfosecBase = infoSecBase;
|
||||
|
||||
// TODO: generate this automatically in a separate file
|
||||
// from the md/meta.json files for each cert and projects
|
||||
const certMap = [
|
||||
const legacyCertMap = [
|
||||
{
|
||||
id: '561add10cb82ac38a17513be',
|
||||
title: 'Legacy Front End',
|
||||
@@ -177,14 +177,7 @@ const certMap = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '561add10cb82ac38a17213bd',
|
||||
title: 'Legacy Full Stack',
|
||||
certSlug: 'full-stack',
|
||||
flag: 'isFullStackCert'
|
||||
// Requirements are other certs and is
|
||||
// handled elsewhere
|
||||
},
|
||||
|
||||
{
|
||||
id: '561add10cb82ac39a17513bc',
|
||||
title: 'Legacy Data Visualization',
|
||||
@@ -292,7 +285,18 @@ const certMap = [
|
||||
certSlug: 'information-security-and-quality-assurance'
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
] as const;
|
||||
const legacyFullStack = {
|
||||
id: '561add10cb82ac38a17213bd',
|
||||
title: 'Legacy Full Stack',
|
||||
certSlug: 'full-stack',
|
||||
flag: 'isFullStackCert',
|
||||
projects: null
|
||||
// Requirements are other certs and is
|
||||
// handled elsewhere
|
||||
} as const;
|
||||
const certMap = [
|
||||
{
|
||||
id: '561add10cb82ac38a17513bc',
|
||||
title: 'Responsive Web Design',
|
||||
@@ -333,7 +337,6 @@ const certMap = [
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
id: '561abd10cb81ac38a17513bc',
|
||||
title: 'JavaScript Algorithms and Data Structures',
|
||||
@@ -754,6 +757,7 @@ const certMap = [
|
||||
]
|
||||
}
|
||||
] as const;
|
||||
const upcomingCertMap = [] as const;
|
||||
|
||||
function getResponsiveWebDesignPath(project: string) {
|
||||
return `${responsiveWeb22Base}/${project}-project/${project}`;
|
||||
@@ -769,23 +773,50 @@ function getJavaScriptAlgoPath(project: string) {
|
||||
: `${jsAlgoBase}/${project}`;
|
||||
}
|
||||
|
||||
const titles = certMap.map(({ title }) => title);
|
||||
type Title = (typeof titles)[number];
|
||||
const legacyProjectMap: Partial<Record<Title, unknown>> = {};
|
||||
const projectMap: Partial<Record<Title, unknown>> = {};
|
||||
const certMapWithoutFullStack = [
|
||||
...upcomingCertMap,
|
||||
...legacyCertMap,
|
||||
...certMap
|
||||
] as const;
|
||||
|
||||
certMap.forEach(cert => {
|
||||
// Filter out Legacy Full Stack so inputs for project
|
||||
// URLs aren't rendered on the settings page
|
||||
if (cert.title !== 'Legacy Full Stack') {
|
||||
if (cert.title.startsWith('Legacy')) {
|
||||
legacyProjectMap[cert.title] = cert.projects;
|
||||
// temporary hiding of certs from settings page
|
||||
// should do suggestion on line 33 and use front matter to hide it
|
||||
} else {
|
||||
projectMap[cert.title] = cert.projects;
|
||||
}
|
||||
}
|
||||
});
|
||||
const fullCertMap = [...certMapWithoutFullStack, legacyFullStack] as const;
|
||||
|
||||
export { certMap, legacyProjectMap, projectMap };
|
||||
export type ProjectMap = Record<
|
||||
(typeof certMap)[number]['title'],
|
||||
(typeof certMap)[number]['projects']
|
||||
>;
|
||||
|
||||
const projectMap = certMap.reduce((acc, curr) => {
|
||||
return {
|
||||
...acc,
|
||||
[curr.title]: curr.projects
|
||||
};
|
||||
}, {} as ProjectMap);
|
||||
|
||||
export type LegacyProjectMap = Record<
|
||||
(typeof legacyCertMap)[number]['title'],
|
||||
(typeof legacyCertMap)[number]['projects']
|
||||
>;
|
||||
|
||||
const legacyProjectMap = legacyCertMap.reduce((acc, curr) => {
|
||||
return {
|
||||
...acc,
|
||||
[curr.title]: curr.projects
|
||||
};
|
||||
}, {} as LegacyProjectMap);
|
||||
|
||||
const fullProjectMap = {
|
||||
...legacyProjectMap,
|
||||
...projectMap
|
||||
};
|
||||
|
||||
export {
|
||||
certMap,
|
||||
certMapWithoutFullStack,
|
||||
fullCertMap,
|
||||
fullProjectMap,
|
||||
legacyCertMap,
|
||||
legacyProjectMap,
|
||||
projectMap,
|
||||
upcomingCertMap
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from '../../../redux/selectors';
|
||||
import { User, Steps } from '../../../redux/prop-types';
|
||||
import { verifyCert } from '../../../redux/settings/actions';
|
||||
import { certMap } from '../../../resources/cert-and-project-map';
|
||||
import { fullCertMap } from '../../../resources/cert-and-project-map';
|
||||
|
||||
interface CertChallengeProps {
|
||||
// TODO: create enum/reuse SuperBlocks enum somehow
|
||||
@@ -33,7 +33,7 @@ interface CertChallengeProps {
|
||||
isSignedIn: boolean;
|
||||
currentCerts: Steps['currentCerts'];
|
||||
superBlock: SuperBlocks;
|
||||
title: (typeof certMap)[number]['title'];
|
||||
title: (typeof fullCertMap)[number]['title'];
|
||||
user: User;
|
||||
verifyCert: typeof verifyCert;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ const CertChallenge = ({
|
||||
const [userLoaded, setUserLoaded] = useState(false);
|
||||
|
||||
// @ts-expect-error Typescript is confused
|
||||
const certSlug = certMap.find(x => x.title === title).certSlug;
|
||||
const certSlug = fullCertMap.find(x => x.title === title).certSlug;
|
||||
|
||||
useEffect(() => {
|
||||
const { pending, complete } = fetchState;
|
||||
|
||||
Reference in New Issue
Block a user