diff --git a/client/less/classic-modal.less b/client/less/classic-modal.less
new file mode 100644
index 0000000000..0434162d94
--- /dev/null
+++ b/client/less/classic-modal.less
@@ -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;
+ }
+ }
+}
diff --git a/client/less/main.less b/client/less/main.less
index c5e8895749..b245442a9c 100644
--- a/client/less/main.less
+++ b/client/less/main.less
@@ -1203,3 +1203,4 @@ and (max-width : 400px) {
@import "map.less";
@import "drawers.less";
@import "sk-wave.less";
+@import "classic-modal.less";
diff --git a/common/app/routes/challenges/components/Classic-Modal.jsx b/common/app/routes/challenges/components/Classic-Modal.jsx
new file mode 100644
index 0000000000..85ff2c39b8
--- /dev/null
+++ b/common/app/routes/challenges/components/Classic-Modal.jsx
@@ -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 (
+
+
+ { successMessage }
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ClassicModal.displayName = 'ClassicModal';
+ClassicModal.propTypes = propTypes;
diff --git a/common/app/routes/challenges/components/classic/Classic.jsx b/common/app/routes/challenges/components/classic/Classic.jsx
index 5d1617ea02..01b78f94d0 100644
--- a/common/app/routes/challenges/components/classic/Classic.jsx
+++ b/common/app/routes/challenges/components/classic/Classic.jsx
@@ -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 (
{ this.renderPreview(showPreview) }
+
);
}
diff --git a/common/app/routes/challenges/redux/actions.js b/common/app/routes/challenges/redux/actions.js
index d4bcb0a421..f258f48fff 100644
--- a/common/app/routes/challenges/redux/actions.js
+++ b/common/app/routes/challenges/redux/actions.js
@@ -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,
diff --git a/common/app/routes/challenges/redux/completion-saga.js b/common/app/routes/challenges/redux/completion-saga.js
index fdf3bc6861..0a92703fec 100644
--- a/common/app/routes/challenges/redux/completion-saga.js
+++ b/common/app/routes/challenges/redux/completion-saga.js
@@ -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) {
diff --git a/common/app/routes/challenges/redux/fetch-challenges-saga.js b/common/app/routes/challenges/redux/fetch-challenges-saga.js
index 63f41f999e..95413a28e0 100644
--- a/common/app/routes/challenges/redux/fetch-challenges-saga.js
+++ b/common/app/routes/challenges/redux/fetch-challenges-saga.js
@@ -1,5 +1,6 @@
import { Observable } from 'rx';
import debug from 'debug';
+
import { challengeSelector } from './selectors';
import types from './types';
import {
diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js
index ed9bd3f821..54ab917e4c 100644
--- a/common/app/routes/challenges/redux/reducer.js
+++ b/common/app/routes/challenges/redux/reducer.js
@@ -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,
diff --git a/common/app/routes/challenges/redux/types.js b/common/app/routes/challenges/redux/types.js
index 18772e0415..a4deb334a3 100644
--- a/common/app/routes/challenges/redux/types.js
+++ b/common/app/routes/challenges/redux/types.js
@@ -21,6 +21,8 @@ export default createTypes([
'updateHint',
'lockUntrustedCode',
'unlockUntrustedCode',
+ 'closeChallengeModal',
+ 'updateSuccessMessage',
// map
'updateFilter',