Merge pull request #11253 from Bouncey/feature/NewSuccessModal
Reactified Success Modal
This commit is contained in:
34
client/less/classic-modal.less
Normal file
34
client/less/classic-modal.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1203,3 +1203,4 @@ and (max-width : 400px) {
|
||||
@import "map.less";
|
||||
@import "drawers.less";
|
||||
@import "sk-wave.less";
|
||||
@import "classic-modal.less";
|
||||
|
81
common/app/routes/challenges/components/Classic-Modal.jsx
Normal file
81
common/app/routes/challenges/components/Classic-Modal.jsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Observable } from 'rx';
|
||||
import debug from 'debug';
|
||||
|
||||
import { challengeSelector } from './selectors';
|
||||
import types from './types';
|
||||
import {
|
||||
|
@ -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,
|
||||
|
@ -21,6 +21,8 @@ export default createTypes([
|
||||
'updateHint',
|
||||
'lockUntrustedCode',
|
||||
'unlockUntrustedCode',
|
||||
'closeChallengeModal',
|
||||
'updateSuccessMessage',
|
||||
|
||||
// map
|
||||
'updateFilter',
|
||||
|
Reference in New Issue
Block a user