Merge pull request #11253 from Bouncey/feature/NewSuccessModal

Reactified Success Modal
This commit is contained in:
Berkeley Martinez
2016-12-05 15:41:28 -08:00
committed by GitHub
9 changed files with 176 additions and 18 deletions

View File

@ -0,0 +1,34 @@
.challenge-success-modal {
display: flex;
flex-direction: column;
justify-content: center;
height: 50vh;
.modal-header {
margin-bottom: 0;
.close {
color: #eee;
font-size: 4rem;
opacity: 0.6;
transition: all 300ms ease-out;
margin-top: 0;
padding-left: 0;
&:hover {
opacity: 1;
}
}
}
.modal-body {
padding: 35px;
display: flex;
flex-direction: column;
justify-content: center;
.fa {
margin-right: 0;
}
}
}

View File

@ -1203,3 +1203,4 @@ and (max-width : 400px) {
@import "map.less";
@import "drawers.less";
@import "sk-wave.less";
@import "classic-modal.less";

View File

@ -0,0 +1,81 @@
import React, { PropTypes } from 'react';
import { Button, Modal } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
import FontAwesome from 'react-fontawesome';
const propTypes = {
close: PropTypes.func,
open: PropTypes.bool.isRequired,
submitChallenge: PropTypes.func.isRequired,
successMessage: PropTypes.string.isRequired
};
export default class ClassicModal extends PureComponent {
constructor(...props) {
super(...props);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
handleKeyDown(e) {
const { open, submitChallenge } = this.props;
if (
e.keyCode === 13 &&
(e.ctrlKey || e.meta) &&
open
) {
e.preventDefault();
submitChallenge();
}
}
render() {
const {
close,
open,
submitChallenge,
successMessage
} = this.props;
return (
<Modal
animation={ false }
dialogClassName='challenge-success-modal'
keyboard={ true }
onHide={ close }
onKeyDown={ this.handleKeyDown }
show={ open }
>
<Modal.Header
className='challenge-list-header'
closeButton={ true }
>
<Modal.Title>{ successMessage }</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className='text-center'>
<div className='row'>
<div>
<FontAwesome
className='completion-icon text-primary'
name='check-circle'
/>
</div>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button
block={ true }
bsSize='large'
bsStyle='primary'
onClick={ submitChallenge }
>
Submit and go to next challenge (Ctrl + Enter)
</Button>
</Modal.Footer>
</Modal>
);
}
}
ClassicModal.displayName = 'ClassicModal';
ClassicModal.propTypes = propTypes;

View File

@ -8,12 +8,17 @@ import Editor from './Editor.jsx';
import SidePanel from './Side-Panel.jsx';
import Preview from './Preview.jsx';
import BugModal from '../Bug-Modal.jsx';
import ClassicModal from '../Classic-Modal.jsx';
import { challengeSelector } from '../../redux/selectors';
import {
executeChallenge,
updateFile,
loadCode
loadCode,
submitChallenge,
closeChallengeModal,
updateSuccessMessage
} from '../../redux/actions';
import { randomCompliment } from '../../../../utils/get-words';
const mapStateToProps = createSelector(
challengeSelector,
@ -21,26 +26,35 @@ const mapStateToProps = createSelector(
state => state.challengesApp.tests,
state => state.challengesApp.files,
state => state.challengesApp.key,
state => state.challengesApp.isChallengeModalOpen,
state => state.challengesApp.successMessage,
(
{ showPreview, mode },
id,
tests,
files = {},
key = ''
key = '',
isChallengeModalOpen,
successMessage,
) => ({
id,
content: files[key] && files[key].contents || '',
file: files[key],
showPreview,
mode,
tests
tests,
isChallengeModalOpen,
successMessage
})
);
const bindableActions = {
executeChallenge,
updateFile,
loadCode
loadCode,
submitChallenge,
closeChallengeModal,
updateSuccessMessage
};
export class Challenge extends PureComponent {
@ -54,16 +68,23 @@ export class Challenge extends PureComponent {
file: PropTypes.object,
updateFile: PropTypes.func,
executeChallenge: PropTypes.func,
loadCode: PropTypes.func
loadCode: PropTypes.func,
submitChallenge: PropTypes.func,
isChallengeModalOpen: PropTypes.bool,
closeChallengeModal: PropTypes.func,
successMessage: PropTypes.string,
updateSuccessMessage: PropTypes.func
};
componentDidMount() {
this.props.loadCode();
this.props.updateSuccessMessage(randomCompliment());
}
componentWillReceiveProps(nextProps) {
if (this.props.id !== nextProps.id) {
this.props.loadCode();
this.props.updateSuccessMessage(randomCompliment());
}
}
@ -88,8 +109,13 @@ export class Challenge extends PureComponent {
file,
mode,
showPreview,
executeChallenge
executeChallenge,
submitChallenge,
successMessage,
isChallengeModalOpen,
closeChallengeModal
} = this.props;
return (
<div>
<Col
@ -111,6 +137,12 @@ export class Challenge extends PureComponent {
</Col>
{ this.renderPreview(showPreview) }
<BugModal />
<ClassicModal
close={ closeChallengeModal }
open={ isChallengeModalOpen }
submitChallenge={ submitChallenge }
successMessage={ successMessage }
/>
</div>
);
}

View File

@ -22,6 +22,7 @@ export const fetchChallengeCompleted = createAction(
(_, challenge) => challenge,
entities => ({ entities })
);
export const closeChallengeModal = createAction(types.closeChallengeModal);
export const resetUi = createAction(types.resetUi);
export const updateHint = createAction(types.updateHint);
export const lockUntrustedCode = createAction(types.lockUntrustedCode);
@ -29,7 +30,7 @@ export const unlockUntrustedCode = createAction(
types.unlockUntrustedCode,
() => null
);
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
export const fetchChallenges = createAction(types.fetchChallenges);
export const fetchChallengesCompleted = createAction(
types.fetchChallengesCompleted,

View File

@ -7,7 +7,6 @@ import {
} from './actions';
import { challengeSelector } from './selectors';
import { randomCompliment } from '../../../utils/get-words';
import {
createErrorObservable,
updateUserPoints,
@ -40,14 +39,7 @@ function submitModern(type, state) {
const { tests } = state.challengesApp;
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
if (type === types.checkChallenge) {
return Observable.of(
makeToast({
message: `${randomCompliment()} Go to your next challenge.`,
action: 'Submit',
actionCreator: 'submitChallenge',
timeout: 10000
})
);
return Observable.just(null);
}
if (type === types.submitChallenge) {

View File

@ -1,5 +1,6 @@
import { Observable } from 'rx';
import debug from 'debug';
import { challengeSelector } from './selectors';
import types from './types';
import {

View File

@ -42,7 +42,9 @@ const initialUiState = {
isPressed: false,
isCorrect: false,
shouldShakeQuestion: false,
shouldShowQuestions: false
shouldShowQuestions: false,
isChallengeModalOpen: false,
successMessage: 'Happy Coding!'
};
const initialState = {
isCodeLocked: false,
@ -81,7 +83,19 @@ const mainReducer = handleActions(
}),
[types.updateTests]: (state, { payload: tests }) => ({
...state,
tests
tests,
isChallengeModalOpen: (
tests.length > 0 &&
tests.every(test => test.pass && !test.err)
)
}),
[types.closeChallengeModal]: state => ({
...state,
isChallengeModalOpen: false
}),
[types.updateSuccessMessage]: (state, { payload }) => ({
...state,
successMessage: payload
}),
[types.updateHint]: state => ({
...state,

View File

@ -21,6 +21,8 @@ export default createTypes([
'updateHint',
'lockUntrustedCode',
'unlockUntrustedCode',
'closeChallengeModal',
'updateSuccessMessage',
// map
'updateFilter',