fix(client): show certs on timeline (#37947)

Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Oliver Eyton-Williams 2020-01-03 07:02:31 +01:00 committed by mrugesh
parent 1b06bb29f0
commit 1a56f4d5f4
6 changed files with 222 additions and 85 deletions

View File

@ -0,0 +1,69 @@
/* eslint-disable max-len */
import React from 'react';
function CertificationIcon() {
return (
<svg
height='248.21'
preserveAspectRatio='xMidYMid meet'
version='1.1'
viewBox='232 241.57142857142856 156.8571428571429 262.2051282051284'
width='142.86'
xmlns='http://www.w3.org/2000/svg'
xmlnsXlink='http://www.w3.org/1999/xlink'
>
<defs>
<path
d='M239 303.14L310.43 248.57L381.86 303.14L354.57 391.43L266.28 391.43L239 303.14Z'
id='aZt9D86Ps'
></path>
<path
d='M327.11 393.59L344.3 496.78L309.91 479.58L275.51 496.78L292.71 393.59L327.11 393.59Z'
id='b7LyrCUAE'
></path>
</defs>
<g>
<g>
<use
fill='var(--primary-color)'
fillOpacity='0'
opacity='1'
xlinkHref='#aZt9D86Ps'
></use>
<g>
<use
fillOpacity='0'
opacity='1'
stroke='var(--primary-color)'
strokeOpacity='1'
strokeWidth='14'
xlinkHref='#aZt9D86Ps'
></use>
</g>
</g>
<g>
<use
fill='var(--primary-color)'
fillOpacity='0'
opacity='1'
xlinkHref='#b7LyrCUAE'
></use>
<g>
<use
fillOpacity='0'
opacity='1'
stroke='var(--primary-color)'
strokeOpacity='1'
strokeWidth='14'
xlinkHref='#b7LyrCUAE'
></use>
</g>
</g>
</g>
</svg>
);
}
CertificationIcon.displayName = 'CertificationIcon';
export default CertificationIcon;

View File

@ -6,89 +6,19 @@ import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import { Row, Col } from '@freecodecamp/react-bootstrap';
import { userByNameSelector } from '../../../redux';
import { certificatesByNameSelector } from '../../../redux';
import FullWidthRow from '../../helpers/FullWidthRow';
import { ButtonSpacer, Spacer } from '../../helpers';
import './certifications.css';
const mapStateToProps = (state, props) =>
createSelector(
userByNameSelector(props.username),
({
isRespWebDesignCert,
is2018DataVisCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isApisMicroservicesCert,
isInfosecQaCert,
isFrontEndCert,
isBackEndCert,
isDataVisCert,
isFullStackCert
}) => ({
hasModernCert:
isRespWebDesignCert ||
is2018DataVisCert ||
isFrontEndLibsCert ||
isJsAlgoDataStructCert ||
isApisMicroservicesCert ||
isInfosecQaCert ||
isFullStackCert,
hasLegacyCert: isFrontEndCert || isBackEndCert || isDataVisCert,
currentCerts: [
{
show: isFullStackCert,
title: 'Full Stack Certification',
showURL: 'full-stack'
},
{
show: isRespWebDesignCert,
title: 'Responsive Web Design Certification',
showURL: 'responsive-web-design'
},
{
show: isJsAlgoDataStructCert,
title: 'JavaScript Algorithms and Data Structures Certification',
showURL: 'javascript-algorithms-and-data-structures'
},
{
show: isFrontEndLibsCert,
title: 'Front End Libraries Certification',
showURL: 'front-end-libraries'
},
{
show: is2018DataVisCert,
title: 'Data Visualization Certification',
showURL: 'data-visualization'
},
{
show: isApisMicroservicesCert,
title: 'APIs and Microservices Certification',
showURL: 'apis-and-microservices'
},
{
show: isInfosecQaCert,
title: 'Information Security and Quality Assurance Certification',
showURL: 'information-security-and-quality-assurance'
}
],
legacyCerts: [
{
show: isFrontEndCert,
title: 'Front End Certification',
showURL: 'legacy-front-end'
},
{
show: isBackEndCert,
title: 'Back End Certification',
showURL: 'legacy-back-end'
},
{
show: isDataVisCert,
title: 'Data Visualization Certification',
showURL: 'legacy-data-visualization'
}
]
certificatesByNameSelector(props.username),
({ hasModernCert, hasLegacyCert, currentCerts, legacyCerts }) => ({
hasModernCert,
hasLegacyCert,
currentCerts,
legacyCerts
})
)(state, props);

View File

@ -8,7 +8,13 @@ import { Link, useStaticQuery, graphql } from 'gatsby';
import TimelinePagination from './TimelinePagination';
import { FullWidthRow } from '../../helpers';
import SolutionViewer from '../../settings/SolutionViewer';
import { challengeTypes } from '../../../../utils/challengeTypes';
import {
getCertIds,
getPathFromID,
getTitleFromId
} from '../../../../../utils';
import CertificationIcon from '../../../assets/icons/CertificationIcon';
// Items per page in timeline.
const ITEMS_PER_PAGE = 15;
@ -73,13 +79,23 @@ class TimelineInner extends Component {
}
renderCompletion(completed) {
const { idToNameMap } = this.props;
const { idToNameMap, username } = this.props;
const { id, completedDate } = completed;
const { challengeTitle, challengePath } = idToNameMap.get(id);
const { challengeTitle, challengePath, certPath } = idToNameMap.get(id);
return (
<tr className='timeline-row' key={id}>
<td>
<Link to={challengePath}>{challengeTitle}</Link>
{certPath ? (
<Link
className='timeline-cert-link'
to={`certification/${username}/${certPath}`}
>
{challengeTitle}
<CertificationIcon />
</Link>
) : (
<Link to={challengePath}>{challengeTitle}</Link>
)}
</td>
<td className='text-center'>
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
@ -225,6 +241,12 @@ function useIdToNameMap() {
}
`);
const idToNameMap = new Map();
for (let id of getCertIds()) {
idToNameMap.set(id, {
challengeTitle: `${getTitleFromId(id)} Certification`,
certPath: getPathFromID(id)
});
}
edges.forEach(({ node: { id, title, fields: { slug } } }) => {
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
});
@ -238,10 +260,7 @@ const Timeline = props => {
const { sortedTimeline, totalPages } = useMemo(() => {
const sortedTimeline = reverse(
sortBy(completedMap, ['completedDate']).filter(challenge => {
return (
challenge.challengeType !== challengeTypes.step &&
idToNameMap.has(challenge.id)
);
return idToNameMap.has(challenge.id);
})
);
const totalPages = Math.ceil(sortedTimeline.length / ITEMS_PER_PAGE);

View File

@ -36,3 +36,15 @@
.timeline-row > td {
vertical-align: middle !important;
}
.timeline-cert-link {
margin-right: 20px;
}
.timeline-cert-link > svg {
display: inline-block;
height: 25px;
width: auto;
margin-left: 10px;
position: absolute;
}

View File

@ -197,6 +197,87 @@ export const userByNameSelector = username => state => {
const { user } = state[ns];
return username in user ? user[username] : {};
};
export const certificatesByNameSelector = username => state => {
const {
isRespWebDesignCert,
is2018DataVisCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isApisMicroservicesCert,
isInfosecQaCert,
isFrontEndCert,
isBackEndCert,
isDataVisCert,
isFullStackCert
} = userByNameSelector(username)(state);
return {
hasModernCert:
isRespWebDesignCert ||
is2018DataVisCert ||
isFrontEndLibsCert ||
isJsAlgoDataStructCert ||
isApisMicroservicesCert ||
isInfosecQaCert ||
isFullStackCert,
hasLegacyCert: isFrontEndCert || isBackEndCert || isDataVisCert,
currentCerts: [
{
show: isFullStackCert,
title: 'Full Stack Certification',
showURL: 'full-stack'
},
{
show: isRespWebDesignCert,
title: 'Responsive Web Design Certification',
showURL: 'responsive-web-design'
},
{
show: isJsAlgoDataStructCert,
title: 'JavaScript Algorithms and Data Structures Certification',
showURL: 'javascript-algorithms-and-data-structures'
},
{
show: isFrontEndLibsCert,
title: 'Front End Libraries Certification',
showURL: 'front-end-libraries'
},
{
show: is2018DataVisCert,
title: 'Data Visualization Certification',
showURL: 'data-visualization'
},
{
show: isApisMicroservicesCert,
title: 'APIs and Microservices Certification',
showURL: 'apis-and-microservices'
},
{
show: isInfosecQaCert,
title: 'Information Security and Quality Assurance Certification',
showURL: 'information-security-and-quality-assurance'
}
],
legacyCerts: [
{
show: isFrontEndCert,
title: 'Front End Certification',
showURL: 'legacy-front-end'
},
{
show: isBackEndCert,
title: 'Back End Certification',
showURL: 'legacy-back-end'
},
{
show: isDataVisCert,
title: 'Data Visualization Certification',
showURL: 'legacy-data-visualization'
}
]
};
};
export const userFetchStateSelector = state => state[ns].userFetchState;
export const userProfileFetchStateSelector = state =>
state[ns].userProfileFetchState;

26
utils/index.js Normal file
View File

@ -0,0 +1,26 @@
import { dasherize } from './slugs';
const idToTitle = new Map(
Object.entries({
'561add10cb82ac38a17523bc': 'APIs and Microservices',
'5a553ca864b52e1d8bceea14': 'Data Visualization',
'561acd10cb82ac38a17513bc': 'Front End Libraries',
'561add10cb82ac38a17213bc': 'Information Security and Quality Assurance',
'561abd10cb81ac38a17513bc': 'JavaScript Algorithms and Data Structures',
'561add10cb82ac38a17513bc': 'Responsive Web Design',
'660add10cb82ac38a17513be': 'Legacy Back End',
'561add10cb82ac39a17513bc': 'Legacy Data Visualization',
'561add10cb82ac38a17513be': 'Legacy Front End',
'561add10cb82ac38a17213bd': 'Full Stack'
})
);
const idToPath = new Map();
for (const [id, title] of idToTitle) {
idToPath.set(id, dasherize(title));
}
export const getCertIds = () => idToPath.keys();
export const getPathFromID = id => idToPath.get(id);
export const getTitleFromId = id => idToTitle.get(id);