fix(client): block donation modal (#40899)
Co-authored-by: Kris Koishigawa <scissorsneedfoodtoo@gmail.com>
This commit is contained in:
@ -6,7 +6,6 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||||
import { Spacer } from '../helpers';
|
import { Spacer } from '../helpers';
|
||||||
import { blockNameify } from '../../../../utils/block-nameify';
|
|
||||||
import Heart from '../../assets/icons/Heart';
|
import Heart from '../../assets/icons/Heart';
|
||||||
import Cup from '../../assets/icons/Cup';
|
import Cup from '../../assets/icons/Cup';
|
||||||
import DonateForm from './DonateForm';
|
import DonateForm from './DonateForm';
|
||||||
@ -16,22 +15,18 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import {
|
import {
|
||||||
closeDonationModal,
|
closeDonationModal,
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
isBlockDonationModalSelector,
|
recentlyClaimedBlockSelector,
|
||||||
executeGA
|
executeGA
|
||||||
} from '../../redux';
|
} from '../../redux';
|
||||||
|
|
||||||
import { challengeMetaSelector } from '../../templates/Challenges/redux';
|
|
||||||
|
|
||||||
import './Donation.css';
|
import './Donation.css';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
challengeMetaSelector,
|
recentlyClaimedBlockSelector,
|
||||||
isBlockDonationModalSelector,
|
(show, recentlyClaimedBlock) => ({
|
||||||
(show, { block }, isBlockDonation) => ({
|
|
||||||
show,
|
show,
|
||||||
block,
|
recentlyClaimedBlock
|
||||||
isBlockDonation
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -46,19 +41,17 @@ const mapDispatchToProps = dispatch =>
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
activeDonors: PropTypes.number,
|
activeDonors: PropTypes.number,
|
||||||
block: PropTypes.string,
|
|
||||||
closeDonationModal: PropTypes.func.isRequired,
|
closeDonationModal: PropTypes.func.isRequired,
|
||||||
executeGA: PropTypes.func,
|
executeGA: PropTypes.func,
|
||||||
isBlockDonation: PropTypes.bool,
|
recentlyClaimedBlock: PropTypes.string,
|
||||||
show: PropTypes.bool
|
show: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
function DonateModal({
|
function DonateModal({
|
||||||
show,
|
show,
|
||||||
block,
|
|
||||||
isBlockDonation,
|
|
||||||
closeDonationModal,
|
closeDonationModal,
|
||||||
executeGA
|
executeGA,
|
||||||
|
recentlyClaimedBlock
|
||||||
}) {
|
}) {
|
||||||
const [closeLabel, setCloseLabel] = React.useState(false);
|
const [closeLabel, setCloseLabel] = React.useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -87,13 +80,13 @@ function DonateModal({
|
|||||||
data: {
|
data: {
|
||||||
category: 'Donation View',
|
category: 'Donation View',
|
||||||
action: `Displayed ${
|
action: `Displayed ${
|
||||||
isBlockDonation ? 'block' : 'progress'
|
recentlyClaimedBlock ? 'block' : 'progress'
|
||||||
} donation modal`,
|
} donation modal`,
|
||||||
nonInteraction: true
|
nonInteraction: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [show, isBlockDonation, executeGA]);
|
}, [show, recentlyClaimedBlock, executeGA]);
|
||||||
|
|
||||||
const getDonationText = () => {
|
const getDonationText = () => {
|
||||||
const donationDuration = modalDefaultDonation.donationDuration;
|
const donationDuration = modalDefaultDonation.donationDuration;
|
||||||
@ -117,7 +110,7 @@ function DonateModal({
|
|||||||
<Row>
|
<Row>
|
||||||
{!closeLabel && (
|
{!closeLabel && (
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<b>{t('donate.nicely-done', { block: blockNameify(block) })}</b>
|
<b>{t('donate.nicely-done', { block: recentlyClaimedBlock })}</b>
|
||||||
<br />
|
<br />
|
||||||
{getDonationText()}
|
{getDonationText()}
|
||||||
</Col>
|
</Col>
|
||||||
@ -144,7 +137,7 @@ function DonateModal({
|
|||||||
return (
|
return (
|
||||||
<Modal bsSize='lg' className='donation-modal' show={show}>
|
<Modal bsSize='lg' className='donation-modal' show={show}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{isBlockDonation ? blockDonationText : progressDonationText}
|
{recentlyClaimedBlock ? blockDonationText : progressDonationText}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<DonateForm handleProcessing={handleProcessing} isMinimalForm={true} />
|
<DonateForm handleProcessing={handleProcessing} isMinimalForm={true} />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
@ -4,7 +4,8 @@ import {
|
|||||||
takeEvery,
|
takeEvery,
|
||||||
takeLeading,
|
takeLeading,
|
||||||
delay,
|
delay,
|
||||||
call
|
call,
|
||||||
|
take
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -12,11 +13,12 @@ import {
|
|||||||
preventBlockDonationRequests,
|
preventBlockDonationRequests,
|
||||||
shouldRequestDonationSelector,
|
shouldRequestDonationSelector,
|
||||||
preventProgressDonationRequests,
|
preventProgressDonationRequests,
|
||||||
canRequestBlockDonationSelector,
|
recentlyClaimedBlockSelector,
|
||||||
addDonationComplete,
|
addDonationComplete,
|
||||||
addDonationError,
|
addDonationError,
|
||||||
postChargeStripeComplete,
|
postChargeStripeComplete,
|
||||||
postChargeStripeError
|
postChargeStripeError,
|
||||||
|
types as appTypes
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import { addDonation, postChargeStripe } from '../utils/ajax';
|
import { addDonation, postChargeStripe } from '../utils/ajax';
|
||||||
@ -27,9 +29,10 @@ function* showDonateModalSaga() {
|
|||||||
let shouldRequestDonation = yield select(shouldRequestDonationSelector);
|
let shouldRequestDonation = yield select(shouldRequestDonationSelector);
|
||||||
if (shouldRequestDonation) {
|
if (shouldRequestDonation) {
|
||||||
yield delay(200);
|
yield delay(200);
|
||||||
const isBlockDonation = yield select(canRequestBlockDonationSelector);
|
const recentlyClaimedBlock = yield select(recentlyClaimedBlockSelector);
|
||||||
yield put(openDonationModal(isBlockDonation));
|
yield put(openDonationModal());
|
||||||
if (isBlockDonation) {
|
yield take(appTypes.closeDonationModal);
|
||||||
|
if (recentlyClaimedBlock) {
|
||||||
yield put(preventBlockDonationRequests());
|
yield put(preventBlockDonationRequests());
|
||||||
} else {
|
} else {
|
||||||
yield put(preventProgressDonationRequests());
|
yield put(preventProgressDonationRequests());
|
||||||
|
@ -38,7 +38,7 @@ export const defaultDonationFormState = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
appUsername: '',
|
appUsername: '',
|
||||||
canRequestBlockDonation: false,
|
recentlyClaimedBlock: null,
|
||||||
canRequestProgressDonation: true,
|
canRequestProgressDonation: true,
|
||||||
completionCount: 0,
|
completionCount: 0,
|
||||||
currentChallengeId: store.get(CURRENT_CHALLENGE_KEY),
|
currentChallengeId: store.get(CURRENT_CHALLENGE_KEY),
|
||||||
@ -55,7 +55,6 @@ const initialState = {
|
|||||||
},
|
},
|
||||||
sessionMeta: { activeDonations: 0 },
|
sessionMeta: { activeDonations: 0 },
|
||||||
showDonationModal: false,
|
showDonationModal: false,
|
||||||
isBlockDonationModal: false,
|
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
donationFormState: {
|
donationFormState: {
|
||||||
...defaultDonationFormState
|
...defaultDonationFormState
|
||||||
@ -187,10 +186,8 @@ export const isDonatingSelector = state => userSelector(state).isDonating;
|
|||||||
export const isOnlineSelector = state => state[ns].isOnline;
|
export const isOnlineSelector = state => state[ns].isOnline;
|
||||||
export const isSignedInSelector = state => !!state[ns].appUsername;
|
export const isSignedInSelector = state => !!state[ns].appUsername;
|
||||||
export const isDonationModalOpenSelector = state => state[ns].showDonationModal;
|
export const isDonationModalOpenSelector = state => state[ns].showDonationModal;
|
||||||
export const canRequestBlockDonationSelector = state =>
|
export const recentlyClaimedBlockSelector = state =>
|
||||||
state[ns].canRequestBlockDonation;
|
state[ns].recentlyClaimedBlock;
|
||||||
export const isBlockDonationModalSelector = state =>
|
|
||||||
state[ns].isBlockDonationModal;
|
|
||||||
export const donationFormStateSelector = state => state[ns].donationFormState;
|
export const donationFormStateSelector = state => state[ns].donationFormState;
|
||||||
export const signInLoadingSelector = state =>
|
export const signInLoadingSelector = state =>
|
||||||
userFetchStateSelector(state).pending;
|
userFetchStateSelector(state).pending;
|
||||||
@ -201,13 +198,13 @@ export const shouldRequestDonationSelector = state => {
|
|||||||
const completionCount = completionCountSelector(state);
|
const completionCount = completionCountSelector(state);
|
||||||
const canRequestProgressDonation = state[ns].canRequestProgressDonation;
|
const canRequestProgressDonation = state[ns].canRequestProgressDonation;
|
||||||
const isDonating = isDonatingSelector(state);
|
const isDonating = isDonatingSelector(state);
|
||||||
const canRequestBlockDonation = canRequestBlockDonationSelector(state);
|
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
|
||||||
|
|
||||||
// don't request donation if already donating
|
// don't request donation if already donating
|
||||||
if (isDonating) return false;
|
if (isDonating) return false;
|
||||||
|
|
||||||
// a block has been completed
|
// a block has been completed
|
||||||
if (canRequestBlockDonation) return true;
|
if (recentlyClaimedBlock) return true;
|
||||||
|
|
||||||
// a donation has already been requested
|
// a donation has already been requested
|
||||||
if (!canRequestProgressDonation) return false;
|
if (!canRequestProgressDonation) return false;
|
||||||
@ -393,10 +390,12 @@ export const reducer = handleActions(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[types.allowBlockDonationRequests]: state => ({
|
[types.allowBlockDonationRequests]: (state, { payload }) => {
|
||||||
...state,
|
return {
|
||||||
canRequestBlockDonation: true
|
...state,
|
||||||
}),
|
recentlyClaimedBlock: payload
|
||||||
|
};
|
||||||
|
},
|
||||||
[types.updateDonationFormState]: (state, { payload }) => ({
|
[types.updateDonationFormState]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
donationFormState: { ...state.donationFormState, ...payload }
|
donationFormState: { ...state.donationFormState, ...payload }
|
||||||
@ -522,14 +521,13 @@ export const reducer = handleActions(
|
|||||||
...state,
|
...state,
|
||||||
showDonationModal: false
|
showDonationModal: false
|
||||||
}),
|
}),
|
||||||
[types.openDonationModal]: (state, { payload }) => ({
|
[types.openDonationModal]: state => ({
|
||||||
...state,
|
...state,
|
||||||
showDonationModal: true,
|
showDonationModal: true
|
||||||
isBlockDonationModal: payload
|
|
||||||
}),
|
}),
|
||||||
[types.preventBlockDonationRequests]: state => ({
|
[types.preventBlockDonationRequests]: state => ({
|
||||||
...state,
|
...state,
|
||||||
canRequestBlockDonation: false
|
recentlyClaimedBlock: null
|
||||||
}),
|
}),
|
||||||
[types.preventProgressDonationRequests]: state => ({
|
[types.preventProgressDonationRequests]: state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -20,11 +20,14 @@ import {
|
|||||||
isCompletionModalOpenSelector,
|
isCompletionModalOpenSelector,
|
||||||
successMessageSelector,
|
successMessageSelector,
|
||||||
challengeFilesSelector,
|
challengeFilesSelector,
|
||||||
challengeMetaSelector,
|
challengeMetaSelector
|
||||||
lastBlockChalSubmitted
|
|
||||||
} from '../redux';
|
} from '../redux';
|
||||||
|
|
||||||
import { isSignedInSelector, executeGA } from '../../../redux';
|
import {
|
||||||
|
isSignedInSelector,
|
||||||
|
executeGA,
|
||||||
|
allowBlockDonationRequests
|
||||||
|
} from '../../../redux';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
challengeFilesSelector,
|
challengeFilesSelector,
|
||||||
@ -57,8 +60,8 @@ const mapDispatchToProps = function(dispatch) {
|
|||||||
submitChallenge: () => {
|
submitChallenge: () => {
|
||||||
dispatch(submitChallenge());
|
dispatch(submitChallenge());
|
||||||
},
|
},
|
||||||
lastBlockChalSubmitted: () => {
|
allowBlockDonationRequests: block => {
|
||||||
dispatch(lastBlockChalSubmitted());
|
dispatch(allowBlockDonationRequests(block));
|
||||||
},
|
},
|
||||||
executeGA
|
executeGA
|
||||||
};
|
};
|
||||||
@ -66,6 +69,7 @@ const mapDispatchToProps = function(dispatch) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
allowBlockDonationRequests: PropTypes.func,
|
||||||
blockName: PropTypes.string,
|
blockName: PropTypes.string,
|
||||||
close: PropTypes.func.isRequired,
|
close: PropTypes.func.isRequired,
|
||||||
completedChallengesIds: PropTypes.array,
|
completedChallengesIds: PropTypes.array,
|
||||||
@ -75,7 +79,6 @@ const propTypes = {
|
|||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
isOpen: PropTypes.bool,
|
isOpen: PropTypes.bool,
|
||||||
isSignedIn: PropTypes.bool.isRequired,
|
isSignedIn: PropTypes.bool.isRequired,
|
||||||
lastBlockChalSubmitted: PropTypes.func,
|
|
||||||
message: PropTypes.string,
|
message: PropTypes.string,
|
||||||
submitChallenge: PropTypes.func.isRequired,
|
submitChallenge: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
@ -168,7 +171,7 @@ export class CompletionModalInner extends Component {
|
|||||||
this.state.completedPercent === 100 &&
|
this.state.completedPercent === 100 &&
|
||||||
!this.props.completedChallengesIds.includes(this.props.id)
|
!this.props.completedChallengesIds.includes(this.props.id)
|
||||||
) {
|
) {
|
||||||
this.props.lastBlockChalSubmitted();
|
this.props.allowBlockDonationRequests(this.props.blockName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ import store from 'store';
|
|||||||
import {
|
import {
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
updateComplete,
|
updateComplete,
|
||||||
updateFailed,
|
updateFailed
|
||||||
allowBlockDonationRequests
|
|
||||||
} from '../../../redux';
|
} from '../../../redux';
|
||||||
|
|
||||||
import { post } from '../../../utils/ajax';
|
import { post } from '../../../utils/ajax';
|
||||||
@ -38,14 +37,9 @@ export function* updateSuccessMessageSaga() {
|
|||||||
yield put(updateSuccessMessage(randomCompliment()));
|
yield put(updateSuccessMessage(randomCompliment()));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* allowBlockDonationRequestsSaga() {
|
|
||||||
yield put(allowBlockDonationRequests());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCurrentChallengeSaga(types) {
|
export function createCurrentChallengeSaga(types) {
|
||||||
return [
|
return [
|
||||||
takeEvery(types.challengeMounted, currentChallengeSaga),
|
takeEvery(types.challengeMounted, currentChallengeSaga),
|
||||||
takeEvery(types.challengeMounted, updateSuccessMessageSaga),
|
takeEvery(types.challengeMounted, updateSuccessMessageSaga)
|
||||||
takeEvery(types.lastBlockChalSubmitted, allowBlockDonationRequestsSaga)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
/* global expect */
|
|
||||||
import { allowBlockDonationRequestsSaga } from './current-challenge-saga';
|
|
||||||
import { types as appTypes } from '../../../redux';
|
|
||||||
|
|
||||||
describe('allowBlockDonationRequestsSaga', () => {
|
|
||||||
it('should call allowBlockDonationRequests', () => {
|
|
||||||
const gen = allowBlockDonationRequestsSaga();
|
|
||||||
expect(gen.next().value.payload.action.type).toEqual(
|
|
||||||
appTypes.allowBlockDonationRequests
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -88,9 +88,7 @@ export const types = createTypes(
|
|||||||
|
|
||||||
'setEditorFocusability',
|
'setEditorFocusability',
|
||||||
'toggleVisibleEditor',
|
'toggleVisibleEditor',
|
||||||
'setAccessibilityMode',
|
'setAccessibilityMode'
|
||||||
|
|
||||||
'lastBlockChalSubmitted'
|
|
||||||
],
|
],
|
||||||
ns
|
ns
|
||||||
);
|
);
|
||||||
@ -171,10 +169,6 @@ export const setEditorFocusability = createAction(types.setEditorFocusability);
|
|||||||
export const toggleVisibleEditor = createAction(types.toggleVisibleEditor);
|
export const toggleVisibleEditor = createAction(types.toggleVisibleEditor);
|
||||||
export const setAccessibilityMode = createAction(types.setAccessibilityMode);
|
export const setAccessibilityMode = createAction(types.setAccessibilityMode);
|
||||||
|
|
||||||
export const lastBlockChalSubmitted = createAction(
|
|
||||||
types.lastBlockChalSubmitted
|
|
||||||
);
|
|
||||||
|
|
||||||
export const currentTabSelector = state => state[ns].currentTab;
|
export const currentTabSelector = state => state[ns].currentTab;
|
||||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||||
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/* global cy */
|
||||||
|
|
||||||
|
describe('Donate page', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.clearCookies();
|
||||||
|
cy.exec('npm run seed');
|
||||||
|
cy.login();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.exec('npm run seed');
|
||||||
|
});
|
||||||
|
|
||||||
|
const projects = [
|
||||||
|
'tribute-page',
|
||||||
|
'survey-form',
|
||||||
|
'product-landing-page',
|
||||||
|
'technical-documentation-page',
|
||||||
|
'personal-portfolio-webpage'
|
||||||
|
];
|
||||||
|
|
||||||
|
it('Should be able to submit projects', () => {
|
||||||
|
const submitProject = str => {
|
||||||
|
cy.visit(
|
||||||
|
`/learn/responsive-web-design/responsive-web-design-projects/build-a-${str}`
|
||||||
|
);
|
||||||
|
cy.get('#dynamic-front-end-form')
|
||||||
|
.get('#solution')
|
||||||
|
.type('https://codepen.io/camperbot/full/oNvPqqo', {
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.contains("I've completed this challenge").click();
|
||||||
|
cy.contains('Submit and go to next challenge').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
projects.forEach(project => submitProject(project));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should have a pop up modal', () => {
|
||||||
|
cy.contains(
|
||||||
|
'Nicely done. You just completed Responsive Web Design Projects.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user