fix(client): block donation modal (#40899)

Co-authored-by: Kris Koishigawa <scissorsneedfoodtoo@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2021-02-08 10:28:36 +03:00
committed by GitHub
parent b1209f5547
commit 3c7979692b
8 changed files with 92 additions and 74 deletions

View File

@ -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 />

View File

@ -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());

View File

@ -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 => ({
...state,
canRequestBlockDonation: true
}),
[types.allowBlockDonationRequests]: (state, { payload }) => {
return {
...state,
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,

View File

@ -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);
}
}

View File

@ -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)
];
}

View File

@ -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
);
});
});

View File

@ -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;

View File

@ -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.'
);
});
});