diff --git a/src/api/apiUtils.js b/src/api/apiUtils.js new file mode 100644 index 0000000..a97e14f --- /dev/null +++ b/src/api/apiUtils.js @@ -0,0 +1,17 @@ +export async function handleResponse(response) { + if (response.ok) return response.json(); + if (response.status === 400) { + // So, a server-side validation error occurred. + // Server side validation returns a string error message, so parse as text instead of json. + const error = await response.text(); + throw new Error(error); + } + throw new Error("Network response was not ok."); +} + +// In a real app, would likely call an error logging service. +export function handleError(error) { + // eslint-disable-next-line no-console + console.error("API call failed. " + error); + throw error; +} diff --git a/src/api/authorApi.js b/src/api/authorApi.js new file mode 100644 index 0000000..8439475 --- /dev/null +++ b/src/api/authorApi.js @@ -0,0 +1,6 @@ +import { handleResponse, handleError } from "./apiUtils"; +const baseUrl = process.env.API_URL + "/authors/"; + +export function getAuthors() { + return fetch(baseUrl).then(handleResponse).catch(handleError); +} diff --git a/src/api/courseApi.js b/src/api/courseApi.js new file mode 100644 index 0000000..134d04c --- /dev/null +++ b/src/api/courseApi.js @@ -0,0 +1,22 @@ +import { handleResponse, handleError } from "./apiUtils"; +const baseUrl = process.env.API_URL + "/courses/"; + +export function getCourses() { + return fetch(baseUrl).then(handleResponse).catch(handleError); +} + +export function saveCourse(course) { + return fetch(baseUrl + (course.id || ""), { + method: course.id ? "PUT" : "POST", // POST for create, PUT to update when id already exists. + headers: { "content-type": "application/json" }, + body: JSON.stringify(course) + }) + .then(handleResponse) + .catch(handleError); +} + +export function deleteCourse(courseId) { + return fetch(baseUrl + courseId, { method: "DELETE" }) + .then(handleResponse) + .catch(handleError); +} diff --git a/src/components/courses/CoursesPage.js b/src/components/courses/CoursesPage.js index 6a1a8df..67187f7 100644 --- a/src/components/courses/CoursesPage.js +++ b/src/components/courses/CoursesPage.js @@ -5,37 +5,14 @@ import PropTypes from "prop-types"; import { bindActionCreators } from "redux"; class CoursesPage extends React.Component { - state = { - course: { - title: "" - } - }; - - handleChange = (event) => { - const course = { ...this.state.course, title: event.target.value }; - this.setState({ course }); - }; - - handleSubmit = (event) => { - event.preventDefault(); - this.props.actions.createCourse(this.state.course); - }; - render() { return ( -
+ <>

Courses

-

Add Course

- - {this.props.courses.map((course) => (
{course.title}
))} -
+ ); } } diff --git a/src/redux/actions/actionTypes.js b/src/redux/actions/actionTypes.js index 18939e7..f78d80f 100644 --- a/src/redux/actions/actionTypes.js +++ b/src/redux/actions/actionTypes.js @@ -1 +1,2 @@ export const CREATE_COURSE = "CREATE_COURSE"; +export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS"; diff --git a/src/redux/actions/courseActions.js b/src/redux/actions/courseActions.js index 5c5e207..d891c33 100644 --- a/src/redux/actions/courseActions.js +++ b/src/redux/actions/courseActions.js @@ -1,5 +1,23 @@ import * as types from "./actionTypes"; +import * as courseApi from "../../api/courseApi"; export function createCourse(course) { return { type: types.CREATE_COURSE, course }; } + +function loadCoursesSuccess(courses) { + return { type: types.LOAD_COURSES_SUCCESS, courses }; +} + +export function loadCourses() { + return function (dispatch) { + return courseApi + .getCourses() + .then((courses) => { + dispatch(loadCoursesSuccess(courses)); + }) + .catch((error) => { + throw error; + }); + }; +} diff --git a/src/redux/configureStore.js b/src/redux/configureStore.js index c1130b8..59f0345 100644 --- a/src/redux/configureStore.js +++ b/src/redux/configureStore.js @@ -1,6 +1,7 @@ import { createStore, applyMiddleware, compose } from "redux"; import rootReducer from "./reducers"; import reduxImmutableStateInvariant from "redux-immutable-state-invariant"; +import thunk from "redux-thunk"; export default function configureStore(initialState) { const composeEnhancers = @@ -9,6 +10,6 @@ export default function configureStore(initialState) { return createStore( rootReducer, initialState, - composeEnhancers(applyMiddleware(reduxImmutableStateInvariant())) + composeEnhancers(applyMiddleware(thunk, reduxImmutableStateInvariant())) ); } diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 2673392..092e8df 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -23,6 +23,9 @@ module.exports = { https: false }, plugins: [ + new webpack.DefinePlugin({ + "process.env.API_URL": JSON.stringify("http://localhost:3001") + }), new HtmlWebpackPlugin({ template: "src/index.html", favicon: "src/favicon.ico"