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 { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||
import { Spacer } from '../helpers';
|
||||
import { blockNameify } from '../../../../utils/block-nameify';
|
||||
import Heart from '../../assets/icons/Heart';
|
||||
import Cup from '../../assets/icons/Cup';
|
||||
import DonateForm from './DonateForm';
|
||||
@ -16,22 +15,18 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
closeDonationModal,
|
||||
isDonationModalOpenSelector,
|
||||
isBlockDonationModalSelector,
|
||||
recentlyClaimedBlockSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
|
||||
import { challengeMetaSelector } from '../../templates/Challenges/redux';
|
||||
|
||||
import './Donation.css';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isDonationModalOpenSelector,
|
||||
challengeMetaSelector,
|
||||
isBlockDonationModalSelector,
|
||||
(show, { block }, isBlockDonation) => ({
|
||||
recentlyClaimedBlockSelector,
|
||||
(show, recentlyClaimedBlock) => ({
|
||||
show,
|
||||
block,
|
||||
isBlockDonation
|
||||
recentlyClaimedBlock
|
||||
})
|
||||
);
|
||||
|
||||
@ -46,19 +41,17 @@ const mapDispatchToProps = dispatch =>
|
||||
|
||||
const propTypes = {
|
||||
activeDonors: PropTypes.number,
|
||||
block: PropTypes.string,
|
||||
closeDonationModal: PropTypes.func.isRequired,
|
||||
executeGA: PropTypes.func,
|
||||
isBlockDonation: PropTypes.bool,
|
||||
recentlyClaimedBlock: PropTypes.string,
|
||||
show: PropTypes.bool
|
||||
};
|
||||
|
||||
function DonateModal({
|
||||
show,
|
||||
block,
|
||||
isBlockDonation,
|
||||
closeDonationModal,
|
||||
executeGA
|
||||
executeGA,
|
||||
recentlyClaimedBlock
|
||||
}) {
|
||||
const [closeLabel, setCloseLabel] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
@ -87,13 +80,13 @@ function DonateModal({
|
||||
data: {
|
||||
category: 'Donation View',
|
||||
action: `Displayed ${
|
||||
isBlockDonation ? 'block' : 'progress'
|
||||
recentlyClaimedBlock ? 'block' : 'progress'
|
||||
} donation modal`,
|
||||
nonInteraction: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [show, isBlockDonation, executeGA]);
|
||||
}, [show, recentlyClaimedBlock, executeGA]);
|
||||
|
||||
const getDonationText = () => {
|
||||
const donationDuration = modalDefaultDonation.donationDuration;
|
||||
@ -117,7 +110,7 @@ function DonateModal({
|
||||
<Row>
|
||||
{!closeLabel && (
|
||||
<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 />
|
||||
{getDonationText()}
|
||||
</Col>
|
||||
@ -144,7 +137,7 @@ function DonateModal({
|
||||
return (
|
||||
<Modal bsSize='lg' className='donation-modal' show={show}>
|
||||
<Modal.Body>
|
||||
{isBlockDonation ? blockDonationText : progressDonationText}
|
||||
{recentlyClaimedBlock ? blockDonationText : progressDonationText}
|
||||
<Spacer />
|
||||
<DonateForm handleProcessing={handleProcessing} isMinimalForm={true} />
|
||||
<Spacer />
|
||||
|
@ -4,7 +4,8 @@ import {
|
||||
takeEvery,
|
||||
takeLeading,
|
||||
delay,
|
||||
call
|
||||
call,
|
||||
take
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import {
|
||||
@ -12,11 +13,12 @@ import {
|
||||
preventBlockDonationRequests,
|
||||
shouldRequestDonationSelector,
|
||||
preventProgressDonationRequests,
|
||||
canRequestBlockDonationSelector,
|
||||
recentlyClaimedBlockSelector,
|
||||
addDonationComplete,
|
||||
addDonationError,
|
||||
postChargeStripeComplete,
|
||||
postChargeStripeError
|
||||
postChargeStripeError,
|
||||
types as appTypes
|
||||
} from './';
|
||||
|
||||
import { addDonation, postChargeStripe } from '../utils/ajax';
|
||||
@ -27,9 +29,10 @@ function* showDonateModalSaga() {
|
||||
let shouldRequestDonation = yield select(shouldRequestDonationSelector);
|
||||
if (shouldRequestDonation) {
|
||||
yield delay(200);
|
||||
const isBlockDonation = yield select(canRequestBlockDonationSelector);
|
||||
yield put(openDonationModal(isBlockDonation));
|
||||
if (isBlockDonation) {
|
||||
const recentlyClaimedBlock = yield select(recentlyClaimedBlockSelector);
|
||||
yield put(openDonationModal());
|
||||
yield take(appTypes.closeDonationModal);
|
||||
if (recentlyClaimedBlock) {
|
||||
yield put(preventBlockDonationRequests());
|
||||
} else {
|
||||
yield put(preventProgressDonationRequests());
|
||||
|
@ -38,7 +38,7 @@ export const defaultDonationFormState = {
|
||||
|
||||
const initialState = {
|
||||
appUsername: '',
|
||||
canRequestBlockDonation: false,
|
||||
recentlyClaimedBlock: null,
|
||||
canRequestProgressDonation: true,
|
||||
completionCount: 0,
|
||||
currentChallengeId: store.get(CURRENT_CHALLENGE_KEY),
|
||||
@ -55,7 +55,6 @@ const initialState = {
|
||||
},
|
||||
sessionMeta: { activeDonations: 0 },
|
||||
showDonationModal: false,
|
||||
isBlockDonationModal: false,
|
||||
isOnline: true,
|
||||
donationFormState: {
|
||||
...defaultDonationFormState
|
||||
@ -187,10 +186,8 @@ export const isDonatingSelector = state => userSelector(state).isDonating;
|
||||
export const isOnlineSelector = state => state[ns].isOnline;
|
||||
export const isSignedInSelector = state => !!state[ns].appUsername;
|
||||
export const isDonationModalOpenSelector = state => state[ns].showDonationModal;
|
||||
export const canRequestBlockDonationSelector = state =>
|
||||
state[ns].canRequestBlockDonation;
|
||||
export const isBlockDonationModalSelector = state =>
|
||||
state[ns].isBlockDonationModal;
|
||||
export const recentlyClaimedBlockSelector = state =>
|
||||
state[ns].recentlyClaimedBlock;
|
||||
export const donationFormStateSelector = state => state[ns].donationFormState;
|
||||
export const signInLoadingSelector = state =>
|
||||
userFetchStateSelector(state).pending;
|
||||
@ -201,13 +198,13 @@ export const shouldRequestDonationSelector = state => {
|
||||
const completionCount = completionCountSelector(state);
|
||||
const canRequestProgressDonation = state[ns].canRequestProgressDonation;
|
||||
const isDonating = isDonatingSelector(state);
|
||||
const canRequestBlockDonation = canRequestBlockDonationSelector(state);
|
||||
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
|
||||
|
||||
// don't request donation if already donating
|
||||
if (isDonating) return false;
|
||||
|
||||
// a block has been completed
|
||||
if (canRequestBlockDonation) return true;
|
||||
if (recentlyClaimedBlock) return true;
|
||||
|
||||
// a donation has already been requested
|
||||
if (!canRequestProgressDonation) return false;
|
||||
@ -393,10 +390,12 @@ export const reducer = handleActions(
|
||||
}
|
||||
};
|
||||
},
|
||||
[types.allowBlockDonationRequests]: state => ({
|
||||
[types.allowBlockDonationRequests]: (state, { payload }) => {
|
||||
return {
|
||||
...state,
|
||||
canRequestBlockDonation: true
|
||||
}),
|
||||
recentlyClaimedBlock: payload
|
||||
};
|
||||
},
|
||||
[types.updateDonationFormState]: (state, { payload }) => ({
|
||||
...state,
|
||||
donationFormState: { ...state.donationFormState, ...payload }
|
||||
@ -522,14 +521,13 @@ export const reducer = handleActions(
|
||||
...state,
|
||||
showDonationModal: false
|
||||
}),
|
||||
[types.openDonationModal]: (state, { payload }) => ({
|
||||
[types.openDonationModal]: state => ({
|
||||
...state,
|
||||
showDonationModal: true,
|
||||
isBlockDonationModal: payload
|
||||
showDonationModal: true
|
||||
}),
|
||||
[types.preventBlockDonationRequests]: state => ({
|
||||
...state,
|
||||
canRequestBlockDonation: false
|
||||
recentlyClaimedBlock: null
|
||||
}),
|
||||
[types.preventProgressDonationRequests]: state => ({
|
||||
...state,
|
||||
|
@ -20,11 +20,14 @@ import {
|
||||
isCompletionModalOpenSelector,
|
||||
successMessageSelector,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector,
|
||||
lastBlockChalSubmitted
|
||||
challengeMetaSelector
|
||||
} from '../redux';
|
||||
|
||||
import { isSignedInSelector, executeGA } from '../../../redux';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
executeGA,
|
||||
allowBlockDonationRequests
|
||||
} from '../../../redux';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeFilesSelector,
|
||||
@ -57,8 +60,8 @@ const mapDispatchToProps = function(dispatch) {
|
||||
submitChallenge: () => {
|
||||
dispatch(submitChallenge());
|
||||
},
|
||||
lastBlockChalSubmitted: () => {
|
||||
dispatch(lastBlockChalSubmitted());
|
||||
allowBlockDonationRequests: block => {
|
||||
dispatch(allowBlockDonationRequests(block));
|
||||
},
|
||||
executeGA
|
||||
};
|
||||
@ -66,6 +69,7 @@ const mapDispatchToProps = function(dispatch) {
|
||||
};
|
||||
|
||||
const propTypes = {
|
||||
allowBlockDonationRequests: PropTypes.func,
|
||||
blockName: PropTypes.string,
|
||||
close: PropTypes.func.isRequired,
|
||||
completedChallengesIds: PropTypes.array,
|
||||
@ -75,7 +79,6 @@ const propTypes = {
|
||||
id: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
isSignedIn: PropTypes.bool.isRequired,
|
||||
lastBlockChalSubmitted: PropTypes.func,
|
||||
message: PropTypes.string,
|
||||
submitChallenge: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
@ -168,7 +171,7 @@ export class CompletionModalInner extends Component {
|
||||
this.state.completedPercent === 100 &&
|
||||
!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 {
|
||||
isSignedInSelector,
|
||||
updateComplete,
|
||||
updateFailed,
|
||||
allowBlockDonationRequests
|
||||
updateFailed
|
||||
} from '../../../redux';
|
||||
|
||||
import { post } from '../../../utils/ajax';
|
||||
@ -38,14 +37,9 @@ export function* updateSuccessMessageSaga() {
|
||||
yield put(updateSuccessMessage(randomCompliment()));
|
||||
}
|
||||
|
||||
export function* allowBlockDonationRequestsSaga() {
|
||||
yield put(allowBlockDonationRequests());
|
||||
}
|
||||
|
||||
export function createCurrentChallengeSaga(types) {
|
||||
return [
|
||||
takeEvery(types.challengeMounted, currentChallengeSaga),
|
||||
takeEvery(types.challengeMounted, updateSuccessMessageSaga),
|
||||
takeEvery(types.lastBlockChalSubmitted, allowBlockDonationRequestsSaga)
|
||||
takeEvery(types.challengeMounted, updateSuccessMessageSaga)
|
||||
];
|
||||
}
|
||||
|
@ -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',
|
||||
'toggleVisibleEditor',
|
||||
'setAccessibilityMode',
|
||||
|
||||
'lastBlockChalSubmitted'
|
||||
'setAccessibilityMode'
|
||||
],
|
||||
ns
|
||||
);
|
||||
@ -171,10 +169,6 @@ export const setEditorFocusability = createAction(types.setEditorFocusability);
|
||||
export const toggleVisibleEditor = createAction(types.toggleVisibleEditor);
|
||||
export const setAccessibilityMode = createAction(types.setAccessibilityMode);
|
||||
|
||||
export const lastBlockChalSubmitted = createAction(
|
||||
types.lastBlockChalSubmitted
|
||||
);
|
||||
|
||||
export const currentTabSelector = state => state[ns].currentTab;
|
||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||
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