fix(client): show certs on timeline (#37947)
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
committed by
mrugesh
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 { connect } from 'react-redux';
|
||||||
import { Row, Col } from '@freecodecamp/react-bootstrap';
|
import { Row, Col } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
import { userByNameSelector } from '../../../redux';
|
import { certificatesByNameSelector } from '../../../redux';
|
||||||
import FullWidthRow from '../../helpers/FullWidthRow';
|
import FullWidthRow from '../../helpers/FullWidthRow';
|
||||||
import { ButtonSpacer, Spacer } from '../../helpers';
|
import { ButtonSpacer, Spacer } from '../../helpers';
|
||||||
import './certifications.css';
|
import './certifications.css';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) =>
|
const mapStateToProps = (state, props) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
userByNameSelector(props.username),
|
certificatesByNameSelector(props.username),
|
||||||
({
|
({ hasModernCert, hasLegacyCert, currentCerts, legacyCerts }) => ({
|
||||||
isRespWebDesignCert,
|
hasModernCert,
|
||||||
is2018DataVisCert,
|
hasLegacyCert,
|
||||||
isFrontEndLibsCert,
|
currentCerts,
|
||||||
isJsAlgoDataStructCert,
|
legacyCerts
|
||||||
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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
)(state, props);
|
)(state, props);
|
||||||
|
|
||||||
|
@ -8,7 +8,13 @@ import { Link, useStaticQuery, graphql } from 'gatsby';
|
|||||||
import TimelinePagination from './TimelinePagination';
|
import TimelinePagination from './TimelinePagination';
|
||||||
import { FullWidthRow } from '../../helpers';
|
import { FullWidthRow } from '../../helpers';
|
||||||
import SolutionViewer from '../../settings/SolutionViewer';
|
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.
|
// Items per page in timeline.
|
||||||
const ITEMS_PER_PAGE = 15;
|
const ITEMS_PER_PAGE = 15;
|
||||||
|
|
||||||
@ -73,13 +79,23 @@ class TimelineInner extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCompletion(completed) {
|
renderCompletion(completed) {
|
||||||
const { idToNameMap } = this.props;
|
const { idToNameMap, username } = this.props;
|
||||||
const { id, completedDate } = completed;
|
const { id, completedDate } = completed;
|
||||||
const { challengeTitle, challengePath } = idToNameMap.get(id);
|
const { challengeTitle, challengePath, certPath } = idToNameMap.get(id);
|
||||||
return (
|
return (
|
||||||
<tr className='timeline-row' key={id}>
|
<tr className='timeline-row' key={id}>
|
||||||
<td>
|
<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>
|
||||||
<td className='text-center'>
|
<td className='text-center'>
|
||||||
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
|
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
|
||||||
@ -225,6 +241,12 @@ function useIdToNameMap() {
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
const idToNameMap = new Map();
|
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 } } }) => {
|
edges.forEach(({ node: { id, title, fields: { slug } } }) => {
|
||||||
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
|
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
|
||||||
});
|
});
|
||||||
@ -238,10 +260,7 @@ const Timeline = props => {
|
|||||||
const { sortedTimeline, totalPages } = useMemo(() => {
|
const { sortedTimeline, totalPages } = useMemo(() => {
|
||||||
const sortedTimeline = reverse(
|
const sortedTimeline = reverse(
|
||||||
sortBy(completedMap, ['completedDate']).filter(challenge => {
|
sortBy(completedMap, ['completedDate']).filter(challenge => {
|
||||||
return (
|
return idToNameMap.has(challenge.id);
|
||||||
challenge.challengeType !== challengeTypes.step &&
|
|
||||||
idToNameMap.has(challenge.id)
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const totalPages = Math.ceil(sortedTimeline.length / ITEMS_PER_PAGE);
|
const totalPages = Math.ceil(sortedTimeline.length / ITEMS_PER_PAGE);
|
||||||
|
@ -36,3 +36,15 @@
|
|||||||
.timeline-row > td {
|
.timeline-row > td {
|
||||||
vertical-align: middle !important;
|
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];
|
const { user } = state[ns];
|
||||||
return username in user ? user[username] : {};
|
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 userFetchStateSelector = state => state[ns].userFetchState;
|
||||||
export const userProfileFetchStateSelector = state =>
|
export const userProfileFetchStateSelector = state =>
|
||||||
state[ns].userProfileFetchState;
|
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);
|
Reference in New Issue
Block a user