| @@ -7,6 +7,7 @@ | ||
| "cors": "^2.8.4", | ||
| "dotenv": "^6.0.0", | ||
| "express-jwt": "^5.3.1", | ||
| "hat": "0.0.3", | ||
| "lodash": "^4.17.10", | ||
| "mongodb": "^3.1.0-beta4", | ||
| "path": "^0.12.7", | ||
| @@ -0,0 +1,12 @@ | ||
| module.exports: [ | ||
| { | ||
| questionId, | ||
| type: "MULTIPLE_CHOICE", | ||
| choice: optionId | ||
| }, | ||
| { | ||
| questionId, | ||
| type: "OPEN_RESPONSE", | ||
| body: "Here's your answer" | ||
| } | ||
| ] |
| @@ -0,0 +1,24 @@ | ||
| const shortid = require("shortid"); | ||
|
|
||
| module.exports = { | ||
| name: "Customer Service", | ||
| id: shortid.generate(), | ||
| questions: [ | ||
| { | ||
| id: shortid(), | ||
| body: "question 1", | ||
| type: "MULTIPLE_CHOICE", | ||
| options: [ | ||
| { id: shortid.generate(), answer: "some answer", correct: false }, | ||
| { id: shortid.generate(), answer: "another answer", correct: false }, | ||
| { id: shortid.generate(), answer: "the right answer", correct: true }, | ||
| { id: shortid.generate(), answer: "bleh", correct: false } | ||
| ] | ||
| }, | ||
| { | ||
| id: shortid.generate(), | ||
| body: "question 2", | ||
| type: "OPEN_RESPONSE" | ||
| } | ||
| ] | ||
| }; |
| @@ -0,0 +1,54 @@ | ||
| import React from 'react'; | ||
|
|
||
| const Question = props => { | ||
| let question = ""; | ||
| switch (props.question.type) { | ||
| case "MULTIPLE_CHOICE": | ||
| question = ( | ||
| <div> | ||
| <p style={{ color: 'purple' }}>Exercise {props.index + 1}</p> | ||
| <p>{props.question.body}</p> | ||
| { | ||
| props.question.options.map(x => | ||
| <div key={x.id}> | ||
| <input | ||
| type="radio" | ||
| name={props.question.id} | ||
| value={x.answer} | ||
| onClick={props.handleChange} | ||
| /> | ||
| <label htmlFor={x.id}>{x.answer}</label> | ||
| </div> | ||
| ) | ||
| } | ||
| </div> | ||
| ); | ||
| break; | ||
| case "OPEN_RESPONSE": | ||
| question = ( | ||
| <div> | ||
| <p style={{ color: 'purple' }}>Exercise {props.index + 1}</p> | ||
| <p>{props.question.body}</p> | ||
| <textarea | ||
| type="text" | ||
| name={props.question.id} | ||
| placeholder="Place response here" | ||
| onChange={props.handleChange} | ||
| rows='10' | ||
| cols='110' | ||
| /> | ||
| </div> | ||
| ); | ||
| break; | ||
| default: | ||
| question = <p>Invalid question type</p>; | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| {question} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Question; |
| @@ -1,13 +1,103 @@ | ||
| import React from 'react'; | ||
|
|
||
| import Question from '../../../components/Question'; | ||
|
|
||
| class Test extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| answers: {}, | ||
| secondsElapsed: props.secondsElapsed | ||
| }; | ||
|
|
||
| this.handleChange = this.handleChange.bind(this); | ||
| this.handleSubmit = this.handleSubmit.bind(this); | ||
| this.incrementer = null; | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.incrementer = setInterval(() => | ||
| this.setState({ | ||
| secondsElapsed: this.state.secondsElapsed + 1 | ||
| }) | ||
| , 1000); | ||
| } | ||
|
|
||
| handleChange = e => { | ||
| this.setState({ | ||
| answers: { | ||
| ...this.state.answers, | ||
| [e.target.name]: e.target.value | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| handleSubmit = () => { | ||
| clearInterval(this.incrementer); | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Content-Type": "application/json" | ||
| }, | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| applicantId: this.props.applicant.id, | ||
| secondsElapsed: this.state.secondsElapsed, | ||
| answers: this.state.answers | ||
| }) | ||
| }; | ||
|
|
||
| fetch(`http://localhost:4567/api/applicant/test-results/${this.props.token}`, options) | ||
| .then(res => | ||
| res.status === 403 ? | ||
| Promise.reject("Auth denied") : | ||
| res.json() | ||
| ).then(data => { | ||
| if (!data.success) { | ||
| return this.props.propagateError(); | ||
| } | ||
|
|
||
| this.props.redirectToFinished(); | ||
| }).catch(err => console.error(err)); | ||
| }; | ||
|
|
||
| formattedSeconds = sec => { | ||
| return (Math.floor(sec / 60) + | ||
| ':' + | ||
| ('0' + sec % 60).slice(-2)); | ||
| }; | ||
|
|
||
| render() { | ||
| const style = { | ||
| submit: { | ||
| backgroundColor: 'purple', | ||
| textDecoration: 'none', | ||
| color: 'white', | ||
| padding: '10px', | ||
| cursor: 'pointer', | ||
| boxShadow: '2px 2px 1px 0px rgba(0,0,0,0.75)' | ||
| } | ||
| }; | ||
|
|
||
| const questions = this.props.test.questions.map((x, i) => | ||
| <div key={x.id}> | ||
| <Question | ||
| question={x} | ||
| index={i} | ||
| handleChange={this.handleChange} | ||
| /> | ||
| </div> | ||
| ); | ||
|
|
||
| return ( | ||
| <div style={{margin: '200px 500px', padding: '10px 30px 35px 30px', backgroundColor: '#cfcfd1', boxShadow: '1px 1px 1px 0px rgba(0,0,0,0.75)'}}> | ||
| <h1>BEGIN TESTING NOW</h1> | ||
| <h1 style={{color: 'red'}}>{this.formattedSeconds(this.state.secondsElapsed)}</h1> | ||
| {questions} | ||
| <a onClick={this.handleSubmit} style={style.submit}>SUBMIT</a> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default Test; |
| @@ -1,17 +1,39 @@ | ||
| import React from 'react'; | ||
|
|
||
| const WelcomePage = (props) => { | ||
| const style = { | ||
| outer: { | ||
| margin: '200px 500px', | ||
| padding: '10px 30px 35px 30px', | ||
| backgroundColor: '#cfcfd1', | ||
| boxShadow: '1px 1px 1px 0px rgba(0,0,0,0.75)' | ||
| }, | ||
| inner: { | ||
| paddingBottom: '10px' | ||
| }, | ||
| startBtn: { | ||
| backgroundColor: '#6d6dc4', | ||
| textDecoration: 'none', | ||
| color: 'white', | ||
| padding: '10px', | ||
| cursor: 'pointer', | ||
| boxShadow: '2px 2px 1px 0px rgba(0,0,0,0.75)' | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div style={style.outer}> | ||
| <div style={style.inner}> | ||
| <p>Hey {props.applicant.firstName}, thanks for showing iinterest in becoming a customer | ||
| service ninja. We think you have the potential to be a great fit | ||
| at our company. Before we can take this any further, we would like | ||
| you to respond to a couple questions. You only have 30 minutes | ||
| to finish all the questions upon the time you click “START TEST NOW”.</p> | ||
| <p><strong>Best of luck!</strong></p> | ||
| </div> | ||
| <a onClick={props.startTest} style={style.startBtn}>START TEST NOW</a> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default WelcomePage; |
| @@ -0,0 +1,83 @@ | ||
| import React, {Component} from 'react'; | ||
|
|
||
| class DeleteTestForm extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| nameConfirmation: "", | ||
| isError: false | ||
| }; | ||
|
|
||
| this.handleChange = this.handleChange.bind(this); | ||
| this.handleDelete = this.handleDelete.bind(this); | ||
| } | ||
|
|
||
| handleDelete() { | ||
| if (this.state.nameConfirmation !== this.props.name) { | ||
| return this.setState({ | ||
| isError: true | ||
| }); | ||
| } | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Authorization": `Bearer ${this.props.token}`, | ||
| "Content-Type": "application/json" | ||
| }, | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| id: this.props.id | ||
| }) | ||
| }; | ||
|
|
||
| fetch("http://localhost:4567/api/company/delete-test", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| console.log(data); | ||
| this.props.refreshTestData(this.props.firstTestId); | ||
| this.props.toggleDeleteForm(); | ||
| }).catch(err => { | ||
| console.error(err); | ||
| }) | ||
| } | ||
|
|
||
| handleChange(e) { | ||
| this.setState({ | ||
| [e.target.name]: e.target.value | ||
| }); | ||
| } | ||
|
|
||
| render() { | ||
| let errorMsg = ""; | ||
| if (this.state.isError) { | ||
| errorMsg = ( | ||
| <p style={{ color: 'red' }}> | ||
| The name you entered does not match | ||
| </p> | ||
| ) | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <h3 style={{ color: 'red '}}> | ||
| Enter name of test to delete: | ||
| </h3> | ||
| <input | ||
| type="text" | ||
| name="nameConfirmation" | ||
| onChange={this.handleChange} | ||
| /> | ||
| <button type="button" | ||
| style={{ backgroundColor: 'red' }} | ||
| onClick={this.handleDelete} | ||
| >Delete</button> | ||
| <button type="button" | ||
| onClick={this.props.toggleDeleteForm} | ||
| >Cancel</button> | ||
| {errorMsg} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default DeleteTestForm; |
| @@ -0,0 +1,150 @@ | ||
| import React, {Component} from 'react'; | ||
| import shortid from 'shortid'; | ||
|
|
||
| import ActionButtons from '../../../components/UI/Buttons/ActionButtons'; | ||
|
|
||
| class EditForm extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| questionType: props.question.type, | ||
| options: props.question.options, | ||
| correctAnswerId: props.question.type === "MULTIPLE_CHOICE" ? | ||
| props.question.options.find(x => x.correct) ? | ||
| props.question.options.find(x => x.correct).id : null | ||
| : null | ||
| }; | ||
|
|
||
| this.onSaveClick = this.onSaveClick.bind(this); | ||
| this.setCorrectAnswer = this.setCorrectAnswer.bind(this); | ||
| this.removeOption = this.removeOption.bind(this); | ||
| this.addOption = this.addOption.bind(this); | ||
| } | ||
|
|
||
| onSaveClick = event => { | ||
| event.preventDefault(); | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Authorization": `Bearer ${this.props.token}` | ||
| }, | ||
| method: "POST" | ||
| }; | ||
|
|
||
| const body = { | ||
| questionType: this.props.question.type, | ||
| body: this.refs.body.value, | ||
| questionId: this.props.question.id, | ||
| testId: this.props.testId | ||
| }; | ||
|
|
||
| if (this.props.question.type === "MULTIPLE_CHOICE") { | ||
| options.body = JSON.stringify({ | ||
| ...body, | ||
| options: this.state.options.filter(x => | ||
| this.refs[x.id].value.length > 0 | ||
| ).map(x => ({ | ||
| ...x, | ||
| answer: this.refs[x.id].value, | ||
| correct: x.id === this.state.correctAnswerId ? true : false | ||
| })) | ||
| }); | ||
| } else { | ||
| options.body = JSON.stringify(body); | ||
| } | ||
|
|
||
| fetch("http://localhost:4567/api/company/edit-question", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| console.log(data); | ||
| this.props.refreshTestData(); | ||
| this.props.toggleEditForm(); | ||
| }).catch(err => console.error(err)); | ||
| } | ||
|
|
||
| setCorrectAnswer(e) { | ||
| this.setState({ | ||
| correctAnswerId: e.target.value | ||
| }); | ||
| } | ||
|
|
||
| addOption() { | ||
| this.setState(prevState => ({ | ||
| options: prevState.options.concat({ | ||
| id: shortid.generate(), | ||
| answer: "", | ||
| correct: false | ||
| }) | ||
| })); | ||
| } | ||
|
|
||
| removeOption(id) { | ||
| this.setState(prevState => ({ | ||
| options: prevState.options.length > 2 ? | ||
| prevState.options.filter(x => x.id !== id) : | ||
| prevState.options | ||
| })); | ||
| } | ||
|
|
||
| render() { | ||
| let options = ""; | ||
| let addOptionBtn = ""; | ||
| if (this.props.question.type === "MULTIPLE_CHOICE") { | ||
| options = this.state.options.map(x => | ||
| <div key={x.id}> | ||
| <input | ||
| type="radio" | ||
| name="options" | ||
| value={x.id} | ||
| onClick={this.setCorrectAnswer} | ||
| defaultChecked={x.id === this.state.correctAnswerId ? true : false} | ||
| /> | ||
| <input | ||
| type="text" | ||
| style={{padding: '5px 10px'}} | ||
| ref={x.id} | ||
| defaultValue={x.answer} | ||
| /> | ||
| <input | ||
| type="button" | ||
| onClick={() => this.removeOption(x.id)} | ||
| value="Delete" | ||
| /> | ||
| <br/><br/> | ||
| </div> | ||
| ); | ||
|
|
||
| addOptionBtn = ( | ||
| <div style={{ padding: '5px 0px 20px 0px' }}> | ||
| <button | ||
| type="button" | ||
| onClick={this.addOption} | ||
| >Add</button> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div style={{padding: '10px 0px'}}> | ||
| <textarea | ||
| rows='5' | ||
| cols='50' | ||
| defaultValue={this.props.question.body} | ||
| ref='body' | ||
| /> | ||
| {options} | ||
| {addOptionBtn} | ||
| <div style={{padding: '20px'}}> | ||
| <ActionButtons | ||
| isEditing={true} | ||
| onCancel={this.props.toggleEditForm} | ||
| onSaveClick={this.onSaveClick} | ||
| /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default EditForm; |
| @@ -0,0 +1,169 @@ | ||
| import React, {Component} from 'react'; | ||
| import shortid from 'shortid'; | ||
|
|
||
| class QuestionForm extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| body: "", | ||
| type: "OPEN_RESPONSE", | ||
| options: [ | ||
| { | ||
| id: shortid.generate(), | ||
| answer: "", | ||
| correct: true | ||
| }, | ||
| { | ||
| id: shortid.generate(), | ||
| answer: "", | ||
| correct: false | ||
| } | ||
| ], | ||
| correctAnswerId: null | ||
| }; | ||
|
|
||
| this.handleChange = this.handleChange.bind(this); | ||
| this.handleSubmit = this.handleSubmit.bind(this); | ||
| this.removeOption = this.removeOption.bind(this); | ||
| this.addOption = this.addOption.bind(this); | ||
| } | ||
|
|
||
| handleChange(e) { | ||
| this.setState({ | ||
| [e.target.name]: e.target.value | ||
| }); | ||
| } | ||
|
|
||
| removeOption(id) { | ||
| this.setState(prevState => ({ | ||
| options: prevState.options.length > 2 ? | ||
| prevState.options.filter(x => x.id !== id) : | ||
| prevState.options | ||
| })); | ||
| } | ||
|
|
||
| addOption() { | ||
| this.setState(prevState => ({ | ||
| options: prevState.options.concat({ | ||
| id: shortid.generate(), | ||
| answer: "", | ||
| correct: false, | ||
| removable: true | ||
| }) | ||
| })); | ||
| } | ||
|
|
||
| handleSubmit(e) { | ||
| e.preventDefault(); | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Authorization": `Bearer ${this.props.token}` | ||
| }, | ||
| method: "POST" | ||
| }; | ||
|
|
||
| const body = { | ||
| questionId: shortid.generate(), | ||
| testId: this.props.testId, | ||
| body: this.state.body, | ||
| type: this.state.type | ||
| }; | ||
|
|
||
| switch (this.state.type) { | ||
| case "OPEN_RESPONSE": | ||
| options.body = JSON.stringify(body); | ||
| break; | ||
| case "MULTIPLE_CHOICE": | ||
| options.body = JSON.stringify({ | ||
| ...body, | ||
| options: this.state.options.filter(x => | ||
| this.refs[x.id].value.length > 0 | ||
| ).map(x => ({ | ||
| ...x, | ||
| answer: this.refs[x.id].value, | ||
| correct: x.id === this.state.correctAnswerId ? true : false | ||
| })) | ||
| }); | ||
| break; | ||
| default: | ||
| console.error("Submitted invalid question type"); | ||
| } | ||
|
|
||
| fetch("http://localhost:4567/api/company/create-question", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| console.log(data); | ||
| this.props.refreshTestData(); | ||
| this.props.toggleQuestionForm(); | ||
| }).catch(err => console.error(err)); | ||
| } | ||
|
|
||
| render() { | ||
| let options = ""; | ||
| let addOptionBtn = ""; | ||
| if (this.state.type === "MULTIPLE_CHOICE") { | ||
| options = this.state.options.map(x => | ||
| <div key={x.id}> | ||
| <input | ||
| type="radio" | ||
| name="correctAnswerId" | ||
| value={x.id} | ||
| onClick={this.handleChange} | ||
| /> | ||
| <input | ||
| type="text" | ||
| style={{padding: '5px 10px'}} | ||
| ref={x.id} | ||
| defaultValue={x.answer} | ||
| /> | ||
| <input | ||
| type="button" | ||
| onClick={() => this.removeOption(x.id)} | ||
| value="Delete" | ||
| /> | ||
| <br/><br/> | ||
| </div> | ||
| ); | ||
|
|
||
| addOptionBtn = ( | ||
| <button | ||
| type="button" | ||
| onClick={this.addOption} | ||
| >Add Option</button> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <form> | ||
| <h3>Question Type:</h3> | ||
| <select name="type" value={this.state.type} onChange={this.handleChange}> | ||
| <option value="OPEN_RESPONSE">Open Response</option> | ||
| <option value="MULTIPLE_CHOICE">Multiple Choice</option> | ||
| </select> | ||
| <br/> | ||
| <h3>Question Body:</h3> | ||
| <textarea | ||
| cols="100" | ||
| rows="5" | ||
| name="body" | ||
| onChange={this.handleChange} | ||
| placeholder="write question here" | ||
| /> | ||
| <br /> | ||
| {options} | ||
| {addOptionBtn} | ||
| <br/><br/> | ||
| <span> | ||
| <button type="button" onClick={this.handleSubmit}>Create</button> | ||
| <button type="button" onClick={this.props.toggleQuestionForm}>Cancel</button> | ||
| </span> | ||
| </form> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default QuestionForm; |
| @@ -0,0 +1,117 @@ | ||
| import React, {Component} from 'react'; | ||
|
|
||
| import EditForm from '../../EditForm'; | ||
| import ActionButtons from '../../../../../components/UI/Buttons/ActionButtons'; | ||
|
|
||
| class IndividualQuestion extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
|
|
||
| this.state = { | ||
| editFormMounted: false | ||
| } | ||
|
|
||
| this.toggleEditForm = this.toggleEditForm.bind(this); | ||
| this.deleteQuestion = this.deleteQuestion.bind(this); | ||
| } | ||
|
|
||
| toggleEditForm = () => { | ||
| this.setState(prevState => ({ | ||
| editFormMounted: !prevState.editFormMounted | ||
| })); | ||
| } | ||
|
|
||
| deleteQuestion = id => { | ||
| const options = { | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Authorization": `Bearer ${this.props.token}` | ||
| }, | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| questionId: this.props.question.id, | ||
| testId: this.props.testId | ||
| }) | ||
| }; | ||
|
|
||
| fetch("http://localhost:4567/api/company/delete-question", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| console.log(data); | ||
| this.props.refreshTestData(); | ||
| }).catch(err => console.error(err)); | ||
| } | ||
|
|
||
| render() { | ||
| let question = ""; | ||
| switch (this.props.question.type) { | ||
| case "OPEN_RESPONSE": | ||
| question = ( | ||
| <div> | ||
| <h3>{this.props.index + 1}. {this.props.question.body}</h3> | ||
| </div> | ||
| ); | ||
| break; | ||
| case "MULTIPLE_CHOICE": | ||
| question = ( | ||
| <div> | ||
| <h3>{this.props.index + 1}. {this.props.question.body}</h3> | ||
| { | ||
| this.props.question.options.map(x => { | ||
| let highlighter = {}; | ||
| if (x.correct) { | ||
| highlighter = { | ||
| color: 'green' | ||
| }; | ||
| } | ||
| return ( | ||
| <div key={x.id}> | ||
| <h4 style={highlighter}>{x.answer}</h4> | ||
| </div> | ||
| ) | ||
| }) | ||
| } | ||
| </div> | ||
| ); | ||
| break; | ||
| default: | ||
| console.error("Invalid question type"); | ||
| return <p>Error</p> | ||
| } | ||
|
|
||
| let editForm = ""; | ||
| let actionButtons = ""; | ||
| if (this.state.editFormMounted) { | ||
| editForm = ( | ||
| <EditForm | ||
| question={this.props.question} | ||
| toggleEditForm={this.toggleEditForm.bind(this)} | ||
| testId={this.props.testId} | ||
| refreshTestData={this.props.refreshTestData} | ||
| token={this.props.token} | ||
| delete={this.props.delete} | ||
| /> | ||
| ); | ||
| } else { | ||
| actionButtons = ( | ||
| <ActionButtons | ||
| isEditing={false} | ||
| editHandler={this.toggleEditForm} | ||
| deleteHandler={this.deleteQuestion} | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| return ( | ||
| <div style={{paddingBottom: '20px', border: 'solid #cccdce 2px', margin: "20px 300px", backgroundColor: '#cccdce', boxShadow: '1px 1px 1px 0px rgba(0,0,0,0.75)'}}> | ||
| <span> | ||
| {question} | ||
| {editForm} | ||
| {actionButtons} | ||
| </span> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default IndividualQuestion; |
| @@ -0,0 +1,238 @@ | ||
| import React, {Component} from 'react'; | ||
| import { Link } from 'react-router-dom'; | ||
|
|
||
| import TestForm from './TestForm'; | ||
| import QuestionList from './QuestionList/QuestionList'; | ||
| import QuestionForm from './QuestionForm'; | ||
| import DeleteTestForm from './DeleteTestForm'; | ||
|
|
||
| class TestEditor extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| isLoading: true, | ||
| isError: false, | ||
| tests: [], | ||
| editingTestId: null, | ||
| testFormMounted: false, | ||
| testName: "", | ||
| questions: [], | ||
| questionFormMounted: false, | ||
| deleteTestForm: false | ||
| }; | ||
|
|
||
| this.token = localStorage.getItem("token"); | ||
|
|
||
| this.toggleQuestionForm = this.toggleQuestionForm.bind(this); | ||
| this.toggleTestForm = this.toggleTestForm.bind(this); | ||
| this.toggleDeleteForm = this.toggleDeleteForm.bind(this); | ||
| this.refreshTestData = this.refreshTestData.bind(this); | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| if (this.token === null) { | ||
| return this.props.history.push("/"); | ||
| } | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Authorization": `Bearer ${this.token}` | ||
| } | ||
| }; | ||
|
|
||
| fetch("http://localhost:4567/api/company/tests/", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| if (!data.success) { | ||
| return this.setState({ | ||
| isError: true, | ||
| isLoading: false | ||
| }); | ||
| } | ||
|
|
||
| console.log("TESTS", data.tests); | ||
|
|
||
| this.setState({ | ||
| isLoading: false, | ||
| tests: data.tests, | ||
| editingTestId: data.tests[0].id | ||
| }); | ||
| }).catch(err => console.error(err)); | ||
| } | ||
|
|
||
| refreshTestData = (editingTestId = this.state.editingTestId) => { | ||
| this.setState({ | ||
| isLoading: true | ||
| }, () => { | ||
| if (this.token === null) { | ||
| return this.props.history.push("/"); | ||
| } | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Authorization": `Bearer ${this.token}` | ||
| } | ||
| }; | ||
|
|
||
| fetch("http://localhost:4567/api/company/tests/", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| if (!data.success) { | ||
| return this.setState({ | ||
| isError: true, | ||
| isLoading: false | ||
| }); | ||
| } | ||
|
|
||
| this.setState({ | ||
| editingTestId, | ||
| tests: data.tests, | ||
| isLoading: false | ||
| }); | ||
| }).catch(err => console.error(err)); | ||
| }); | ||
| } | ||
|
|
||
| toggleQuestionForm = () => { | ||
| this.setState(prevState => ({ | ||
| questionFormMounted: !prevState.questionFormMounted | ||
| })); | ||
| } | ||
|
|
||
| toggleTestForm = () => { | ||
| this.setState(prevState => ({ | ||
| testFormMounted: !prevState.testFormMounted | ||
| })); | ||
| } | ||
|
|
||
| toggleDeleteForm = () => { | ||
| this.setState(prevState => ({ | ||
| deleteFormMounted: !prevState.deleteFormMounted | ||
| })); | ||
| } | ||
|
|
||
| setEditingTestId = (id) => { | ||
| this.setState({ | ||
| editingTestId: id | ||
| }); | ||
| } | ||
|
|
||
| render() { | ||
| const style = { | ||
| header: { | ||
| padding: '50px' | ||
| }, | ||
| li: { | ||
| cursor: 'pointer', | ||
| border: 'solid', | ||
| padding: '10px' | ||
| } | ||
| }; | ||
|
|
||
| if (this.state.isLoading) { | ||
| return <p>Loading...</p> | ||
| } | ||
|
|
||
| let header = this.state.tests.map(x => | ||
| <span key={x.id} | ||
| style={style.li} | ||
| onClick={() => this.setEditingTestId(x.id)} | ||
| >{x.name}</span> | ||
| ); | ||
|
|
||
| let test = this.state.tests.find(x => | ||
| x.id === this.state.editingTestId | ||
| ); | ||
|
|
||
| let newTestBtn = ""; | ||
| let newTestForm = ""; | ||
| if (!this.state.testFormMounted) { | ||
| newTestBtn = ( | ||
| <button type="button" | ||
| onClick={this.toggleTestForm} | ||
| >New Test</button> | ||
| ); | ||
| } else { | ||
| newTestForm = ( | ||
| <div> | ||
| <TestForm | ||
| refreshTestData={this.refreshTestData} | ||
| toggleTestForm={this.toggleTestForm} | ||
| token={this.token} | ||
| setEditingTestId={this.setEditingTestId} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| let addQuestionBtn = ""; | ||
| let questionForm = ""; | ||
| if (!this.state.questionFormMounted) { | ||
| addQuestionBtn = ( | ||
| <button | ||
| type="button" | ||
| onClick={this.toggleQuestionForm} | ||
| >Add New Question</button> | ||
| ); | ||
| } else { | ||
| questionForm = ( | ||
| <QuestionForm | ||
| testId={test.id} | ||
| toggleQuestionForm={this.toggleQuestionForm} | ||
| refreshTestData={this.refreshTestData} | ||
| token={this.token} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| let deleteBtn = ""; | ||
| let deleteForm = ""; | ||
| if (this.state.deleteFormMounted) { | ||
| deleteForm = ( | ||
| <DeleteTestForm | ||
| toggleDeleteForm={this.toggleDeleteForm} | ||
| id={this.state.editingTestId} | ||
| refreshTestData={this.refreshTestData} | ||
| name={test.name} | ||
| token={this.token} | ||
| firstTestId={this.state.tests[0].id} | ||
| /> | ||
| ); | ||
| } else { | ||
| deleteBtn = ( | ||
| <button type="button" | ||
| onClick={this.toggleDeleteForm} | ||
| >Delete</button> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <div style={{padding: '20px', textAlign: 'left'}}> | ||
| <Link to='/company' style={{textDecoration: 'none', color: 'white', padding: '10px', cursor: 'pointer', boxShadow: '2px 2px 1px 0px rgba(0,0,0,0.75)', backgroundColor: 'purple'}}>BACK</Link> | ||
| </div> | ||
| <div> | ||
| {newTestBtn} | ||
| {newTestForm} | ||
| </div> | ||
| <div style={style.header}> | ||
| {header} | ||
| </div> | ||
| <h1>{test.name}</h1> | ||
| {addQuestionBtn} | ||
| {questionForm} | ||
| <QuestionList | ||
| questions={test.questions} | ||
| testId={test.id} | ||
| inTestForm={false} | ||
| refreshTestData={this.refreshTestData} | ||
| token={this.token} | ||
| /> | ||
| {deleteBtn} | ||
| {deleteForm} | ||
| </div> | ||
| ); | ||
| }; | ||
| }; | ||
|
|
||
| export default TestEditor; |
| @@ -0,0 +1,66 @@ | ||
| import React, { Component } from 'react'; | ||
|
|
||
| class TestForm extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| name: "" | ||
| }; | ||
|
|
||
| this.handleChange = this.handleChange.bind(this); | ||
| this.handleSubmit = this.handleSubmit.bind(this); | ||
| } | ||
|
|
||
| handleSubmit(e) { | ||
| e.preventDefault(); | ||
|
|
||
| const options = { | ||
| headers: { | ||
| "Authorization": `Bearer ${this.props.token}`, | ||
| "Content-Type": "application/json" | ||
| }, | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| name: this.state.name | ||
| }) | ||
| }; | ||
|
|
||
| fetch("http://localhost:4567/api/company/create-test", options) | ||
| .then(res => res.json()) | ||
| .then(data => { | ||
| console.log(data); | ||
| this.props.refreshTestData(data.createdTestId); | ||
| this.props.toggleTestForm(); | ||
| }).catch(err => console.error(err)); | ||
| } | ||
|
|
||
| handleChange(e) { | ||
| this.setState({ | ||
| [e.target.name]: e.target.value | ||
| }); | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <div> | ||
| <div> | ||
| <label htmlFor="name">Name:</label> | ||
| <input | ||
| type="text" | ||
| name="name" | ||
| onChange={this.handleChange} | ||
| placeholder="name" | ||
| /> | ||
| <button type="button" | ||
| onClick={this.handleSubmit} | ||
| >Submit</button> | ||
| <button type="button" | ||
| onClick={this.props.toggleTestForm} | ||
| >Cancel</button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default TestForm; |
| @@ -0,0 +1,14 @@ | ||
| - Pull state up from Questions to Test so that we can send all answers on submit. | ||
| - Pass handleChange as prop | ||
|
|
||
| <QuestionList | ||
| questions={this.state.questions} | ||
| inTestForm={true} | ||
| token={this.token} | ||
| /> | ||
| {addQuestionBtn} | ||
| {questionForm} | ||
| <button | ||
| type="button" | ||
| onClick={this.handleSubmit} | ||
| >Submit</button> |