fix: webhook process (#45385)

* fix: token rework functional

fix: clean up

fix: more clean up

fix: more clean up

fix: add widget back to settings

fix:

fix:

fix: cypress

Apply suggestions from code review

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

fix: use flash enum

* chore: rename webhookToken -> userToken

fix: add translations I forgot to save

* fix: add missing tones for flash messages

* fix: node test
This commit is contained in:
Tom
2022-03-11 15:58:23 -06:00
committed by GitHub
parent 692605de3a
commit 9e5f9b2a7c
24 changed files with 370 additions and 434 deletions

View File

@@ -520,10 +520,11 @@
"provide-username": "Check if you have provided a username and a report",
"report-sent": "A report was sent to the team with {{email}} in copy",
"certificate-missing": "The certification you tried to view does not exist",
"create-token-err": "An error occurred trying to create a token",
"delete-token-err": "An error occurred trying to delete your token",
"token-created": "You have successfully created a new token.",
"token-deleted": "Your token has been deleted.",
"create-token-err": "An error occurred while creating your user token",
"delete-token-err": "An error occurred while deleting your user token",
"token-created": "You have successfully created a new user token.",
"token-deleted": "Your user token has been deleted.",
"start-project-err": "Something went wrong trying to start the project. Please try again.",
"complete-project-first": "You must complete the project first.",
"local-code-save-error": "Oops, your code did not save, your browser's local storage may be full.",
"local-code-saved": "Saved! Your code was saved to your browser's local storage."
@@ -656,14 +657,14 @@
"add-code-two": "Please leave the ``` line above and the ``` line below,",
"add-code-three": "because they allow your code to properly format in the post."
},
"webhook-token": {
"title": "Webhook Token",
"user-token": {
"title": "User Token",
"create": "Create a new token",
"create-p1": "It looks like you don't have a webhook token. Create one to save your progress on this section",
"create-p2": "Create a webhook token to save your progress on the curriculum sections that use a virtual machine.",
"delete": "Delete my token",
"delete-title": "Delete My Webhook Token",
"delete-p1": "Your webhook token below is used to save your progress on the curriculum sections that use a virtual machine.",
"create-p1": "It looks like you don't have a user token. Create one to save your progress on this section",
"create-p2": "Create a user token to save your progress on the curriculum sections that use a virtual machine.",
"delete": "Delete my user token",
"delete-title": "Delete My User Token",
"delete-p1": "Your user token is used to save your progress on curriculum sections that use a virtual machine. If you suspect it has been compromised, you can delete it without losing any progress. A new one will be created automatically the next time you open a project.",
"delete-p2": "If you suspect your token has been compromised, you can delete it to make it unusable. Progress on previously submitted lessons will not be lost.",
"delete-p3": "You will need to create a new token to save future progress on the curriculum sections that use a virtual machine.",
"no-thanks": "No thanks, I would like to keep my token",

View File

@@ -17,17 +17,18 @@ import Internet from '../components/settings/internet';
import Portfolio from '../components/settings/portfolio';
import Privacy from '../components/settings/privacy';
import { Themes } from '../components/settings/theme';
import WebhookToken from '../components/settings/webhook-token';
import UserToken from '../components/settings/user-token';
import {
signInLoadingSelector,
userSelector,
isSignedInSelector,
hardGoTo as navigate
hardGoTo as navigate,
userTokenSelector
} from '../redux';
import { User } from '../redux/prop-types';
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
const { apiLocation, deploymentEnv } = envData;
const { apiLocation } = envData;
// TODO: update types for actions
interface ShowSettingsProps {
@@ -45,16 +46,19 @@ interface ShowSettingsProps {
user: User;
verifyCert: () => void;
path?: string;
userToken: string | null;
}
const mapStateToProps = createSelector(
signInLoadingSelector,
userSelector,
isSignedInSelector,
(showLoading: boolean, user: User, isSignedIn) => ({
userTokenSelector,
(showLoading: boolean, user: User, isSignedIn, userToken: string | null) => ({
showLoading,
user,
isSignedIn
isSignedIn,
userToken
})
);
@@ -122,7 +126,8 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element {
updateInternetSettings,
updatePortfolio,
updateIsHonest,
verifyCert
verifyCert,
userToken
} = props;
if (showLoading) {
@@ -202,8 +207,12 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element {
username={username}
verifyCert={verifyCert}
/>
{deploymentEnv == 'staging' && <Spacer />}
{deploymentEnv == 'staging' && <WebhookToken />}
{userToken && (
<>
<Spacer />
<UserToken />
</>
)}
<Spacer />
<DangerZone />
</main>

View File

@@ -6,7 +6,6 @@ export enum FlashMessages {
CertificateMissing = 'flash.certificate-missing',
CertsPrivate = 'flash.certs-private',
CompleteProjectFirst = 'flash.complete-project-first',
CreateTokenErr = 'flash.create-token-err',
DeleteTokenErr = 'flash.delete-token-err',
EmailValid = 'flash.email-valid',
HonestFirst = 'flash.honest-first',
@@ -24,7 +23,7 @@ export enum FlashMessages {
ReallyWeird = 'flash.really-weird',
ReportSent = 'flash.report-sent',
SigninSuccess = 'flash.signin-success',
TokenCreated = 'flash.token-created',
StartProjectErr = 'flash.start-project-err',
TokenDeleted = 'flash.token-deleted',
UpdatedPreferences = 'flash.updated-preferences',
UsernameNotFound = 'flash.username-not-found',

View File

@@ -1,21 +1,7 @@
.webhook-panel {
.user-panel {
border-color: var(--highlight-background);
}
.webhook-token-input {
display: block;
width: 100%;
padding: 3px 8px;
background-color: var(--primary-background);
border-color: var(--highlight-background);
border-style: solid;
color: var(--highlight-color);
}
.webhook-token-input:focus {
border-color: var(--highlight-color);
}
.btn-info {
background-color: var(--highlight-color);
color: var(--highlight-background);
@@ -29,17 +15,17 @@
border-color: var(--highlight-background);
}
.webhook-token .panel-heading {
.user-token .panel-heading {
color: var(--highlight-color);
background-color: var(--highlight-background);
border-radius: 0;
border: none;
}
.webhook-token .panel-info {
.user-token .panel-info {
border-color: var(--highlight-background);
}
.webhook-token p {
.user-token p {
color: var(--highlight-color);
}

View File

@@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Button, Panel } from '@freecodecamp/react-bootstrap';
import React, { Component } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { deleteUserToken } from '../../redux';
import { ButtonSpacer, FullWidthRow, Spacer } from '../helpers';
import './user-token.css';
type UserTokenProps = {
deleteUserToken: () => void;
t: TFunction;
};
const mapDispatchToProps = {
deleteUserToken
};
class UserToken extends Component<UserTokenProps> {
static displayName: string;
deleteToken = () => {
this.props.deleteUserToken();
};
render() {
const { t } = this.props;
return (
<div className='user-token text-center'>
<FullWidthRow>
<Panel className='user-panel'>
<Panel.Heading>{t('user-token.title')}</Panel.Heading>
<Spacer />
<p>{t('user-token.delete-p1')}</p>
<FullWidthRow>
<ButtonSpacer />
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-info'
onClick={this.deleteToken}
type='button'
>
{t('user-token.delete')}
</Button>
<Spacer />
</FullWidthRow>
</Panel>
</FullWidthRow>
</div>
);
}
}
UserToken.displayName = 'UserToken';
export default connect(null, mapDispatchToProps)(withTranslation()(UserToken));

View File

@@ -1,66 +0,0 @@
import { Modal, Button } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ButtonSpacer } from '../helpers';
type WebhookDeleteModalProps = {
onHide: () => void;
deleteFunction: () => void;
show: boolean;
};
function WebhookDeleteModal(props: WebhookDeleteModalProps): JSX.Element {
const { t } = useTranslation();
const { show, onHide, deleteFunction } = props;
return (
<Modal
aria-labelledby='modal-title'
backdrop={true}
bsSize='lg'
className='text-center'
keyboard={true}
onHide={onHide}
show={show}
>
<Modal.Header closeButton={true}>
<Modal.Title id='modal-title'>
{t('webhook-token.delete-title')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>{t('webhook-token.delete-p2')}</p>
<p>{t('webhook-token.delete-p3')}</p>
<hr />
<Button
block={true}
bsSize='lg'
bsStyle='primary'
className='btn-invert'
onClick={props.onHide}
type='button'
>
{t('webhook-token.no-thanks')}
</Button>
<ButtonSpacer />
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={deleteFunction}
type='button'
>
{t('webhook-token.yes-please')}
</Button>
</Modal.Body>
<Modal.Footer>
<Button onClick={props.onHide}>{t('buttons.close')}</Button>
</Modal.Footer>
</Modal>
);
}
WebhookDeleteModal.displayName = 'WebhookDeleteModal';
export default WebhookDeleteModal;

View File

@@ -1,154 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Button, Panel } from '@freecodecamp/react-bootstrap';
import React, { Component } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import {
postWebhookToken,
deleteWebhookToken,
webhookTokenSelector
} from '../../redux';
import { ButtonSpacer, FullWidthRow, Spacer } from '../helpers';
import WebhookDeleteModal from './webhook-delete-modal';
import './webhook-token.css';
type WebhookTokenProps = {
deleteWebhookToken: () => void;
isChallengePage?: boolean;
postWebhookToken: () => void;
t: TFunction;
webhookToken: string | null;
};
type WebhookTokenState = {
webhookDeleteModal: boolean;
};
const mapStateToProps = createSelector(
webhookTokenSelector,
(webhookToken: string | null) => ({
webhookToken
})
);
const mapDispatchToProps = {
postWebhookToken,
deleteWebhookToken
};
class WebhookToken extends Component<WebhookTokenProps, WebhookTokenState> {
static displayName: string;
constructor(props: WebhookTokenProps) {
super(props);
this.state = {
webhookDeleteModal: false
};
this.createToken = this.createToken.bind(this);
this.deleteToken = this.deleteToken.bind(this);
}
createToken = () => {
this.props.postWebhookToken();
};
deleteToken = () => {
this.props.deleteWebhookToken();
this.toggleWebhookDeleteModal();
};
toggleWebhookDeleteModal = () => {
return this.setState(state => ({
...state,
webhookDeleteModal: !state.webhookDeleteModal
}));
};
render() {
const { isChallengePage = false, t, webhookToken = null } = this.props;
return isChallengePage ? (
<>
{!webhookToken && (
<div className='alert alert-info'>
<p>{t('webhook-token.create-p1')}</p>
<Spacer />
<Button
block={true}
bsSize='lg'
onClick={() => this.createToken()}
type='button'
>
{t('webhook-token.create')}
</Button>
</div>
)}
</>
) : (
<div className='webhook-token text-center'>
<FullWidthRow>
<Panel className='webhook-panel'>
<Panel.Heading>{t('webhook-token.title')}</Panel.Heading>
<Spacer />
{!webhookToken ? (
<p>{t('webhook-token.create-p2')}</p>
) : (
<p>{t('webhook-token.delete-p1')}</p>
)}
<FullWidthRow>
<input
aria-label='Webhook token'
className='webhook-token-input'
readOnly={true}
type='text'
value={webhookToken || ''}
/>
<ButtonSpacer />
{!webhookToken ? (
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-info'
onClick={() => this.createToken()}
type='button'
>
{t('webhook-token.create')}
</Button>
) : (
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-info'
onClick={() => this.toggleWebhookDeleteModal()}
type='button'
>
{t('webhook-token.delete')}
</Button>
)}
<Spacer />
</FullWidthRow>
</Panel>
<WebhookDeleteModal
deleteFunction={() => this.deleteToken()}
onHide={() => this.toggleWebhookDeleteModal()}
show={this.state.webhookDeleteModal}
/>
</FullWidthRow>
</div>
);
}
}
WebhookToken.displayName = 'WebhookToken';
export default connect(
mapStateToProps,
mapDispatchToProps
)(withTranslation()(WebhookToken));

View File

@@ -8,19 +8,23 @@ export const actionTypes = createTypes(
'hardGoTo',
'allowBlockDonationRequests',
'closeDonationModal',
'hideCodeAlly',
'preventBlockDonationRequests',
'preventProgressDonationRequests',
'openDonationModal',
'onlineStatusChange',
'serverStatusChange',
'resetUserData',
'tryToShowCodeAlly',
'tryToShowDonationModal',
'executeGA',
'showCodeAlly',
'submitComplete',
'updateComplete',
'updateCurrentChallengeId',
'updateFailed',
'updateDonationFormState',
'updateUserToken',
...createAsyncTypes('fetchUser'),
...createAsyncTypes('addDonation'),
...createAsyncTypes('createStripeSession'),
@@ -30,8 +34,7 @@ export const actionTypes = createTypes(
...createAsyncTypes('showCert'),
...createAsyncTypes('reportUser'),
...createAsyncTypes('postChargeStripeCard'),
...createAsyncTypes('postWebhookToken'),
...createAsyncTypes('deleteWebhookToken')
...createAsyncTypes('deleteUserToken')
],
ns
);

View File

@@ -0,0 +1,41 @@
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { createFlashMessage } from '../components/Flash/redux';
import { FlashMessages } from '../components/Flash/redux/flash-messages';
import { postUserToken } from '../utils/ajax';
import {
isSignedInSelector,
showCodeAlly,
updateUserToken,
userTokenSelector
} from './';
const startProjectErrMessage = {
type: 'danger',
message: FlashMessages.StartProjectErr
};
function* tryToShowCodeAllySaga() {
const isSignedIn = yield select(isSignedInSelector);
const hasUserToken = !!(yield select(userTokenSelector));
if (!isSignedIn || hasUserToken) {
yield put(showCodeAlly());
} else {
try {
const response = yield call(postUserToken);
if (response?.token) {
yield put(updateUserToken(response.token));
yield put(showCodeAlly());
} else {
yield put(createFlashMessage(startProjectErrMessage));
}
} catch (e) {
yield put(createFlashMessage(startProjectErrMessage));
}
}
}
export function createCodeAllySaga(types) {
return [takeEvery(types.tryToShowCodeAlly, tryToShowCodeAllySaga)];
}

View File

@@ -9,6 +9,7 @@ import { emailToABVariant } from '../utils/A-B-tester';
import { createAcceptTermsSaga } from './accept-terms-saga';
import { actionTypes } from './action-types';
import { createAppMountSaga } from './app-mount-saga';
import { createCodeAllySaga } from './codeally-saga';
import { createDonationSaga } from './donation-saga';
import failedUpdatesEpic from './failed-updates-epic';
import { createFetchUserSaga } from './fetch-user-saga';
@@ -20,7 +21,7 @@ import { actionTypes as settingsTypes } from './settings/action-types';
import { createShowCertSaga } from './show-cert-saga';
import { createSoundModeSaga } from './sound-mode-saga';
import updateCompleteEpic from './update-complete-epic';
import { createWebhookSaga } from './webhook-saga';
import { createUserTokenSaga } from './user-token-saga';
export const MainApp = 'app';
@@ -52,6 +53,7 @@ const initialState = {
showCertFetchState: {
...defaultFetchState
},
showCodeAlly: false,
user: {},
userFetchState: {
...defaultFetchState
@@ -73,13 +75,14 @@ export const epics = [hardGoToEpic, failedUpdatesEpic, updateCompleteEpic];
export const sagas = [
...createAcceptTermsSaga(actionTypes),
...createAppMountSaga(actionTypes),
...createCodeAllySaga(actionTypes),
...createDonationSaga(actionTypes),
...createGaSaga(actionTypes),
...createFetchUserSaga(actionTypes),
...createShowCertSaga(actionTypes),
...createReportUserSaga(actionTypes),
...createSoundModeSaga({ ...actionTypes, ...settingsTypes }),
...createWebhookSaga(actionTypes)
...createUserTokenSaga(actionTypes)
];
export const appMount = createAction(actionTypes.appMount);
@@ -171,15 +174,16 @@ export const showCert = createAction(actionTypes.showCert);
export const showCertComplete = createAction(actionTypes.showCertComplete);
export const showCertError = createAction(actionTypes.showCertError);
export const postWebhookToken = createAction(actionTypes.postWebhookToken);
export const postWebhookTokenComplete = createAction(
actionTypes.postWebhookTokenComplete
);
export const deleteWebhookToken = createAction(actionTypes.deleteWebhookToken);
export const deleteWebhookTokenComplete = createAction(
actionTypes.deleteWebhookTokenComplete
export const updateUserToken = createAction(actionTypes.updateUserToken);
export const deleteUserToken = createAction(actionTypes.deleteUserToken);
export const deleteUserTokenComplete = createAction(
actionTypes.deleteUserTokenComplete
);
export const hideCodeAlly = createAction(actionTypes.hideCodeAlly);
export const showCodeAlly = createAction(actionTypes.showCodeAlly);
export const tryToShowCodeAlly = createAction(actionTypes.tryToShowCodeAlly);
export const updateCurrentChallengeId = createAction(
actionTypes.updateCurrentChallengeId
);
@@ -242,8 +246,12 @@ export const shouldRequestDonationSelector = state => {
return completionCount >= 3;
};
export const webhookTokenSelector = state => {
return userSelector(state).webhookToken;
export const userTokenSelector = state => {
return userSelector(state).userToken;
};
export const showCodeAllySelector = state => {
return state[MainApp].showCodeAlly;
};
export const userByNameSelector = username => state => {
@@ -652,7 +660,7 @@ export const reducer = handleActions(
}
};
},
[actionTypes.postWebhookTokenComplete]: (state, { payload }) => {
[actionTypes.updateUserToken]: (state, { payload }) => {
const { appUsername } = state;
return {
...state,
@@ -660,12 +668,12 @@ export const reducer = handleActions(
...state.user,
[appUsername]: {
...state.user[appUsername],
webhookToken: payload
userToken: payload
}
}
};
},
[actionTypes.deleteWebhookTokenComplete]: state => {
[actionTypes.deleteUserTokenComplete]: state => {
const { appUsername } = state;
return {
...state,
@@ -673,11 +681,23 @@ export const reducer = handleActions(
...state.user,
[appUsername]: {
...state.user[appUsername],
webhookToken: null
userToken: null
}
}
};
},
[actionTypes.hideCodeAlly]: state => {
return {
...state,
showCodeAlly: false
};
},
[actionTypes.showCodeAlly]: state => {
return {
...state,
showCodeAlly: true
};
},
[challengeTypes.challengeMounted]: (state, { payload }) => ({
...state,
currentChallengeId: payload

View File

@@ -0,0 +1,35 @@
import { call, put, takeEvery } from 'redux-saga/effects';
import { createFlashMessage } from '../components/Flash/redux';
import { FlashMessages } from '../components/Flash/redux/flash-messages';
import { deleteUserToken } from '../utils/ajax';
import { deleteUserTokenComplete } from '.';
const message = {
deleted: {
type: 'info',
message: FlashMessages.TokenDeleted
},
deleteErr: {
type: 'danger',
message: FlashMessages.DeleteTokenErr
}
};
function* deleteUserTokenSaga() {
try {
const response = yield call(deleteUserToken);
if (response && Object.prototype.hasOwnProperty.call(response, 'token')) {
yield put(deleteUserTokenComplete());
yield put(createFlashMessage(message.deleted));
} else {
yield put(createFlashMessage(message.deleteErr));
}
} catch (e) {
yield put(createFlashMessage(message.deleteErr));
}
}
export function createUserTokenSaga(types) {
return [takeEvery(types.deleteUserToken, deleteUserTokenSaga)];
}

View File

@@ -1,61 +0,0 @@
import { call, put, takeEvery } from 'redux-saga/effects';
import { createFlashMessage } from '../components/Flash/redux';
import { FlashMessages } from '../components/Flash/redux/flash-messages';
import { postWebhookToken, deleteWebhookToken } from '../utils/ajax';
import { postWebhookTokenComplete, deleteWebhookTokenComplete } from '.';
const message = {
created: {
type: 'success',
message: FlashMessages.TokenCreated
},
createErr: {
type: 'danger',
message: FlashMessages.CreateTokenErr
},
deleted: {
type: 'info',
message: FlashMessages.TokenDeleted
},
deleteErr: {
type: 'danger',
message: FlashMessages.DeleteTokenErr
}
};
function* postWebhookTokenSaga() {
try {
const response = yield call(postWebhookToken);
if (response?.message) {
yield put(createFlashMessage(response));
} else {
yield put(postWebhookTokenComplete(response));
yield put(createFlashMessage(message.created));
}
} catch (e) {
yield put(createFlashMessage(message.createErr));
}
}
function* deleteWebhookTokenSaga() {
try {
const response = yield call(deleteWebhookToken);
if (response?.message) {
yield put(createFlashMessage(response));
} else {
yield put(deleteWebhookTokenComplete());
yield put(createFlashMessage(message.deleted));
}
} catch (e) {
yield put(createFlashMessage(message.deleteErr));
}
}
export function createWebhookSaga(types) {
return [
takeEvery(types.postWebhookToken, postWebhookTokenSaga),
takeEvery(types.deleteWebhookToken, deleteWebhookTokenSaga)
];
}

View File

@@ -22,8 +22,11 @@ import Hotkeys from '../components/Hotkeys';
import {
completedChallengesSelector,
isSignedInSelector,
hideCodeAlly,
partiallyCompletedChallengesSelector,
webhookTokenSelector
showCodeAllySelector,
tryToShowCodeAlly,
userTokenSelector
} from '../../../redux';
import {
challengeMounted,
@@ -40,7 +43,6 @@ import {
} from '../../../redux/prop-types';
import ProjectToolPanel from '../projects/tool-panel';
import SolutionForm from '../projects/solution-form';
import WebhookToken from '../../../components/settings/webhook-token';
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import './codeally.css';
@@ -51,19 +53,22 @@ const mapStateToProps = createSelector(
isChallengeCompletedSelector,
isSignedInSelector,
partiallyCompletedChallengesSelector,
webhookTokenSelector,
showCodeAllySelector,
userTokenSelector,
(
completedChallenges: CompletedChallenge[],
isChallengeCompleted: boolean,
isSignedIn: boolean,
partiallyCompletedChallenges: CompletedChallenge[],
webhookToken: string | null
showCodeAlly: boolean,
userToken: string | null
) => ({
completedChallenges,
isChallengeCompleted,
isSignedIn,
partiallyCompletedChallenges,
webhookToken
showCodeAlly,
userToken
})
);
@@ -72,7 +77,9 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
{
challengeMounted,
createFlashMessage,
hideCodeAlly,
openCompletionModal: () => openModal('completion'),
tryToShowCodeAlly,
updateChallengeMeta,
updateSolutionFormValues
},
@@ -85,6 +92,7 @@ interface ShowCodeAllyProps {
completedChallenges: CompletedChallenge[];
createFlashMessage: typeof createFlashMessage;
data: { challengeNode: ChallengeNode };
hideCodeAlly: () => void;
isChallengeCompleted: boolean;
isSignedIn: boolean;
openCompletionModal: () => void;
@@ -92,26 +100,18 @@ interface ShowCodeAllyProps {
challengeMeta: ChallengeMeta;
};
partiallyCompletedChallenges: CompletedChallenge[];
showCodeAlly: boolean;
t: TFunction;
tryToShowCodeAlly: () => void;
updateChallengeMeta: (arg0: ChallengeMeta) => void;
updateSolutionFormValues: () => void;
webhookToken: string | null;
}
interface ShowCodeAllyState {
showIframe: boolean;
userToken: string | null;
}
// Component
class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
class ShowCodeAlly extends Component<ShowCodeAllyProps> {
static displayName: string;
private _container: HTMLElement | null = null;
constructor(props: ShowCodeAllyProps) {
super(props);
this.state = {
showIframe: false
};
}
componentDidMount(): void {
const {
@@ -134,11 +134,9 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
this._container?.focus();
}
showIframe = () => {
this.setState({
showIframe: true
});
};
componentWillUnmount() {
this.props.hideCodeAlly();
}
handleSubmit = ({
showCompletionModal
@@ -153,6 +151,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
challenge: { id: challengeId }
}
},
openCompletionModal,
partiallyCompletedChallenges
} = this.props;
@@ -170,7 +169,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
message: FlashMessages.CompleteProjectFirst
});
} else if (showCompletionModal) {
this.props.openCompletionModal();
openCompletionModal();
}
};
@@ -201,14 +200,15 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
challengeMeta: { nextChallengePath, prevChallengePath }
},
partiallyCompletedChallenges,
showCodeAlly,
t,
tryToShowCodeAlly,
updateSolutionFormValues,
webhookToken = null
userToken = null
} = this.props;
const { showIframe } = this.state;
const envVariables = webhookToken
? `&envVariables=CODEROAD_WEBHOOK_TOKEN=${webhookToken}`
const envVariables = userToken
? `&envVariables=CODEROAD_WEBHOOK_TOKEN=${userToken}`
: '';
const isPartiallyCompleted = partiallyCompletedChallenges.some(
@@ -219,7 +219,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
challenge => challenge.id === challengeId
);
return showIframe ? (
return showCodeAlly ? (
<LearnLayout>
<Helmet title={`${blockName}: ${title} | freeCodeCamp.org`} />
<iframe
@@ -251,7 +251,6 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
{title}
</ChallengeTitle>
<Spacer />
{isSignedIn && <WebhookToken isChallengePage={true} />}
<PrismFormatted text={description} />
<Spacer />
<div className='ca-description'>
@@ -304,7 +303,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps, ShowCodeAllyState> {
<Button
block={true}
bsStyle='primary'
onClick={this.showIframe}
onClick={tryToShowCodeAlly}
>
{challengeType === challengeTypes.codeAllyCert
? t('buttons.click-start-project')

View File

@@ -203,8 +203,8 @@ export function postResetProgress(): Promise<void> {
return post('/account/reset-progress', {});
}
export function postWebhookToken(): Promise<void> {
return post('/user/webhook-token', {});
export function postUserToken(): Promise<void> {
return post('/user/user-token', {});
}
/** PUT **/
@@ -251,6 +251,6 @@ export function putVerifyCert(certSlug: string): Promise<void> {
}
/** DELETE **/
export function deleteWebhookToken(): Promise<void> {
return deleteRequest('/user/webhook-token', {});
export function deleteUserToken(): Promise<void> {
return deleteRequest('/user/user-token', {});
}

View File

@@ -21,7 +21,6 @@ const toneUrls = {
[FlashMessages.CertificateMissing]: TRY_AGAIN,
[FlashMessages.CertsPrivate]: TRY_AGAIN,
[FlashMessages.CompleteProjectFirst]: TRY_AGAIN,
[FlashMessages.CreateTokenErr]: TRY_AGAIN,
[FlashMessages.DeleteTokenErr]: TRY_AGAIN,
[FlashMessages.EmailValid]: CHAL_COMP,
[FlashMessages.HonestFirst]: TRY_AGAIN,
@@ -39,7 +38,7 @@ const toneUrls = {
[FlashMessages.ReallyWeird]: TRY_AGAIN,
[FlashMessages.ReportSent]: CHAL_COMP,
[FlashMessages.SigninSuccess]: CHAL_COMP,
[FlashMessages.TokenCreated]: CHAL_COMP,
[FlashMessages.StartProjectErr]: TRY_AGAIN,
[FlashMessages.TokenDeleted]: CHAL_COMP,
[FlashMessages.UpdatedPreferences]: CHAL_COMP,
[FlashMessages.UsernameNotFound]: TRY_AGAIN,