feat(client): pull percentage challenge data from redux (#49308)

* feat(client): pull percentage challenge data from redux

* Apply suggestions from code review

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>

* feat: update updateAllChallengesInfo name

---------

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Ahmad Abdolsaheb
2023-02-10 16:16:11 +03:00
committed by GitHub
parent ffc97f15d1
commit 6e53c852de
7 changed files with 99 additions and 55 deletions

View File

@@ -4,6 +4,7 @@ import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect';
import { useStaticQuery, graphql } from 'gatsby';
import latoBoldURL from '../../../static/fonts/lato/Lato-Bold.woff';
import latoLightURL from '../../../static/fonts/lato/Lato-Light.woff';
@@ -16,7 +17,8 @@ import { isBrowser } from '../../../utils';
import {
fetchUser,
onlineStatusChange,
serverStatusChange
serverStatusChange,
updateAllChallengesInfo
} from '../../redux/actions';
import {
isSignedInSelector,
@@ -26,7 +28,12 @@ import {
userFetchStateSelector
} from '../../redux/selectors';
import { UserFetchState, User } from '../../redux/prop-types';
import {
UserFetchState,
User,
AllChallengeNode,
CertificateNode
} from '../../redux/prop-types';
import BreadCrumb from '../../templates/Challenges/components/bread-crumb';
import Flash from '../Flash';
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
@@ -77,7 +84,8 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
fetchUser,
removeFlashMessage,
onlineStatusChange,
serverStatusChange
serverStatusChange,
updateAllChallengesInfo
},
dispatch
);
@@ -117,10 +125,13 @@ function DefaultLayout({
t,
theme = Themes.Default,
user,
fetchUser
fetchUser,
updateAllChallengesInfo
}: DefaultLayoutProps): JSX.Element {
const { challengeEdges, certificateNodes } = useGetAllBlockIds();
useEffect(() => {
// componentDidMount
updateAllChallengesInfo({ challengeEdges, certificateNodes });
if (!isSignedIn) {
fetchUser();
}
@@ -237,6 +248,50 @@ function DefaultLayout({
}
}
// TODO: get challenge nodes directly rather than wrapped in edges
const useGetAllBlockIds = () => {
const {
allChallengeNode: { edges: challengeEdges },
allCertificateNode: { nodes: certificateNodes }
}: {
allChallengeNode: AllChallengeNode;
allCertificateNode: { nodes: CertificateNode[] };
} = useStaticQuery(graphql`
query getBlockNode {
allChallengeNode(
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges {
node {
challenge {
block
id
}
}
}
}
allCertificateNode {
nodes {
challenge {
certification
tests {
id
}
}
}
}
}
`);
return { challengeEdges, certificateNodes };
};
DefaultLayout.displayName = 'DefaultLayout';
export default connect(

View File

@@ -27,6 +27,7 @@ export const actionTypes = createTypes(
'updateDonationFormState',
'updateUserToken',
'postChargeProcessing',
'updateAllChallengesInfo',
...createAsyncTypes('fetchUser'),
...createAsyncTypes('postCharge'),
...createAsyncTypes('fetchProfileForUser'),

View File

@@ -53,6 +53,10 @@ export const fetchUser = createAction(actionTypes.fetchUser);
export const fetchUserComplete = createAction(actionTypes.fetchUserComplete);
export const fetchUserError = createAction(actionTypes.fetchUserError);
export const updateAllChallengesInfo = createAction(
actionTypes.updateAllChallengesInfo
);
export const postCharge = createAction(actionTypes.postCharge);
export const postChargeProcessing = createAction(
actionTypes.postChargeProcessing

View File

@@ -56,6 +56,10 @@ const initialState = {
userFetchState: {
...defaultFetchState
},
allChallengesInfo: {
challengeEdges: [],
certificateNodes: []
},
userProfileFetchState: {
...defaultFetchState
},
@@ -153,7 +157,10 @@ export const reducer = handleActions(
...state,
donationFormState: { ...defaultDonationFormState, error: payload }
}),
[actionTypes.updateAllChallengesInfo]: (state, { payload }) => ({
...state,
allChallengesInfo: { ...payload }
}),
[actionTypes.fetchUser]: state => ({
...state,
userFetchState: { ...defaultFetchState }

View File

@@ -142,6 +142,19 @@ export type ChallengeNode = {
};
};
export type CertificateNode = {
challenge: {
// TODO: use enum
certification: string;
tests: { id: string }[];
};
};
export type AllChallengesInfo = {
challengeEdges: { node: ChallengeNode }[];
certificateNodes: CertificateNode[];
};
export type AllChallengeNode = {
edges: [
{

View File

@@ -202,6 +202,8 @@ export const certificatesByNameSelector = username => state => {
};
export const userFetchStateSelector = state => state[MainApp].userFetchState;
export const allChallengesInfoSelector = state =>
state[MainApp].allChallengesInfo;
export const userProfileFetchStateSelector = state =>
state[MainApp].userProfileFetchState;
export const usernameSelector = state => state[MainApp].appUsername;

View File

@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { Button, Modal } from '@freecodecamp/react-bootstrap';
import { useStaticQuery, graphql } from 'gatsby';
import { noop } from 'lodash-es';
import React, { Component } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
@@ -13,8 +12,11 @@ import { dasherize } from '../../../../../utils/slugs';
import { isFinalProject } from '../../../../utils/challenge-types';
import Login from '../../../components/Header/components/Login';
import { executeGA, allowBlockDonationRequests } from '../../../redux/actions';
import { isSignedInSelector } from '../../../redux/selectors';
import { AllChallengeNode, ChallengeFiles } from '../../../redux/prop-types';
import {
isSignedInSelector,
allChallengesInfoSelector
} from '../../../redux/selectors';
import { AllChallengesInfo, ChallengeFiles } from '../../../redux/prop-types';
import { closeModal, submitChallenge } from '../redux/actions';
import {
@@ -34,6 +36,7 @@ const mapStateToProps = createSelector(
completedChallengesIds,
isCompletionModalOpenSelector,
isSignedInSelector,
allChallengesInfoSelector,
successMessageSelector,
(
challengeFiles: ChallengeFiles,
@@ -45,6 +48,7 @@ const mapStateToProps = createSelector(
completedChallengesIds: string[],
isOpen: boolean,
isSignedIn: boolean,
allChallengesInfo: AllChallengesInfo,
message: string
) => ({
challengeFiles,
@@ -54,6 +58,7 @@ const mapStateToProps = createSelector(
completedChallengesIds,
isOpen,
isSignedIn,
allChallengesInfo,
message
})
);
@@ -118,6 +123,7 @@ interface CompletionModalsProps {
id: string;
isOpen: boolean;
isSignedIn: boolean;
allChallengesInfo: AllChallengesInfo;
message: string;
submitChallenge: () => void;
superBlock: string;
@@ -324,58 +330,13 @@ interface Options {
isFinalProjectBlock: boolean;
}
interface CertificateNode {
challenge: {
// TODO: use enum
certification: string;
tests: { id: string }[];
};
}
const useCurrentBlockIds = (
allChallengesInfo: AllChallengesInfo,
block: string,
certification: string,
options?: Options
) => {
const {
allChallengeNode: { edges: challengeEdges },
allCertificateNode: { nodes: certificateNodes }
}: {
allChallengeNode: AllChallengeNode;
allCertificateNode: { nodes: CertificateNode[] };
} = useStaticQuery(graphql`
query getCurrentBlockNodes {
allChallengeNode(
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges {
node {
challenge {
block
id
}
}
}
}
allCertificateNode {
nodes {
challenge {
certification
tests {
id
}
}
}
}
}
`);
const { challengeEdges, certificateNodes } = allChallengesInfo;
const currentCertificateIds = certificateNodes
.filter(
node => dasherize(node.challenge.certification) === certification
@@ -390,6 +351,7 @@ const useCurrentBlockIds = (
const CompletionModal = (props: CompletionModalsProps) => {
const currentBlockIds = useCurrentBlockIds(
props.allChallengesInfo,
props.block || '',
props.certification || '',
// eslint-disable-next-line @typescript-eslint/no-unsafe-call