Skip to content

Commit a70b10b

Browse files
authored
Merge pull request freeCodeCamp#11253 from Bouncey/feature/NewSuccessModal
Reactified Success Modal
2 parents 1a7db66 + 60d46e0 commit a70b10b

File tree

9 files changed

+176
-18
lines changed

9 files changed

+176
-18
lines changed

client/less/classic-modal.less

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.challenge-success-modal {
2+
display: flex;
3+
flex-direction: column;
4+
justify-content: center;
5+
height: 50vh;
6+
7+
.modal-header {
8+
margin-bottom: 0;
9+
10+
.close {
11+
color: #eee;
12+
font-size: 4rem;
13+
opacity: 0.6;
14+
transition: all 300ms ease-out;
15+
margin-top: 0;
16+
padding-left: 0;
17+
18+
&:hover {
19+
opacity: 1;
20+
}
21+
}
22+
}
23+
24+
.modal-body {
25+
padding: 35px;
26+
display: flex;
27+
flex-direction: column;
28+
justify-content: center;
29+
30+
.fa {
31+
margin-right: 0;
32+
}
33+
}
34+
}

client/less/main.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,3 +1203,4 @@ and (max-width : 400px) {
12031203
@import "map.less";
12041204
@import "drawers.less";
12051205
@import "sk-wave.less";
1206+
@import "classic-modal.less";
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { PropTypes } from 'react';
2+
import { Button, Modal } from 'react-bootstrap';
3+
import PureComponent from 'react-pure-render/component';
4+
import FontAwesome from 'react-fontawesome';
5+
6+
const propTypes = {
7+
close: PropTypes.func,
8+
open: PropTypes.bool.isRequired,
9+
submitChallenge: PropTypes.func.isRequired,
10+
successMessage: PropTypes.string.isRequired
11+
};
12+
13+
export default class ClassicModal extends PureComponent {
14+
constructor(...props) {
15+
super(...props);
16+
this.handleKeyDown = this.handleKeyDown.bind(this);
17+
}
18+
19+
handleKeyDown(e) {
20+
const { open, submitChallenge } = this.props;
21+
if (
22+
e.keyCode === 13 &&
23+
(e.ctrlKey || e.meta) &&
24+
open
25+
) {
26+
e.preventDefault();
27+
submitChallenge();
28+
}
29+
}
30+
31+
render() {
32+
const {
33+
close,
34+
open,
35+
submitChallenge,
36+
successMessage
37+
} = this.props;
38+
return (
39+
<Modal
40+
animation={ false }
41+
dialogClassName='challenge-success-modal'
42+
keyboard={ true }
43+
onHide={ close }
44+
onKeyDown={ this.handleKeyDown }
45+
show={ open }
46+
>
47+
<Modal.Header
48+
className='challenge-list-header'
49+
closeButton={ true }
50+
>
51+
<Modal.Title>{ successMessage }</Modal.Title>
52+
</Modal.Header>
53+
<Modal.Body>
54+
<div className='text-center'>
55+
<div className='row'>
56+
<div>
57+
<FontAwesome
58+
className='completion-icon text-primary'
59+
name='check-circle'
60+
/>
61+
</div>
62+
</div>
63+
</div>
64+
</Modal.Body>
65+
<Modal.Footer>
66+
<Button
67+
block={ true }
68+
bsSize='large'
69+
bsStyle='primary'
70+
onClick={ submitChallenge }
71+
>
72+
Submit and go to next challenge (Ctrl + Enter)
73+
</Button>
74+
</Modal.Footer>
75+
</Modal>
76+
);
77+
}
78+
}
79+
80+
ClassicModal.displayName = 'ClassicModal';
81+
ClassicModal.propTypes = propTypes;

common/app/routes/challenges/components/classic/Classic.jsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,53 @@ import Editor from './Editor.jsx';
88
import SidePanel from './Side-Panel.jsx';
99
import Preview from './Preview.jsx';
1010
import BugModal from '../Bug-Modal.jsx';
11+
import ClassicModal from '../Classic-Modal.jsx';
1112
import { challengeSelector } from '../../redux/selectors';
1213
import {
1314
executeChallenge,
1415
updateFile,
15-
loadCode
16+
loadCode,
17+
submitChallenge,
18+
closeChallengeModal,
19+
updateSuccessMessage
1620
} from '../../redux/actions';
21+
import { randomCompliment } from '../../../../utils/get-words';
1722

1823
const mapStateToProps = createSelector(
1924
challengeSelector,
2025
state => state.challengesApp.id,
2126
state => state.challengesApp.tests,
2227
state => state.challengesApp.files,
2328
state => state.challengesApp.key,
29+
state => state.challengesApp.isChallengeModalOpen,
30+
state => state.challengesApp.successMessage,
2431
(
2532
{ showPreview, mode },
2633
id,
2734
tests,
2835
files = {},
29-
key = ''
36+
key = '',
37+
isChallengeModalOpen,
38+
successMessage,
3039
) => ({
3140
id,
3241
content: files[key] && files[key].contents || '',
3342
file: files[key],
3443
showPreview,
3544
mode,
36-
tests
45+
tests,
46+
isChallengeModalOpen,
47+
successMessage
3748
})
3849
);
3950

4051
const bindableActions = {
4152
executeChallenge,
4253
updateFile,
43-
loadCode
54+
loadCode,
55+
submitChallenge,
56+
closeChallengeModal,
57+
updateSuccessMessage
4458
};
4559

4660
export class Challenge extends PureComponent {
@@ -54,16 +68,23 @@ export class Challenge extends PureComponent {
5468
file: PropTypes.object,
5569
updateFile: PropTypes.func,
5670
executeChallenge: PropTypes.func,
57-
loadCode: PropTypes.func
71+
loadCode: PropTypes.func,
72+
submitChallenge: PropTypes.func,
73+
isChallengeModalOpen: PropTypes.bool,
74+
closeChallengeModal: PropTypes.func,
75+
successMessage: PropTypes.string,
76+
updateSuccessMessage: PropTypes.func
5877
};
5978

6079
componentDidMount() {
6180
this.props.loadCode();
81+
this.props.updateSuccessMessage(randomCompliment());
6282
}
6383

6484
componentWillReceiveProps(nextProps) {
6585
if (this.props.id !== nextProps.id) {
6686
this.props.loadCode();
87+
this.props.updateSuccessMessage(randomCompliment());
6788
}
6889
}
6990

@@ -88,8 +109,13 @@ export class Challenge extends PureComponent {
88109
file,
89110
mode,
90111
showPreview,
91-
executeChallenge
112+
executeChallenge,
113+
submitChallenge,
114+
successMessage,
115+
isChallengeModalOpen,
116+
closeChallengeModal
92117
} = this.props;
118+
93119
return (
94120
<div>
95121
<Col
@@ -111,6 +137,12 @@ export class Challenge extends PureComponent {
111137
</Col>
112138
{ this.renderPreview(showPreview) }
113139
<BugModal />
140+
<ClassicModal
141+
close={ closeChallengeModal }
142+
open={ isChallengeModalOpen }
143+
submitChallenge={ submitChallenge }
144+
successMessage={ successMessage }
145+
/>
114146
</div>
115147
);
116148
}

common/app/routes/challenges/redux/actions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ export const fetchChallengeCompleted = createAction(
2222
(_, challenge) => challenge,
2323
entities => ({ entities })
2424
);
25+
export const closeChallengeModal = createAction(types.closeChallengeModal);
2526
export const resetUi = createAction(types.resetUi);
2627
export const updateHint = createAction(types.updateHint);
2728
export const lockUntrustedCode = createAction(types.lockUntrustedCode);
2829
export const unlockUntrustedCode = createAction(
2930
types.unlockUntrustedCode,
3031
() => null
3132
);
32-
33+
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
3334
export const fetchChallenges = createAction(types.fetchChallenges);
3435
export const fetchChallengesCompleted = createAction(
3536
types.fetchChallengesCompleted,

common/app/routes/challenges/redux/completion-saga.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
} from './actions';
88

99
import { challengeSelector } from './selectors';
10-
import { randomCompliment } from '../../../utils/get-words';
1110
import {
1211
createErrorObservable,
1312
updateUserPoints,
@@ -40,14 +39,7 @@ function submitModern(type, state) {
4039
const { tests } = state.challengesApp;
4140
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
4241
if (type === types.checkChallenge) {
43-
return Observable.of(
44-
makeToast({
45-
message: `${randomCompliment()} Go to your next challenge.`,
46-
action: 'Submit',
47-
actionCreator: 'submitChallenge',
48-
timeout: 10000
49-
})
50-
);
42+
return Observable.just(null);
5143
}
5244

5345
if (type === types.submitChallenge) {

common/app/routes/challenges/redux/fetch-challenges-saga.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Observable } from 'rx';
22
import debug from 'debug';
3+
34
import { challengeSelector } from './selectors';
45
import types from './types';
56
import {

common/app/routes/challenges/redux/reducer.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ const initialUiState = {
4242
isPressed: false,
4343
isCorrect: false,
4444
shouldShakeQuestion: false,
45-
shouldShowQuestions: false
45+
shouldShowQuestions: false,
46+
isChallengeModalOpen: false,
47+
successMessage: 'Happy Coding!'
4648
};
4749
const initialState = {
4850
isCodeLocked: false,
@@ -81,7 +83,19 @@ const mainReducer = handleActions(
8183
}),
8284
[types.updateTests]: (state, { payload: tests }) => ({
8385
...state,
84-
tests
86+
tests,
87+
isChallengeModalOpen: (
88+
tests.length > 0 &&
89+
tests.every(test => test.pass && !test.err)
90+
)
91+
}),
92+
[types.closeChallengeModal]: state => ({
93+
...state,
94+
isChallengeModalOpen: false
95+
}),
96+
[types.updateSuccessMessage]: (state, { payload }) => ({
97+
...state,
98+
successMessage: payload
8599
}),
86100
[types.updateHint]: state => ({
87101
...state,

common/app/routes/challenges/redux/types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export default createTypes([
2121
'updateHint',
2222
'lockUntrustedCode',
2323
'unlockUntrustedCode',
24+
'closeChallengeModal',
25+
'updateSuccessMessage',
2426

2527
// map
2628
'updateFilter',

0 commit comments

Comments
 (0)