feat(client): ts-migration for Introduction templates (#43752)
* chore: rename the file CertificationCard.js to tsx * refactor: refactor the file CertificationCard to tsx refactor: refactor the file CertificationCard to tsx refactor: refactor the file CertificationCard to tsx * chore: rename the file ClaimCertSteps.js to tsx * refactor: refactor the file ClaimCertSteps to TS * chore: rename the file SuperBlockIntro.js to tsx * refactor: refactor the file SuperBlockIntro to TS * chore: rename the file Block.js to tsx * refactor: refactor the file Block to TS refactor: refactor the file Block to TS refactor: refactor the file Block to TS * chore: rename the file CertChallenge.js to tsx * refactor: refactor the file CertChallenge to TS * fix typing * ignore missing redux store connection Co-authored-by: ismail <i.tlemcani@quinten-maroc.com> Co-authored-by: Ismail Tlemcani <ismail.tlemcani@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HandlerProps } from 'react-reflex';
|
import { HandlerProps } from 'react-reflex';
|
||||||
import { SuperBlocks } from '../../../config/certification-settings';
|
import { SuperBlocks } from '../../../config/certification-settings';
|
||||||
|
import { certMap } from '../resources/cert-and-project-map';
|
||||||
|
|
||||||
export const UserPropType = PropTypes.shape({
|
export const UserPropType = PropTypes.shape({
|
||||||
about: PropTypes.string,
|
about: PropTypes.string,
|
||||||
@ -63,12 +64,13 @@ export const CurrentCertsPropType = PropTypes.arrayOf(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const StepsPropType = PropTypes.shape({
|
export type Steps = {
|
||||||
currentCerts: CurrentCertsPropType,
|
isHonest?: boolean;
|
||||||
isShowCerts: PropTypes.bool,
|
currentCerts?: Array<CurrentCert>;
|
||||||
isShowName: PropTypes.bool,
|
isShowCerts?: boolean;
|
||||||
isShowProfile: PropTypes.bool
|
isShowName?: boolean;
|
||||||
});
|
isShowProfile?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type CurrentCert = {
|
export type CurrentCert = {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@ -83,7 +85,7 @@ export type MarkdownRemark = {
|
|||||||
block: string;
|
block: string;
|
||||||
isBeta: boolean;
|
isBeta: boolean;
|
||||||
superBlock: SuperBlocks;
|
superBlock: SuperBlocks;
|
||||||
title: string;
|
title: typeof certMap[number]['title'];
|
||||||
};
|
};
|
||||||
headings: [
|
headings: [
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation, TFunction } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ScrollableAnchor from 'react-scrollable-anchor';
|
import ScrollableAnchor from 'react-scrollable-anchor';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
@ -12,60 +11,69 @@ import { isAuditedCert } from '../../../../../utils/is-audited';
|
|||||||
import Caret from '../../../assets/icons/caret';
|
import Caret from '../../../assets/icons/caret';
|
||||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||||
import GreenPass from '../../../assets/icons/green-pass';
|
import GreenPass from '../../../assets/icons/green-pass';
|
||||||
import { Link } from '../../../components/helpers/';
|
import { Link } from '../../../components/helpers';
|
||||||
import { completedChallengesSelector, executeGA } from '../../../redux';
|
import { completedChallengesSelector, executeGA } from '../../../redux';
|
||||||
|
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import Challenges from './Challenges';
|
import Challenges from './Challenges';
|
||||||
|
|
||||||
const { curriculumLocale } = envData;
|
const { curriculumLocale } = envData;
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (
|
||||||
|
state: unknown,
|
||||||
|
ownProps: { blockDashedName: string } & unknown
|
||||||
|
) => {
|
||||||
const expandedSelector = makeExpandedBlockSelector(ownProps.blockDashedName);
|
const expandedSelector = makeExpandedBlockSelector(ownProps.blockDashedName);
|
||||||
|
|
||||||
return createSelector(
|
return createSelector(
|
||||||
expandedSelector,
|
expandedSelector,
|
||||||
completedChallengesSelector,
|
completedChallengesSelector,
|
||||||
(isExpanded, completedChallenges) => ({
|
(isExpanded: boolean, completedChallenges: CompletedChallenge[]) => ({
|
||||||
isExpanded,
|
isExpanded,
|
||||||
completedChallenges: completedChallenges.map(({ id }) => id)
|
completedChallengeIds: completedChallenges.map(({ id }) => id)
|
||||||
})
|
})
|
||||||
)(state);
|
)(state);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch =>
|
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||||
bindActionCreators({ toggleBlock, executeGA }, dispatch);
|
bindActionCreators({ toggleBlock, executeGA }, dispatch);
|
||||||
|
|
||||||
const propTypes = {
|
interface BlockProps {
|
||||||
blockDashedName: PropTypes.string,
|
blockDashedName: string;
|
||||||
challenges: PropTypes.array,
|
challenges: ChallengeNode[];
|
||||||
completedChallenges: PropTypes.arrayOf(PropTypes.string),
|
completedChallengeIds: string[];
|
||||||
executeGA: PropTypes.func,
|
executeGA: typeof executeGA;
|
||||||
isExpanded: PropTypes.bool,
|
isExpanded: boolean;
|
||||||
superBlock: PropTypes.string,
|
superBlock: string;
|
||||||
t: PropTypes.func,
|
t: TFunction;
|
||||||
toggleBlock: PropTypes.func.isRequired
|
toggleBlock: typeof toggleBlock;
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||||
|
|
||||||
export class Block extends Component {
|
export class Block extends Component<BlockProps> {
|
||||||
constructor(...props) {
|
static displayName: string;
|
||||||
super(...props);
|
constructor(props: BlockProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.handleBlockClick = this.handleBlockClick.bind(this);
|
this.handleBlockClick = this.handleBlockClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockClick() {
|
handleBlockClick(): void {
|
||||||
const { blockDashedName, toggleBlock, executeGA } = this.props;
|
const { blockDashedName, toggleBlock, executeGA } = this.props;
|
||||||
const playSound = store.get('fcc-sound');
|
const playSound = store.get('fcc-sound') as boolean;
|
||||||
if (playSound) {
|
if (playSound) {
|
||||||
void import('tone').then(tone => {
|
void (async () => {
|
||||||
|
const tone = await import('tone');
|
||||||
|
|
||||||
const player = new tone.Player(
|
const player = new tone.Player(
|
||||||
'https://tonejs.github.io/audio/berklee/guitar_chord1.mp3'
|
'https://tonejs.github.io/audio/berklee/guitar_chord1.mp3'
|
||||||
).toDestination();
|
).toDestination();
|
||||||
if (tone.context.state !== 'running') tone.context.resume();
|
if (tone.context.state !== 'running') {
|
||||||
|
void tone.context.resume();
|
||||||
|
}
|
||||||
player.autostart = playSound;
|
player.autostart = playSound;
|
||||||
});
|
})();
|
||||||
}
|
}
|
||||||
executeGA({
|
executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -74,10 +82,10 @@ export class Block extends Component {
|
|||||||
action: blockDashedName
|
action: blockDashedName
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return toggleBlock(blockDashedName);
|
toggleBlock(blockDashedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCheckMark(isCompleted) {
|
renderCheckMark(isCompleted: boolean): JSX.Element {
|
||||||
return isCompleted ? (
|
return isCompleted ? (
|
||||||
<GreenPass style={mapIconStyle} />
|
<GreenPass style={mapIconStyle} />
|
||||||
) : (
|
) : (
|
||||||
@ -85,7 +93,7 @@ export class Block extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBlockIntros(arr) {
|
renderBlockIntros(arr: string[]): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='block-description'>
|
<div className='block-description'>
|
||||||
{arr.map((str, i) => (
|
{arr.map((str, i) => (
|
||||||
@ -95,10 +103,10 @@ export class Block extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
blockDashedName,
|
blockDashedName,
|
||||||
completedChallenges,
|
completedChallengeIds,
|
||||||
challenges,
|
challenges,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
superBlock,
|
superBlock,
|
||||||
@ -108,8 +116,8 @@ export class Block extends Component {
|
|||||||
let completedCount = 0;
|
let completedCount = 0;
|
||||||
const challengesWithCompleted = challenges.map(challenge => {
|
const challengesWithCompleted = challenges.map(challenge => {
|
||||||
const { id } = challenge;
|
const { id } = challenge;
|
||||||
const isCompleted = completedChallenges.some(
|
const isCompleted = completedChallengeIds.some(
|
||||||
completedId => id === completedId
|
(completedChallengeId: string) => completedChallengeId === id
|
||||||
);
|
);
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
completedCount++;
|
completedCount++;
|
||||||
@ -134,13 +142,19 @@ export class Block extends Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const blockIntroObj = t(`intro:${superBlock}.blocks.${blockDashedName}`);
|
const blockIntroObj: { title?: string; intro: string[] } = t(
|
||||||
|
`intro:${superBlock}.blocks.${blockDashedName}`
|
||||||
|
);
|
||||||
const blockTitle = blockIntroObj ? blockIntroObj.title : null;
|
const blockTitle = blockIntroObj ? blockIntroObj.title : null;
|
||||||
const blockIntroArr = blockIntroObj ? blockIntroObj.intro : [];
|
const blockIntroArr = blockIntroObj ? blockIntroObj.intro : [];
|
||||||
const {
|
const {
|
||||||
expand: expandText,
|
expand: expandText,
|
||||||
collapse: collapseText,
|
collapse: collapseText,
|
||||||
courses: coursesText
|
courses: coursesText
|
||||||
|
}: {
|
||||||
|
expand: string;
|
||||||
|
collapse: string;
|
||||||
|
courses: string;
|
||||||
} = t('intro:misc-text');
|
} = t('intro:misc-text');
|
||||||
|
|
||||||
return isProjectBlock ? (
|
return isProjectBlock ? (
|
||||||
@ -196,7 +210,9 @@ export class Block extends Component {
|
|||||||
<button
|
<button
|
||||||
aria-expanded={isExpanded}
|
aria-expanded={isExpanded}
|
||||||
className='map-title'
|
className='map-title'
|
||||||
onClick={this.handleBlockClick}
|
onClick={() => {
|
||||||
|
this.handleBlockClick();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Caret />
|
<Caret />
|
||||||
<h4 className='course-title'>
|
<h4 className='course-title'>
|
||||||
@ -224,7 +240,6 @@ export class Block extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Block.displayName = 'Block';
|
Block.displayName = 'Block';
|
||||||
Block.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
@ -1,13 +1,13 @@
|
|||||||
import { Button } from '@freecodecamp/react-bootstrap';
|
import { Button } from '@freecodecamp/react-bootstrap';
|
||||||
import { navigate } from 'gatsby-link';
|
import { navigate } from 'gatsby-link';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { TFunction, withTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import {
|
import {
|
||||||
certSlugTypeMap,
|
certSlugTypeMap,
|
||||||
superBlockCertTypeMap
|
superBlockCertTypeMap,
|
||||||
|
SuperBlocks
|
||||||
} from '../../../../../config/certification-settings';
|
} from '../../../../../config/certification-settings';
|
||||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||||
import {
|
import {
|
||||||
@ -15,41 +15,40 @@ import {
|
|||||||
stepsToClaimSelector,
|
stepsToClaimSelector,
|
||||||
isSignedInSelector
|
isSignedInSelector
|
||||||
} from '../../../redux';
|
} from '../../../redux';
|
||||||
|
import { User, Steps } from '../../../redux/prop-types';
|
||||||
import { StepsPropType, UserPropType } from '../../../redux/prop-types';
|
|
||||||
import { verifyCert } from '../../../redux/settings';
|
import { verifyCert } from '../../../redux/settings';
|
||||||
|
|
||||||
import { certMap } from '../../../resources/cert-and-project-map';
|
import { certMap } from '../../../resources/cert-and-project-map';
|
||||||
import { getVerifyCanClaimCert } from '../../../utils/ajax';
|
import { getVerifyCanClaimCert } from '../../../utils/ajax';
|
||||||
import CertificationCard from './CertificationCard';
|
import CertificationCard from './certification-card';
|
||||||
|
|
||||||
const propTypes = {
|
interface CertChallengeProps {
|
||||||
createFlashMessage: PropTypes.func.isRequired,
|
createFlashMessage: typeof createFlashMessage;
|
||||||
fetchState: PropTypes.shape({
|
fetchState: {
|
||||||
pending: PropTypes.bool,
|
pending: boolean;
|
||||||
complete: PropTypes.bool,
|
complete: boolean;
|
||||||
errored: PropTypes.bool
|
errored: boolean;
|
||||||
}),
|
error: null | string;
|
||||||
isSignedIn: PropTypes.bool,
|
|
||||||
steps: StepsPropType,
|
|
||||||
superBlock: PropTypes.string,
|
|
||||||
t: PropTypes.func,
|
|
||||||
title: PropTypes.string,
|
|
||||||
user: UserPropType,
|
|
||||||
verifyCert: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
isSignedIn: boolean;
|
||||||
|
steps: Steps;
|
||||||
|
superBlock: SuperBlocks;
|
||||||
|
t: TFunction;
|
||||||
|
title: typeof certMap[number]['title'];
|
||||||
|
user: User;
|
||||||
|
verifyCert: typeof verifyCert;
|
||||||
|
}
|
||||||
|
|
||||||
const honestyInfoMessage = {
|
const honestyInfoMessage = {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.honest-first'
|
message: 'flash.honest-first'
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = (state: unknown) => {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
stepsToClaimSelector,
|
stepsToClaimSelector,
|
||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
(steps, fetchState, isSignedIn) => ({
|
(steps, fetchState: CertChallengeProps['fetchState'], isSignedIn) => ({
|
||||||
steps,
|
steps,
|
||||||
fetchState,
|
fetchState,
|
||||||
isSignedIn
|
isSignedIn
|
||||||
@ -72,7 +71,7 @@ const CertChallenge = ({
|
|||||||
fetchState,
|
fetchState,
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
user: { isHonest, username }
|
user: { isHonest, username }
|
||||||
}) => {
|
}: CertChallengeProps): JSX.Element => {
|
||||||
const [canClaimCert, setCanClaimCert] = useState(false);
|
const [canClaimCert, setCanClaimCert] = useState(false);
|
||||||
const [certVerificationMessage, setCertVerificationMessage] = useState('');
|
const [certVerificationMessage, setCertVerificationMessage] = useState('');
|
||||||
const [isCertified, setIsCertified] = useState(false);
|
const [isCertified, setIsCertified] = useState(false);
|
||||||
@ -88,7 +87,7 @@ const CertChallenge = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (username) {
|
if (username) {
|
||||||
(async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getVerifyCanClaimCert(username, superBlock);
|
const data = await getVerifyCanClaimCert(username, superBlock);
|
||||||
const { status, result } = data?.response?.message;
|
const { status, result } = data?.response?.message;
|
||||||
@ -103,7 +102,8 @@ const CertChallenge = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [username]);
|
}, [username]);
|
||||||
|
|
||||||
const { certSlug } = certMap.find(x => x.title === title);
|
// @ts-expect-error Typescript is confused
|
||||||
|
const certSlug = certMap.find(x => x.title === title).certSlug;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { pending, complete } = fetchState;
|
const { pending, complete } = fetchState;
|
||||||
@ -113,20 +113,26 @@ const CertChallenge = ({
|
|||||||
}
|
}
|
||||||
}, [fetchState]);
|
}, [fetchState]);
|
||||||
|
|
||||||
|
const certSlugTypeMapTyped: { [key: string]: string } = certSlugTypeMap;
|
||||||
|
const superBlockCertTypeMapTyped: { [key: string]: string } =
|
||||||
|
superBlockCertTypeMap;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsCertified(
|
setIsCertified(
|
||||||
steps?.currentCerts?.find(
|
steps?.currentCerts?.find(
|
||||||
cert =>
|
(cert: { certSlug: string }) =>
|
||||||
certSlugTypeMap[cert.certSlug] === superBlockCertTypeMap[superBlock]
|
certSlugTypeMapTyped[cert.certSlug] ===
|
||||||
|
superBlockCertTypeMapTyped[superBlock]
|
||||||
)?.show ?? false
|
)?.show ?? false
|
||||||
);
|
);
|
||||||
|
|
||||||
const projectsCompleted =
|
const projectsCompleted =
|
||||||
canClaimCert || certVerificationMessage === 'projects-completed';
|
canClaimCert || certVerificationMessage === 'projects-completed';
|
||||||
|
const projectsCompletedNumber = projectsCompleted ? 1 : 0;
|
||||||
const completedCount =
|
const completedCount =
|
||||||
Object.values(steps).filter(
|
Object.values(steps).filter(
|
||||||
stepVal => typeof stepVal === 'boolean' && stepVal
|
stepVal => typeof stepVal === 'boolean' && stepVal
|
||||||
).length + projectsCompleted;
|
).length + projectsCompletedNumber;
|
||||||
const numberOfSteps = Object.keys(steps).length;
|
const numberOfSteps = Object.keys(steps).length;
|
||||||
setHasCompletedRequiredSteps(completedCount === numberOfSteps);
|
setHasCompletedRequiredSteps(completedCount === numberOfSteps);
|
||||||
setStepState({ numberOfSteps, completedCount });
|
setStepState({ numberOfSteps, completedCount });
|
||||||
@ -145,7 +151,8 @@ const CertChallenge = ({
|
|||||||
isSignedIn &&
|
isSignedIn &&
|
||||||
(!isCertified || (!hasCompletedRequiredSteps && verificationComplete));
|
(!isCertified || (!hasCompletedRequiredSteps && verificationComplete));
|
||||||
|
|
||||||
const createClickHandler = certSlug => e => {
|
const createClickHandler =
|
||||||
|
(certSlug: string | undefined) => (e: { preventDefault: () => void }) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (isCertified) {
|
if (isCertified) {
|
||||||
return navigate(certLocation);
|
return navigate(certLocation);
|
||||||
@ -188,7 +195,6 @@ const CertChallenge = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
CertChallenge.displayName = 'CertChallenge';
|
CertChallenge.displayName = 'CertChallenge';
|
||||||
CertChallenge.propTypes = propTypes;
|
|
||||||
|
|
||||||
export { CertChallenge };
|
export { CertChallenge };
|
||||||
|
|
@ -1,26 +1,24 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ScrollableAnchor from 'react-scrollable-anchor';
|
import ScrollableAnchor from 'react-scrollable-anchor';
|
||||||
|
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||||
import Caret from '../../../assets/icons/caret';
|
import Caret from '../../../assets/icons/caret';
|
||||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||||
// import { navigate } from 'gatsby';
|
|
||||||
import GreenPass from '../../../assets/icons/green-pass';
|
import GreenPass from '../../../assets/icons/green-pass';
|
||||||
import { StepsPropType } from '../../../redux/prop-types';
|
import { Steps } from '../../../redux/prop-types';
|
||||||
import ClaimCertSteps from './ClaimCertSteps';
|
import ClaimCertSteps from './claim-cert-steps';
|
||||||
|
|
||||||
const propTypes = {
|
interface CertificationCardProps {
|
||||||
i18nCertText: PropTypes.string,
|
i18nCertText: string;
|
||||||
isProjectsCompleted: PropTypes.bool,
|
isProjectsCompleted: boolean;
|
||||||
stepState: PropTypes.shape({
|
stepState: {
|
||||||
numberOfSteps: PropTypes.number,
|
numberOfSteps: number;
|
||||||
completedCount: PropTypes.number
|
completedCount: number;
|
||||||
}),
|
|
||||||
steps: StepsPropType,
|
|
||||||
superBlock: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
steps: Steps;
|
||||||
|
superBlock: SuperBlocks;
|
||||||
|
}
|
||||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||||
|
|
||||||
const CertificationCard = ({
|
const CertificationCard = ({
|
||||||
@ -29,7 +27,7 @@ const CertificationCard = ({
|
|||||||
i18nCertText,
|
i18nCertText,
|
||||||
stepState: { completedCount, numberOfSteps },
|
stepState: { completedCount, numberOfSteps },
|
||||||
steps
|
steps
|
||||||
}) => {
|
}: CertificationCardProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
|
||||||
@ -41,6 +39,10 @@ const CertificationCard = ({
|
|||||||
expand: expandText,
|
expand: expandText,
|
||||||
collapse: collapseText,
|
collapse: collapseText,
|
||||||
courses: coursesText
|
courses: coursesText
|
||||||
|
}: {
|
||||||
|
expand: string;
|
||||||
|
collapse: string;
|
||||||
|
courses: string;
|
||||||
} = t('intro:misc-text');
|
} = t('intro:misc-text');
|
||||||
return (
|
return (
|
||||||
<ScrollableAnchor id='claim-cert-block'>
|
<ScrollableAnchor id='claim-cert-block'>
|
||||||
@ -90,6 +92,5 @@ const CertificationCard = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
CertificationCard.displayName = 'CertStatus';
|
CertificationCard.displayName = 'CertStatus';
|
||||||
CertificationCard.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default CertificationCard;
|
export default CertificationCard;
|
@ -1,28 +1,28 @@
|
|||||||
import { Link } from 'gatsby';
|
import { Link } from 'gatsby';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withTranslation, useTranslation } from 'react-i18next';
|
import { withTranslation, useTranslation } from 'react-i18next';
|
||||||
|
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||||
import GreenPass from '../../../assets/icons/green-pass';
|
import GreenPass from '../../../assets/icons/green-pass';
|
||||||
import { StepsPropType } from '../../../redux/prop-types';
|
import { Steps } from '../../../redux/prop-types';
|
||||||
|
|
||||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||||
|
|
||||||
const propTypes = {
|
interface ClaimCertStepsProps {
|
||||||
i18nCertText: PropTypes.string,
|
i18nCertText: string;
|
||||||
isProjectsCompleted: PropTypes.bool,
|
isProjectsCompleted: boolean;
|
||||||
steps: StepsPropType,
|
steps: Steps;
|
||||||
superBlock: PropTypes.string
|
superBlock: SuperBlocks;
|
||||||
};
|
}
|
||||||
|
|
||||||
const ClaimCertSteps = ({
|
const ClaimCertSteps = ({
|
||||||
isProjectsCompleted,
|
isProjectsCompleted,
|
||||||
i18nCertText,
|
i18nCertText,
|
||||||
steps,
|
steps,
|
||||||
superBlock
|
superBlock
|
||||||
}) => {
|
}: ClaimCertStepsProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const renderCheckMark = isCompleted => {
|
const renderCheckMark = (isCompleted: boolean) => {
|
||||||
return isCompleted ? (
|
return isCompleted ? (
|
||||||
<GreenPass style={mapIconStyle} />
|
<GreenPass style={mapIconStyle} />
|
||||||
) : (
|
) : (
|
||||||
@ -84,6 +84,5 @@ const ClaimCertSteps = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
ClaimCertSteps.displayName = 'ClaimCertSteps';
|
ClaimCertSteps.displayName = 'ClaimCertSteps';
|
||||||
ClaimCertSteps.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default withTranslation()(ClaimCertSteps);
|
export default withTranslation()(ClaimCertSteps);
|
@ -1,19 +1,22 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||||
import { generateIconComponent } from '../../../assets/icons';
|
import { generateIconComponent } from '../../../assets/icons';
|
||||||
import { Spacer } from '../../../components/helpers';
|
import { Spacer } from '../../../components/helpers';
|
||||||
|
|
||||||
const propTypes = {
|
interface SuperBlockIntroProps {
|
||||||
superBlock: PropTypes.string
|
superBlock: SuperBlocks;
|
||||||
};
|
}
|
||||||
|
|
||||||
function SuperBlockIntro(props) {
|
function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { superBlock } = props;
|
const { superBlock } = props;
|
||||||
|
|
||||||
const superBlockIntroObj = t(`intro:${superBlock}`);
|
const superBlockIntroObj: {
|
||||||
|
title: string;
|
||||||
|
intro: string[];
|
||||||
|
note: string[];
|
||||||
|
} = t(`intro:${superBlock}`);
|
||||||
const {
|
const {
|
||||||
title: i18nSuperBlock,
|
title: i18nSuperBlock,
|
||||||
intro: superBlockIntroText,
|
intro: superBlockIntroText,
|
||||||
@ -39,6 +42,5 @@ function SuperBlockIntro(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SuperBlockIntro.displayName = 'SuperBlockIntro';
|
SuperBlockIntro.displayName = 'SuperBlockIntro';
|
||||||
SuperBlockIntro.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default SuperBlockIntro;
|
export default SuperBlockIntro;
|
@ -25,9 +25,9 @@ import {
|
|||||||
userSelector
|
userSelector
|
||||||
} from '../../redux';
|
} from '../../redux';
|
||||||
import { MarkdownRemark, AllChallengeNode, User } from '../../redux/prop-types';
|
import { MarkdownRemark, AllChallengeNode, User } from '../../redux/prop-types';
|
||||||
import Block from './components/Block';
|
import Block from './components/block';
|
||||||
import CertChallenge from './components/CertChallenge';
|
import CertChallenge from './components/cert-challenge';
|
||||||
import SuperBlockIntro from './components/SuperBlockIntro';
|
import SuperBlockIntro from './components/super-block-intro';
|
||||||
import { resetExpansion, toggleBlock } from './redux';
|
import { resetExpansion, toggleBlock } from './redux';
|
||||||
|
|
||||||
import './intro.css';
|
import './intro.css';
|
||||||
|
@ -3,6 +3,7 @@ import envData from '../../../config/env.json';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ChallengeFile,
|
ChallengeFile,
|
||||||
|
ClaimedCertifications,
|
||||||
CompletedChallenge,
|
CompletedChallenge,
|
||||||
User
|
User
|
||||||
} from '../redux/prop-types';
|
} from '../redux/prop-types';
|
||||||
@ -158,11 +159,25 @@ export function getUsernameExists(username: string): Promise<boolean> {
|
|||||||
return get(`/api/users/exists?username=${username}`);
|
return get(`/api/users/exists?username=${username}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Does a GET return a bolean?
|
export interface GetVerifyCanClaimCert {
|
||||||
|
response: {
|
||||||
|
type: string;
|
||||||
|
message: {
|
||||||
|
status: boolean;
|
||||||
|
result: string;
|
||||||
|
};
|
||||||
|
variables: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
isCertMap: ClaimedCertifications;
|
||||||
|
completedChallenges: CompletedChallenge[];
|
||||||
|
}
|
||||||
|
|
||||||
export function getVerifyCanClaimCert(
|
export function getVerifyCanClaimCert(
|
||||||
username: string,
|
username: string,
|
||||||
superBlock: string
|
superBlock: string
|
||||||
): Promise<boolean> {
|
): Promise<GetVerifyCanClaimCert> {
|
||||||
return get(
|
return get(
|
||||||
`/certificate/verify-can-claim-cert?username=${username}&superBlock=${superBlock}`
|
`/certificate/verify-can-claim-cert?username=${username}&superBlock=${superBlock}`
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user