2021-06-15 10:37:13 -05:00
|
|
|
// Package Utilities
|
2022-02-11 09:39:27 -06:00
|
|
|
import { Grid, Col, Row, Button } from '@freecodecamp/react-bootstrap';
|
2021-08-02 15:39:40 +02:00
|
|
|
import { graphql } from 'gatsby';
|
2021-06-15 10:37:13 -05:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import Helmet from 'react-helmet';
|
2022-02-11 09:39:27 -06:00
|
|
|
import { TFunction, Trans, withTranslation } from 'react-i18next';
|
2021-08-02 15:39:40 +02:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { bindActionCreators } from 'redux';
|
2021-06-25 09:59:33 -05:00
|
|
|
import type { Dispatch } from 'redux';
|
2021-11-17 08:19:24 -06:00
|
|
|
import { createSelector } from 'reselect';
|
2021-06-15 10:37:13 -05:00
|
|
|
|
|
|
|
// Local Utilities
|
2022-02-11 09:39:27 -06:00
|
|
|
import Spacer from '../../../components/helpers/spacer';
|
2021-06-29 19:31:22 +05:30
|
|
|
import LearnLayout from '../../../components/layouts/learn';
|
2022-02-11 09:39:27 -06:00
|
|
|
import ChallengeTitle from '../components/challenge-title';
|
|
|
|
import PrismFormatted from '../components/prism-formatted';
|
|
|
|
import { challengeTypes } from '../../../../utils/challenge-types';
|
|
|
|
import CompletionModal from '../components/completion-modal';
|
|
|
|
import GreenPass from '../../../assets/icons/green-pass';
|
|
|
|
import HelpModal from '../components/help-modal';
|
|
|
|
import Hotkeys from '../components/Hotkeys';
|
|
|
|
import {
|
|
|
|
completedChallengesSelector,
|
|
|
|
isSignedInSelector,
|
2022-03-11 15:58:23 -06:00
|
|
|
hideCodeAlly,
|
2022-02-11 09:39:27 -06:00
|
|
|
partiallyCompletedChallengesSelector,
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAllySelector,
|
|
|
|
tryToShowCodeAlly,
|
|
|
|
userTokenSelector
|
2022-02-11 09:39:27 -06:00
|
|
|
} from '../../../redux';
|
|
|
|
import {
|
|
|
|
challengeMounted,
|
|
|
|
isChallengeCompletedSelector,
|
|
|
|
updateChallengeMeta,
|
|
|
|
openModal,
|
|
|
|
updateSolutionFormValues
|
|
|
|
} from '../redux';
|
|
|
|
import { createFlashMessage } from '../../../components/Flash/redux';
|
|
|
|
import {
|
|
|
|
ChallengeNode,
|
|
|
|
ChallengeMeta,
|
|
|
|
CompletedChallenge
|
|
|
|
} from '../../../redux/prop-types';
|
|
|
|
import ProjectToolPanel from '../projects/tool-panel';
|
|
|
|
import SolutionForm from '../projects/solution-form';
|
|
|
|
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
|
|
|
|
|
|
|
import './codeally.css';
|
2021-11-17 08:19:24 -06:00
|
|
|
|
2022-02-11 09:39:27 -06:00
|
|
|
// Redux
|
2021-11-17 08:19:24 -06:00
|
|
|
const mapStateToProps = createSelector(
|
2022-02-11 09:39:27 -06:00
|
|
|
completedChallengesSelector,
|
|
|
|
isChallengeCompletedSelector,
|
|
|
|
isSignedInSelector,
|
|
|
|
partiallyCompletedChallengesSelector,
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAllySelector,
|
|
|
|
userTokenSelector,
|
2022-02-11 09:39:27 -06:00
|
|
|
(
|
|
|
|
completedChallenges: CompletedChallenge[],
|
|
|
|
isChallengeCompleted: boolean,
|
|
|
|
isSignedIn: boolean,
|
|
|
|
partiallyCompletedChallenges: CompletedChallenge[],
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAlly: boolean,
|
|
|
|
userToken: string | null
|
2022-02-11 09:39:27 -06:00
|
|
|
) => ({
|
|
|
|
completedChallenges,
|
|
|
|
isChallengeCompleted,
|
|
|
|
isSignedIn,
|
|
|
|
partiallyCompletedChallenges,
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAlly,
|
|
|
|
userToken
|
2021-11-17 08:19:24 -06:00
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2021-06-25 09:59:33 -05:00
|
|
|
const mapDispatchToProps = (dispatch: Dispatch) =>
|
2021-06-15 10:37:13 -05:00
|
|
|
bindActionCreators(
|
|
|
|
{
|
2022-02-11 09:39:27 -06:00
|
|
|
challengeMounted,
|
|
|
|
createFlashMessage,
|
2022-03-11 15:58:23 -06:00
|
|
|
hideCodeAlly,
|
2022-02-11 09:39:27 -06:00
|
|
|
openCompletionModal: () => openModal('completion'),
|
2022-03-11 15:58:23 -06:00
|
|
|
tryToShowCodeAlly,
|
2021-06-15 10:37:13 -05:00
|
|
|
updateChallengeMeta,
|
2022-02-11 09:39:27 -06:00
|
|
|
updateSolutionFormValues
|
2021-06-15 10:37:13 -05:00
|
|
|
},
|
|
|
|
dispatch
|
|
|
|
);
|
|
|
|
|
2021-06-25 09:59:33 -05:00
|
|
|
// Types
|
|
|
|
interface ShowCodeAllyProps {
|
2022-02-11 09:39:27 -06:00
|
|
|
challengeMounted: (arg0: string) => void;
|
|
|
|
completedChallenges: CompletedChallenge[];
|
|
|
|
createFlashMessage: typeof createFlashMessage;
|
2021-11-09 19:51:46 +05:30
|
|
|
data: { challengeNode: ChallengeNode };
|
2022-03-11 15:58:23 -06:00
|
|
|
hideCodeAlly: () => void;
|
2022-02-11 09:39:27 -06:00
|
|
|
isChallengeCompleted: boolean;
|
|
|
|
isSignedIn: boolean;
|
|
|
|
openCompletionModal: () => void;
|
2021-06-25 09:59:33 -05:00
|
|
|
pageContext: {
|
2021-11-09 19:51:46 +05:30
|
|
|
challengeMeta: ChallengeMeta;
|
2021-06-25 09:59:33 -05:00
|
|
|
};
|
2022-02-11 09:39:27 -06:00
|
|
|
partiallyCompletedChallenges: CompletedChallenge[];
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAlly: boolean;
|
2022-02-11 09:39:27 -06:00
|
|
|
t: TFunction;
|
2022-03-11 15:58:23 -06:00
|
|
|
tryToShowCodeAlly: () => void;
|
2021-11-09 19:51:46 +05:30
|
|
|
updateChallengeMeta: (arg0: ChallengeMeta) => void;
|
2022-02-11 09:39:27 -06:00
|
|
|
updateSolutionFormValues: () => void;
|
2022-03-11 15:58:23 -06:00
|
|
|
userToken: string | null;
|
2022-02-11 09:39:27 -06:00
|
|
|
}
|
|
|
|
|
2021-06-15 10:37:13 -05:00
|
|
|
// Component
|
2022-03-11 15:58:23 -06:00
|
|
|
class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
2021-06-25 09:59:33 -05:00
|
|
|
static displayName: string;
|
2022-02-11 09:39:27 -06:00
|
|
|
private _container: HTMLElement | null = null;
|
|
|
|
|
2021-06-25 09:59:33 -05:00
|
|
|
componentDidMount(): void {
|
2021-06-15 10:37:13 -05:00
|
|
|
const {
|
2022-02-11 09:39:27 -06:00
|
|
|
challengeMounted,
|
2021-06-15 10:37:13 -05:00
|
|
|
data: {
|
2021-12-14 19:11:20 +01:00
|
|
|
challengeNode: {
|
2022-02-18 10:29:30 -06:00
|
|
|
challenge: { challengeType, helpCategory, title }
|
2021-12-14 19:11:20 +01:00
|
|
|
}
|
2021-06-15 10:37:13 -05:00
|
|
|
},
|
2022-02-18 10:29:30 -06:00
|
|
|
pageContext: { challengeMeta },
|
|
|
|
updateChallengeMeta
|
2021-06-15 10:37:13 -05:00
|
|
|
} = this.props;
|
2022-02-18 10:29:30 -06:00
|
|
|
updateChallengeMeta({
|
|
|
|
...challengeMeta,
|
|
|
|
title,
|
|
|
|
challengeType,
|
|
|
|
helpCategory
|
|
|
|
});
|
2022-02-11 09:39:27 -06:00
|
|
|
challengeMounted(challengeMeta.id);
|
|
|
|
this._container?.focus();
|
2021-06-15 10:37:13 -05:00
|
|
|
}
|
|
|
|
|
2022-03-11 15:58:23 -06:00
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.hideCodeAlly();
|
|
|
|
}
|
2022-02-11 09:39:27 -06:00
|
|
|
|
|
|
|
handleSubmit = ({
|
|
|
|
showCompletionModal
|
|
|
|
}: {
|
|
|
|
showCompletionModal: boolean;
|
|
|
|
}) => {
|
|
|
|
const {
|
|
|
|
completedChallenges,
|
|
|
|
createFlashMessage,
|
|
|
|
data: {
|
|
|
|
challengeNode: {
|
|
|
|
challenge: { id: challengeId }
|
|
|
|
}
|
|
|
|
},
|
2022-03-11 15:58:23 -06:00
|
|
|
openCompletionModal,
|
2022-02-11 09:39:27 -06:00
|
|
|
partiallyCompletedChallenges
|
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
const isPartiallyCompleted = partiallyCompletedChallenges.some(
|
|
|
|
challenge => challenge.id === challengeId
|
|
|
|
);
|
|
|
|
|
|
|
|
const isCompleted = completedChallenges.some(
|
|
|
|
challenge => challenge.id === challengeId
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!isPartiallyCompleted && !isCompleted) {
|
|
|
|
createFlashMessage({
|
|
|
|
type: 'danger',
|
|
|
|
message: FlashMessages.CompleteProjectFirst
|
|
|
|
});
|
|
|
|
} else if (showCompletionModal) {
|
2022-03-11 15:58:23 -06:00
|
|
|
openCompletionModal();
|
2022-02-11 09:39:27 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-15 10:37:13 -05:00
|
|
|
render() {
|
|
|
|
const {
|
2022-02-11 09:39:27 -06:00
|
|
|
completedChallenges,
|
2021-11-17 08:19:24 -06:00
|
|
|
data: {
|
|
|
|
challengeNode: {
|
2021-12-14 19:11:20 +01:00
|
|
|
challenge: {
|
2022-02-11 09:39:27 -06:00
|
|
|
block,
|
|
|
|
certification,
|
|
|
|
challengeType,
|
|
|
|
description,
|
2021-12-14 19:11:20 +01:00
|
|
|
fields: { blockName },
|
2022-02-11 09:39:27 -06:00
|
|
|
id: challengeId,
|
|
|
|
instructions,
|
|
|
|
notes,
|
|
|
|
superBlock,
|
|
|
|
title,
|
|
|
|
translationPending,
|
2021-12-14 19:11:20 +01:00
|
|
|
url
|
|
|
|
}
|
2021-11-17 08:19:24 -06:00
|
|
|
}
|
|
|
|
},
|
2022-02-11 09:39:27 -06:00
|
|
|
isChallengeCompleted,
|
|
|
|
isSignedIn,
|
|
|
|
pageContext: {
|
|
|
|
challengeMeta: { nextChallengePath, prevChallengePath }
|
|
|
|
},
|
|
|
|
partiallyCompletedChallenges,
|
2022-03-11 15:58:23 -06:00
|
|
|
showCodeAlly,
|
2022-02-11 09:39:27 -06:00
|
|
|
t,
|
2022-03-11 15:58:23 -06:00
|
|
|
tryToShowCodeAlly,
|
2022-02-11 09:39:27 -06:00
|
|
|
updateSolutionFormValues,
|
2022-03-11 15:58:23 -06:00
|
|
|
userToken = null
|
2021-11-17 08:19:24 -06:00
|
|
|
} = this.props;
|
|
|
|
|
2022-03-11 15:58:23 -06:00
|
|
|
const envVariables = userToken
|
|
|
|
? `&envVariables=CODEROAD_WEBHOOK_TOKEN=${userToken}`
|
2021-11-17 08:19:24 -06:00
|
|
|
: '';
|
2021-06-15 10:37:13 -05:00
|
|
|
|
2022-02-11 09:39:27 -06:00
|
|
|
const isPartiallyCompleted = partiallyCompletedChallenges.some(
|
|
|
|
challenge => challenge.id === challengeId
|
|
|
|
);
|
|
|
|
|
|
|
|
const isCompleted = completedChallenges.some(
|
|
|
|
challenge => challenge.id === challengeId
|
|
|
|
);
|
|
|
|
|
2022-03-11 15:58:23 -06:00
|
|
|
return showCodeAlly ? (
|
2021-06-15 10:37:13 -05:00
|
|
|
<LearnLayout>
|
|
|
|
<Helmet title={`${blockName}: ${title} | freeCodeCamp.org`} />
|
|
|
|
<iframe
|
2021-09-23 17:31:56 +02:00
|
|
|
className='codeally-frame'
|
2021-06-25 09:59:33 -05:00
|
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
2021-09-23 17:31:56 +02:00
|
|
|
sandbox='allow-modals allow-forms allow-popups allow-scripts allow-same-origin'
|
2021-11-17 08:19:24 -06:00
|
|
|
src={`https://codeally.io/embed/?repoUrl=${url}${envVariables}`}
|
2021-06-15 10:37:13 -05:00
|
|
|
title='Editor'
|
|
|
|
/>
|
|
|
|
</LearnLayout>
|
2022-02-11 09:39:27 -06:00
|
|
|
) : (
|
|
|
|
<Hotkeys
|
|
|
|
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
|
|
|
nextChallengePath={nextChallengePath}
|
|
|
|
prevChallengePath={prevChallengePath}
|
|
|
|
>
|
|
|
|
<LearnLayout>
|
|
|
|
<Helmet title={`${blockName}: ${title} | freeCodeCamp.org`} />
|
|
|
|
<Grid>
|
|
|
|
<Row>
|
|
|
|
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
|
|
|
<Spacer />
|
|
|
|
<ChallengeTitle
|
|
|
|
block={block}
|
|
|
|
isCompleted={isChallengeCompleted}
|
|
|
|
superBlock={superBlock}
|
|
|
|
translationPending={translationPending}
|
|
|
|
>
|
|
|
|
{title}
|
|
|
|
</ChallengeTitle>
|
|
|
|
<Spacer />
|
|
|
|
<PrismFormatted text={description} />
|
|
|
|
<Spacer />
|
|
|
|
<div className='ca-description'>
|
|
|
|
<Trans i18nKey='learn.github-required'>
|
|
|
|
<a
|
2022-03-04 08:15:20 -06:00
|
|
|
href='https://github.com'
|
2022-02-11 09:39:27 -06:00
|
|
|
rel='noopener noreferrer'
|
|
|
|
target='_blank'
|
|
|
|
title={t('learn.github-link')}
|
|
|
|
>
|
|
|
|
placeholder
|
|
|
|
</a>
|
|
|
|
</Trans>
|
|
|
|
</div>
|
|
|
|
<Spacer />
|
|
|
|
{isSignedIn && challengeType === challengeTypes.codeAllyCert && (
|
|
|
|
<>
|
|
|
|
<div className='ca-description'>
|
|
|
|
{t('learn.complete-both-steps')}
|
|
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<Spacer />
|
|
|
|
<b>{t('learn.step-1')}</b>
|
|
|
|
{(isPartiallyCompleted || isCompleted) && (
|
|
|
|
<GreenPass
|
|
|
|
style={{
|
|
|
|
height: '15px',
|
|
|
|
width: '15px',
|
|
|
|
marginLeft: '7px'
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<Spacer />
|
|
|
|
<div className='ca-description'>
|
|
|
|
{t('learn.runs-in-vm')}
|
|
|
|
</div>
|
|
|
|
<Spacer />
|
|
|
|
<PrismFormatted text={instructions} />
|
|
|
|
<Spacer />
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<div
|
|
|
|
className={`ca-btn-padding ${
|
|
|
|
!isSignedIn ||
|
|
|
|
challengeType === challengeTypes.codeAllyPractice
|
|
|
|
? 'ca-btn-margin'
|
|
|
|
: ''
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<Button
|
|
|
|
block={true}
|
|
|
|
bsStyle='primary'
|
2022-03-11 15:58:23 -06:00
|
|
|
onClick={tryToShowCodeAlly}
|
2022-02-11 09:39:27 -06:00
|
|
|
>
|
|
|
|
{challengeType === challengeTypes.codeAllyCert
|
|
|
|
? t('buttons.click-start-project')
|
|
|
|
: t('buttons.click-start-course')}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{isSignedIn && challengeType === challengeTypes.codeAllyCert && (
|
|
|
|
<>
|
|
|
|
<hr />
|
|
|
|
<Spacer />
|
|
|
|
<b>{t('learn.step-2')}</b>
|
|
|
|
{isCompleted && (
|
|
|
|
<GreenPass
|
|
|
|
style={{
|
|
|
|
height: '15px',
|
|
|
|
width: '15px',
|
|
|
|
marginLeft: '7px'
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<Spacer />
|
|
|
|
<div className='ca-description'>
|
|
|
|
{t('learn.submit-public-url')}
|
|
|
|
</div>
|
|
|
|
<Spacer />
|
|
|
|
<PrismFormatted text={notes} />
|
|
|
|
<Spacer />
|
|
|
|
<SolutionForm
|
|
|
|
challengeType={challengeType}
|
|
|
|
description={description}
|
|
|
|
onSubmit={this.handleSubmit}
|
|
|
|
updateSolutionForm={updateSolutionFormValues}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<ProjectToolPanel />
|
|
|
|
<br />
|
|
|
|
<Spacer />
|
|
|
|
</Col>
|
|
|
|
<CompletionModal
|
|
|
|
block={block}
|
|
|
|
blockName={blockName}
|
|
|
|
certification={certification}
|
|
|
|
superBlock={superBlock}
|
|
|
|
/>
|
|
|
|
<HelpModal />
|
|
|
|
</Row>
|
|
|
|
</Grid>
|
|
|
|
</LearnLayout>
|
|
|
|
</Hotkeys>
|
2021-06-15 10:37:13 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ShowCodeAlly.displayName = 'ShowCodeAlly';
|
|
|
|
|
2022-02-11 09:39:27 -06:00
|
|
|
export default connect(
|
|
|
|
mapStateToProps,
|
|
|
|
mapDispatchToProps
|
|
|
|
)(withTranslation()(ShowCodeAlly));
|
2021-06-15 10:37:13 -05:00
|
|
|
|
|
|
|
// GraphQL
|
|
|
|
export const query = graphql`
|
|
|
|
query CodeAllyChallenge($slug: String!) {
|
2021-12-14 19:11:20 +01:00
|
|
|
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
|
|
|
challenge {
|
2022-02-11 09:39:27 -06:00
|
|
|
block
|
|
|
|
certification
|
2021-12-14 19:11:20 +01:00
|
|
|
challengeType
|
2022-02-11 09:39:27 -06:00
|
|
|
description
|
2021-12-14 19:11:20 +01:00
|
|
|
fields {
|
|
|
|
blockName
|
|
|
|
}
|
2022-02-11 09:39:27 -06:00
|
|
|
helpCategory
|
|
|
|
id
|
|
|
|
instructions
|
|
|
|
notes
|
|
|
|
superBlock
|
|
|
|
title
|
|
|
|
translationPending
|
|
|
|
url
|
2021-06-15 10:37:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|