fix(client): show certs on timeline (#37947)
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
parent
1b06bb29f0
commit
1a56f4d5f4
69
client/src/assets/icons/CertificationIcon.js
Normal file
69
client/src/assets/icons/CertificationIcon.js
Normal 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;
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
26
utils/index.js
Normal 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);
|
Loading…
x
Reference in New Issue
Block a user