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 "map.less";
|
||||||
@import "drawers.less";
|
@import "drawers.less";
|
||||||
@import "sk-wave.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 SidePanel from './Side-Panel.jsx';
|
||||||
import Preview from './Preview.jsx';
|
import Preview from './Preview.jsx';
|
||||||
import BugModal from '../Bug-Modal.jsx';
|
import BugModal from '../Bug-Modal.jsx';
|
||||||
|
import ClassicModal from '../Classic-Modal.jsx';
|
||||||
import { challengeSelector } from '../../redux/selectors';
|
import { challengeSelector } from '../../redux/selectors';
|
||||||
import {
|
import {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
updateFile,
|
updateFile,
|
||||||
loadCode
|
loadCode,
|
||||||
|
submitChallenge,
|
||||||
|
closeChallengeModal,
|
||||||
|
updateSuccessMessage
|
||||||
} from '../../redux/actions';
|
} from '../../redux/actions';
|
||||||
|
import { randomCompliment } from '../../../../utils/get-words';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
challengeSelector,
|
challengeSelector,
|
||||||
@ -21,26 +26,35 @@ const mapStateToProps = createSelector(
|
|||||||
state => state.challengesApp.tests,
|
state => state.challengesApp.tests,
|
||||||
state => state.challengesApp.files,
|
state => state.challengesApp.files,
|
||||||
state => state.challengesApp.key,
|
state => state.challengesApp.key,
|
||||||
|
state => state.challengesApp.isChallengeModalOpen,
|
||||||
|
state => state.challengesApp.successMessage,
|
||||||
(
|
(
|
||||||
{ showPreview, mode },
|
{ showPreview, mode },
|
||||||
id,
|
id,
|
||||||
tests,
|
tests,
|
||||||
files = {},
|
files = {},
|
||||||
key = ''
|
key = '',
|
||||||
|
isChallengeModalOpen,
|
||||||
|
successMessage,
|
||||||
) => ({
|
) => ({
|
||||||
id,
|
id,
|
||||||
content: files[key] && files[key].contents || '',
|
content: files[key] && files[key].contents || '',
|
||||||
file: files[key],
|
file: files[key],
|
||||||
showPreview,
|
showPreview,
|
||||||
mode,
|
mode,
|
||||||
tests
|
tests,
|
||||||
|
isChallengeModalOpen,
|
||||||
|
successMessage
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const bindableActions = {
|
const bindableActions = {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
updateFile,
|
updateFile,
|
||||||
loadCode
|
loadCode,
|
||||||
|
submitChallenge,
|
||||||
|
closeChallengeModal,
|
||||||
|
updateSuccessMessage
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Challenge extends PureComponent {
|
export class Challenge extends PureComponent {
|
||||||
@ -54,16 +68,23 @@ export class Challenge extends PureComponent {
|
|||||||
file: PropTypes.object,
|
file: PropTypes.object,
|
||||||
updateFile: PropTypes.func,
|
updateFile: PropTypes.func,
|
||||||
executeChallenge: 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() {
|
componentDidMount() {
|
||||||
this.props.loadCode();
|
this.props.loadCode();
|
||||||
|
this.props.updateSuccessMessage(randomCompliment());
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.id !== nextProps.id) {
|
if (this.props.id !== nextProps.id) {
|
||||||
this.props.loadCode();
|
this.props.loadCode();
|
||||||
|
this.props.updateSuccessMessage(randomCompliment());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +109,13 @@ export class Challenge extends PureComponent {
|
|||||||
file,
|
file,
|
||||||
mode,
|
mode,
|
||||||
showPreview,
|
showPreview,
|
||||||
executeChallenge
|
executeChallenge,
|
||||||
|
submitChallenge,
|
||||||
|
successMessage,
|
||||||
|
isChallengeModalOpen,
|
||||||
|
closeChallengeModal
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Col
|
<Col
|
||||||
@ -111,6 +137,12 @@ export class Challenge extends PureComponent {
|
|||||||
</Col>
|
</Col>
|
||||||
{ this.renderPreview(showPreview) }
|
{ this.renderPreview(showPreview) }
|
||||||
<BugModal />
|
<BugModal />
|
||||||
|
<ClassicModal
|
||||||
|
close={ closeChallengeModal }
|
||||||
|
open={ isChallengeModalOpen }
|
||||||
|
submitChallenge={ submitChallenge }
|
||||||
|
successMessage={ successMessage }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ export const fetchChallengeCompleted = createAction(
|
|||||||
(_, challenge) => challenge,
|
(_, challenge) => challenge,
|
||||||
entities => ({ entities })
|
entities => ({ entities })
|
||||||
);
|
);
|
||||||
|
export const closeChallengeModal = createAction(types.closeChallengeModal);
|
||||||
export const resetUi = createAction(types.resetUi);
|
export const resetUi = createAction(types.resetUi);
|
||||||
export const updateHint = createAction(types.updateHint);
|
export const updateHint = createAction(types.updateHint);
|
||||||
export const lockUntrustedCode = createAction(types.lockUntrustedCode);
|
export const lockUntrustedCode = createAction(types.lockUntrustedCode);
|
||||||
@ -29,7 +30,7 @@ export const unlockUntrustedCode = createAction(
|
|||||||
types.unlockUntrustedCode,
|
types.unlockUntrustedCode,
|
||||||
() => null
|
() => null
|
||||||
);
|
);
|
||||||
|
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
|
||||||
export const fetchChallenges = createAction(types.fetchChallenges);
|
export const fetchChallenges = createAction(types.fetchChallenges);
|
||||||
export const fetchChallengesCompleted = createAction(
|
export const fetchChallengesCompleted = createAction(
|
||||||
types.fetchChallengesCompleted,
|
types.fetchChallengesCompleted,
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
} from './actions';
|
} from './actions';
|
||||||
|
|
||||||
import { challengeSelector } from './selectors';
|
import { challengeSelector } from './selectors';
|
||||||
import { randomCompliment } from '../../../utils/get-words';
|
|
||||||
import {
|
import {
|
||||||
createErrorObservable,
|
createErrorObservable,
|
||||||
updateUserPoints,
|
updateUserPoints,
|
||||||
@ -40,14 +39,7 @@ function submitModern(type, state) {
|
|||||||
const { tests } = state.challengesApp;
|
const { tests } = state.challengesApp;
|
||||||
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
|
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
|
||||||
if (type === types.checkChallenge) {
|
if (type === types.checkChallenge) {
|
||||||
return Observable.of(
|
return Observable.just(null);
|
||||||
makeToast({
|
|
||||||
message: `${randomCompliment()} Go to your next challenge.`,
|
|
||||||
action: 'Submit',
|
|
||||||
actionCreator: 'submitChallenge',
|
|
||||||
timeout: 10000
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === types.submitChallenge) {
|
if (type === types.submitChallenge) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
||||||
import { challengeSelector } from './selectors';
|
import { challengeSelector } from './selectors';
|
||||||
import types from './types';
|
import types from './types';
|
||||||
import {
|
import {
|
||||||
|
@ -42,7 +42,9 @@ const initialUiState = {
|
|||||||
isPressed: false,
|
isPressed: false,
|
||||||
isCorrect: false,
|
isCorrect: false,
|
||||||
shouldShakeQuestion: false,
|
shouldShakeQuestion: false,
|
||||||
shouldShowQuestions: false
|
shouldShowQuestions: false,
|
||||||
|
isChallengeModalOpen: false,
|
||||||
|
successMessage: 'Happy Coding!'
|
||||||
};
|
};
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isCodeLocked: false,
|
isCodeLocked: false,
|
||||||
@ -81,7 +83,19 @@ const mainReducer = handleActions(
|
|||||||
}),
|
}),
|
||||||
[types.updateTests]: (state, { payload: tests }) => ({
|
[types.updateTests]: (state, { payload: tests }) => ({
|
||||||
...state,
|
...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 => ({
|
[types.updateHint]: state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -21,6 +21,8 @@ export default createTypes([
|
|||||||
'updateHint',
|
'updateHint',
|
||||||
'lockUntrustedCode',
|
'lockUntrustedCode',
|
||||||
'unlockUntrustedCode',
|
'unlockUntrustedCode',
|
||||||
|
'closeChallengeModal',
|
||||||
|
'updateSuccessMessage',
|
||||||
|
|
||||||
// map
|
// map
|
||||||
'updateFilter',
|
'updateFilter',
|
||||||
|
Reference in New Issue
Block a user