diff --git a/frontend/components/App.js b/frontend/components/App.js index c1695091..e741777f 100644 --- a/frontend/components/App.js +++ b/frontend/components/App.js @@ -21,7 +21,7 @@ export const resetStore = () => { store = createStore(reducer, composeEnhancers(applyMiddleware(thunk))) } resetStore() - +// console.log(store.getState()) export default function App() { return ( diff --git a/frontend/components/Form.js b/frontend/components/Form.js index 1d6f2bf6..7855c70c 100644 --- a/frontend/components/Form.js +++ b/frontend/components/Form.js @@ -1,24 +1,52 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { connect } from 'react-redux' import * as actionCreators from '../state/action-creators' +import { object, string, InferType } from "yup"; + +const formSchema = object().shape({ + newQuestion: + string().min(2).trim().required("Must include question!"), + newTrueAnswer: + string().min(2).trim().required("Must include True answer!"), + newFalseAnswer: + string().min(2).trim().required("Must include False answer!") +}) + export function Form(props) { - const onChange = evt => { + const checkValues = () => { + if(props.form.newQuestion.trim() && props.form.newTrueAnswer.trim() && props.form.newFalseAnswer.trim()){ + return false + }else{ + return true + } + } + + const onChange = evt => { + props.inputChange(evt) } const onSubmit = evt => { + evt.preventDefault(); + props.resetForm() + const formData = { + question_text: props.form.newQuestion, + true_answer_text: props.form.newTrueAnswer, + false_answer_text: props.form.newFalseAnswer + } + props.postQuiz(formData) } return (
-

Create New Quiz

- - - - +

console.log(props.form)}>Create New Quiz

+ + + +
) } diff --git a/frontend/components/Message.js b/frontend/components/Message.js index ef1b6145..950587d7 100644 --- a/frontend/components/Message.js +++ b/frontend/components/Message.js @@ -1,5 +1,9 @@ import React from 'react' +import { useSelector } from 'react-redux' -export default function Message(props) { - return
Nice job!
+export default function Message() { + const messages = useSelector((state) => state.infoMessage) + + return
{messages}
} + diff --git a/frontend/components/Quiz.js b/frontend/components/Quiz.js index ded2cd57..c946b7ef 100644 --- a/frontend/components/Quiz.js +++ b/frontend/components/Quiz.js @@ -1,34 +1,60 @@ -import React from 'react' +import React, {useEffect, useState} from 'react' +import { connect } from 'react-redux' +import { fetchQuiz, selectAnswer, postAnswer } from '../state/action-creators' -export default function Quiz(props) { +export function Quiz(props) { + + if(!props.quiz){ + useEffect(() => { + props.fetchQuiz() + }, []) + + } + + const submit = () => { + if(props.selected){ + const answer = { + quiz_id: props.quiz.quiz_id, + answer_id: props.selected.answer_id + } + props.postAnswer(answer) + } + } + return (
{ // quiz already in state? Let's use that, otherwise render "Loading next quiz..." - true ? ( + props.quiz ? ( <> -

What is a closure?

+

{props.quiz.question}

-
- A function - -
- -
- An elephant - -
+ {props.quiz.answers.map((answer,idx) => { + return( +
+ {answer.text} + +
+ ) + })}
- + - ) : 'Loading next quiz...' + ) :'Loading next quiz...' }
) } +const mapStateToProps = state => { + return { + quiz: state.quiz, + selected: state.selectedAnswer + } + +} + +export default connect(mapStateToProps,{fetchQuiz, selectAnswer, postAnswer})(Quiz) diff --git a/frontend/components/Wheel.js b/frontend/components/Wheel.js index 895dc9df..26c1a09f 100644 --- a/frontend/components/Wheel.js +++ b/frontend/components/Wheel.js @@ -1,20 +1,30 @@ import React from 'react' +import { connect } from 'react-redux' +import { moveClockwise, moveCounterClockwise } from '../state/action-creators' -export default function Wheel(props) { +export function Wheel(props) { + const style = [1,2,3,4,5,6] + return (
-
B
-
-
-
-
-
{/* --i is a custom CSS property, no need to touch that nor the style object */} + {style.map((num,idx) => { + return ( +
{idx === props.wheel ? 'B' : ''}
+ ) + })}
- - + +
) } +const mapStateToProps = (state) => { + return { + wheel: state.wheel + } +} + +export default connect(mapStateToProps,{moveClockwise,moveCounterClockwise})(Wheel) \ No newline at end of file diff --git a/frontend/state/action-creators.js b/frontend/state/action-creators.js index 0909e825..7df1df4d 100644 --- a/frontend/state/action-creators.js +++ b/frontend/state/action-creators.js @@ -1,39 +1,79 @@ +import reducer from "./reducer" +import axios from "axios" + +import { MOVE_CLOCKWISE, + MOVE_COUNTERCLOCKWISE, + SET_QUIZ_INTO_STATE, + SET_SELECTED_ANSWER, + SET_INFO_MESSAGE, + INPUT_CHANGE, + RESET_FORM,} from "./action-types" + + // ❗ You don't need to add extra action creators to achieve MVP -export function moveClockwise() { } +export const moveClockwise = () => { + return ({type: MOVE_CLOCKWISE}) +} -export function moveCounterClockwise() { } +export const moveCounterClockwise = () => { + return ({type: MOVE_COUNTERCLOCKWISE}) + } -export function selectAnswer() { } +export const selectAnswer = (answer) => { + return ({type: SET_SELECTED_ANSWER, payload: answer}) + } -export function setMessage() { } +export function setMessage(message) { + return ({type: SET_INFO_MESSAGE, payload: message}) + } -export function setQuiz() { } +export function setQuiz() { + } -export function inputChange() { } +export function inputChange(evt) { + return ({type:INPUT_CHANGE, payload: evt.target}) +} -export function resetForm() { } +export function resetForm() { + return ({type: RESET_FORM}) + } // ❗ Async action creators export function fetchQuiz() { return function (dispatch) { + dispatch({type: SET_QUIZ_INTO_STATE}) + axios.get('http://localhost:9000/api/quiz/next') + .then(({data}) => { + dispatch ({type: SET_QUIZ_INTO_STATE, payload: data}) + }) + + // First, dispatch an action to reset the quiz state (so the "Loading next quiz..." message can display) // On successful GET: // - Dispatch an action to send the obtained quiz to its state } } -export function postAnswer() { +export function postAnswer(answer) { return function (dispatch) { + axios.post('http://localhost:9000/api/quiz/answer', answer) + .then(({data}) => dispatch({type: SET_INFO_MESSAGE, payload: data.message})) + .then(res => dispatch({type: SET_QUIZ_INTO_STATE})) + .catch(err => dispatch({SET_INFO_MESSAGE, payload: err})) // On successful POST: // - Dispatch an action to reset the selected answer state // - Dispatch an action to set the server message to state // - Dispatch the fetching of the next quiz } } -export function postQuiz() { +export function postQuiz(quiz) { return function (dispatch) { + axios.post('http://localhost:9000/api/quiz/new', quiz) + .then(({data}) => dispatch({type: SET_INFO_MESSAGE, payload: `Congrats: "${quiz.question_text}" is a great question!`})) + .catch(err => dispatch({type: SET_INFO_MESSAGE, payload: 'ERROR POSTING NEW QUIZ'})) // On successful POST: // - Dispatch the correct message to the the appropriate state // - Dispatch the resetting of the form } + } // ❗ On promise rejections, use log statements or breakpoints, and put an appropriate error message in state diff --git a/frontend/state/reducer.js b/frontend/state/reducer.js index f1b0305d..77ff6b4b 100644 --- a/frontend/state/reducer.js +++ b/frontend/state/reducer.js @@ -1,24 +1,67 @@ // ❗ You don't need to add extra reducers to achieve MVP +import { INPUT_CHANGE, MOVE_CLOCKWISE,MOVE_COUNTERCLOCKWISE, RESET_FORM, SET_INFO_MESSAGE, SET_QUIZ_INTO_STATE, SET_SELECTED_ANSWER } from './action-types' import { combineReducers } from 'redux' +import axios from 'axios' + const initialWheelState = 0 -function wheel(state = initialWheelState, action) { - return state +const wheel = (state = initialWheelState, action) => { + switch(action.type){ + case MOVE_CLOCKWISE: + if(state === 5){ + return state = initialWheelState + }else{ + return state + 1 + } + case MOVE_COUNTERCLOCKWISE: + if(state === 0){ + return state = 5 + }else{ + return state - 1 + } + default: + return state + } } const initialQuizState = null function quiz(state = initialQuizState, action) { - return state + switch(action.type){ + case SET_QUIZ_INTO_STATE: + if(action.payload === undefined){ + return( state = initialQuizState)} + case SET_QUIZ_INTO_STATE: + if(action.payload){ + return ( state = action.payload) + } + default: + return state + } + } const initialSelectedAnswerState = null function selectedAnswer(state = initialSelectedAnswerState, action) { - return state + switch(action.type){ + case SET_SELECTED_ANSWER: + return state = action.payload + default: + return state + } } const initialMessageState = '' function infoMessage(state = initialMessageState, action) { - return state + switch(action.type){ + case SET_INFO_MESSAGE: + if(action.payload === undefined){ + return state = initialMessageState + } + case SET_INFO_MESSAGE: + return state = action.payload + default: + return state + } } const initialFormState = { @@ -27,7 +70,17 @@ const initialFormState = { newFalseAnswer: '', } function form(state = initialFormState, action) { - return state + switch(action.type){ + case INPUT_CHANGE: + return { + ...state, + [action.payload.id] : action.payload.value + } + case RESET_FORM: + return state = initialFormState + default: + return state + } } export default combineReducers({ wheel, quiz, selectedAnswer, infoMessage, form })