Feature(challenges): add bug modal and logic

This commit is contained in:
Berkeley Martinez
2016-07-11 21:54:55 -07:00
parent efcfaf0391
commit 57b6debb44
11 changed files with 202 additions and 13 deletions

View File

@ -0,0 +1,80 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { Button, Modal } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
import { createIssue, openIssueSearch, closeBugModal } from '../redux/actions';
const mapStateToProps = state => ({ isOpen: state.challengesApp.isBugOpen });
const actions = { createIssue, openIssueSearch, closeBugModal };
const bugLink = 'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' +
'FreeCodeCamp-Report-Bugs';
export class BugModal extends PureComponent {
static propTypes = {
isOpen: PropTypes.bool,
closeBugModal: PropTypes.func,
openIssueSearch: PropTypes.func,
createIssue: PropTypes.func
};
render() {
const {
isOpen,
closeBugModal,
openIssueSearch,
createIssue
} = this.props;
return (
<Modal
onHide={ closeBugModal }
show={ isOpen }
>
<Modal.Header className='challenge-list-header'>
Did you find a bug?
<span className='close closing-x'>×</span>
</Modal.Header>
<Modal.Body className='text-center'>
<h3>
Before you submit a new issue,
read "Help I've Found a Bug" and
browse other issues with this challenge.
</h3>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
href={ bugLink }
target='_blank'
>
Read "Help I've Found a Bug"
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
onClick={ openIssueSearch }
>
Browse other issues with this challenge
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
onClick={ createIssue }
>
Create my GitHub issue
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
>
Cancel
</Button>
</Modal.Body>
</Modal>
);
}
}
export default connect(mapStateToProps, actions)(BugModal);

View File

@ -7,6 +7,7 @@ import PureComponent from 'react-pure-render/component';
import Editor from './Editor.jsx';
import SidePanel from './Side-Panel.jsx';
import Preview from './Preview.jsx';
import BugModal from '../Bug-Modal.jsx';
import { challengeSelector } from '../../redux/selectors';
import {
executeChallenge,
@ -102,6 +103,7 @@ export class Challenge extends PureComponent {
/>
</Col>
{ this.renderPreview(showPreview) }
<BugModal />
</div>
);
}

View File

@ -9,7 +9,11 @@ import TestSuite from './Test-Suite.jsx';
import Output from './Output.jsx';
import ToolPanel from './Tool-Panel.jsx';
import { challengeSelector } from '../../redux/selectors';
import { updateHint, executeChallenge } from '../../redux/actions';
import {
openBugModal,
updateHint,
executeChallenge
} from '../../redux/actions';
import { makeToast } from '../../../../toasts/redux/actions';
import { toggleHelpChat } from '../../../../redux/actions';
@ -17,7 +21,8 @@ const bindableActions = {
makeToast,
executeChallenge,
updateHint,
toggleHelpChat
toggleHelpChat,
openBugModal
};
const mapStateToProps = createSelector(
challengeSelector,
@ -59,7 +64,8 @@ export class SidePanel extends PureComponent {
hints: PropTypes.string,
updateHint: PropTypes.func,
makeToast: PropTypes.func,
toggleHelpChat: PropTypes.func
toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func
};
renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) {
@ -99,7 +105,8 @@ export class SidePanel extends PureComponent {
executeChallenge,
updateHint,
makeToast,
toggleHelpChat
toggleHelpChat,
openBugModal
} = this.props;
const style = {
overflowX: 'hidden',
@ -131,6 +138,7 @@ export class SidePanel extends PureComponent {
executeChallenge={ executeChallenge }
hint={ hint }
makeToast={ makeToast }
openBugModal={ openBugModal }
toggleHelpChat={ toggleHelpChat }
updateHint={ updateHint }
/>

View File

@ -14,7 +14,8 @@ export default class ToolPanel extends PureComponent {
executeChallenge: PropTypes.func,
updateHint: PropTypes.func,
hint: PropTypes.string,
toggleHelpChat: PropTypes.func
toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func
};
makeHint() {
@ -54,7 +55,8 @@ export default class ToolPanel extends PureComponent {
const {
hint,
executeChallenge,
toggleHelpChat
toggleHelpChat,
openBugModal
} = this.props;
return (
<div>
@ -92,6 +94,7 @@ export default class ToolPanel extends PureComponent {
bsSize='large'
bsStyle='primary'
componentClass='label'
onClick={ openBugModal }
>
Bug
</Button>

View File

@ -7,6 +7,7 @@ import PureComponent from 'react-pure-render/component';
import { Col } from 'react-bootstrap';
import SidePanel from './Side-Panel.jsx';
import ToolPanel from './Tool-Panel.jsx';
import BugModal from '../Bug-Modal.jsx';
import { challengeSelector } from '../../redux/selectors';
@ -71,6 +72,7 @@ export class Project extends PureComponent {
<br />
<ToolPanel />
<br />
<BugModal />
</Col>
</div>
);

View File

@ -9,7 +9,7 @@ import {
BackEndForm
} from './Forms.jsx';
import { submitChallenge } from '../../redux/actions';
import { submitChallenge, openBugModal } from '../../redux/actions';
import { challengeSelector } from '../../redux/selectors';
import {
simpleProject,
@ -19,7 +19,8 @@ import { toggleHelpChat } from '../../../../redux/actions';
const bindableActions = {
submitChallenge,
toggleHelpChat
toggleHelpChat,
openBugModal
};
const mapStateToProps = createSelector(
challengeSelector,
@ -43,7 +44,8 @@ export class ToolPanel extends PureComponent {
isSimple: PropTypes.bool,
isFrontEnd: PropTypes.bool,
isSubmitting: PropTypes.bool,
toggleHelpChat: PropTypes.func
toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func
};
renderSubmitButton(isSignedIn, submitChallenge) {
@ -69,7 +71,8 @@ export class ToolPanel extends PureComponent {
isSignedIn,
isSubmitting,
submitChallenge,
toggleHelpChat
toggleHelpChat,
openBugModal
} = this.props;
const FormElement = isFrontEnd ? FrontEndForm : BackEndForm;
@ -94,6 +97,7 @@ export class ToolPanel extends PureComponent {
bsStyle='primary'
className='btn-primary-ghost btn-big'
componentClass='div'
onClick={ openBugModal }
>
Bug
</Button>

View File

@ -139,3 +139,9 @@ export const endShake = createAction(types.primeNextQuestion);
export const goToNextQuestion = createAction(types.goToNextQuestion);
export const videoCompleted = createAction(types.videoCompleted);
// bug
export const openBugModal = createAction(types.openBugModal);
export const closeBugModal = createAction(types.closeBugModal);
export const openIssueSearch = createAction(types.openIssueSearch);
export const createIssue = createAction(types.createIssue);

View File

@ -0,0 +1,72 @@
import dedent from 'dedent';
import types from '../redux/types';
import { closeBugModal } from '../redux/actions';
function filesToMarkdown(files = {}) {
const moreThenOneFile = Object.keys(files).length > 1;
return Object.keys(files).reduce((fileString, key) => {
const file = files[key];
if (!file) {
return fileString;
}
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
const fileType = file.ext;
return fileString + dedent`
\`\`\`${fileType}
${fileName}
${file.contents}
\`\`\`
\n
`;
}, '\n');
}
export default function bugSaga(actions$, getState, { window }) {
return actions$
.filter(({ type }) => (
type === types.openIssueSearch ||
type === types.createIssue
))
.map(({ type }) => {
const {
challengesApp: {
challenge: challengeName,
files
}
} = getState();
const {
navigator: { userAgent },
location: { href }
} = window;
if (type === types.openIssueSearch) {
window.open(
'https://github.com/FreeCodeCamp/FreeCodeCamp/issues?q=' +
'is:issue is:all ' +
challengeName
);
}
let textMessage = [
'Challenge [',
challengeName,
'](',
href,
') has an issue.\n',
'User Agent is: <code>',
userAgent,
'</code>.\n',
'Please describe how to reproduce this issue, and include ',
'links to screenshots if possible.\n\n'
].join('');
const body = filesToMarkdown(files);
if (body.length > 10) {
textMessage = textMessage + body;
}
window.open(
'https://github.com/freecodecamp/freecodecamp/issues/new?&body=' +
window.encodeURIComponent(textMessage),
'_blank'
);
return closeBugModal();
});
}

View File

@ -3,6 +3,7 @@ import completionSaga from './completion-saga';
import nextChallengeSaga from './next-challenge-saga';
import answerSaga from './answer-saga';
import resetChallengeSaga from './reset-challenge-saga';
import bugSaga from './bug-saga';
export * as actions from './actions';
export reducer from './reducer';
@ -15,5 +16,6 @@ export const sagas = [
completionSaga,
nextChallengeSaga,
answerSaga,
resetChallengeSaga
resetChallengeSaga,
bugSaga
];

View File

@ -43,6 +43,7 @@ const initialState = {
id: '',
challenge: '',
helpChatRoom: 'Help',
isBugOpen: false,
// old code storage key
legacyKey: '',
files: {},
@ -185,7 +186,10 @@ const mainReducer = handleActions(
isPressed: false,
delta: [ 0, 0 ],
mouse: [ userAnswer ? 1000 : -1000, 0]
})
}),
[types.openBugModal]: state => ({ ...state, isBugOpen: true }),
[types.closeBugModal]: state => ({ ...state, isBugOpen: false })
},
initialState
);

View File

@ -64,5 +64,11 @@ export default createTypes([
'primeNextQuestion',
'goToNextQuestion',
'transitionVideo',
'videoCompleted'
'videoCompleted',
// bug
'openBugModal',
'closeBugModal',
'openIssueSearch',
'createIssue'
], 'challenges');