diff --git a/src/api/authorApi.js b/src/api/authorApi.js deleted file mode 100644 index 2689db1..0000000 --- a/src/api/authorApi.js +++ /dev/null @@ -1,6 +0,0 @@ -import { get } from "./api"; -const baseUrl = process.env.API_URL + "/authors/"; - -export function getAuthors() { - return get(baseUrl); -} diff --git a/src/api/courseApi.js b/src/api/courseApi.js deleted file mode 100644 index 366accb..0000000 --- a/src/api/courseApi.js +++ /dev/null @@ -1,15 +0,0 @@ -import { get, del, post, put } from "./api"; -const baseUrl = process.env.API_URL + "/courses/"; - -export function getCourses() { - return get(baseUrl); -} - -export function saveCourse(course) { - const url = baseUrl + (course.id || ""); - return course.id ? put(url, course) : post(url, course); // POST for create, PUT to update when id already exists. -} - -export function deleteCourse(courseId) { - return del(baseUrl + courseId); -} diff --git a/src/components/App.js b/src/components/App.js index 2c9b68c..5cf5ab7 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -4,8 +4,6 @@ import HomePage from "./home/HomePage"; import AboutPage from "./about/AboutPage"; import Header from "./common/Header"; import PageNotFound from "./PageNotFound"; -import CoursesPage from "./courses/CoursesPage"; -import ManageCoursePage from "./courses/ManageCoursePage"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import SessionContainer from "../features/session/components/SessionContainer"; @@ -17,9 +15,6 @@ function App() { - - - diff --git a/src/components/courses/CourseForm.js b/src/components/courses/CourseForm.js deleted file mode 100644 index f0ab750..0000000 --- a/src/components/courses/CourseForm.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import TextInput from "../common/TextInput"; -import SelectInput from "../common/SelectInput"; - -const CourseForm = ({ - course, - authors, - onSave, - onChange, - saving = false, - errors = {} -}) => { - return ( -
-

{course.id ? "Edit" : "Add"} Course

- {errors.onSave && ( -
- {errors.onSave} -
- )} - - - ({ - value: author.id, - text: author.name - }))} - onChange={onChange} - error={errors.author} - /> - - - - - - ); -}; - -CourseForm.propTypes = { - authors: PropTypes.array.isRequired, - course: PropTypes.object.isRequired, - errors: PropTypes.object, - onSave: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - saving: PropTypes.bool -}; - -export default CourseForm; diff --git a/src/components/courses/CourseList.js b/src/components/courses/CourseList.js deleted file mode 100644 index 7ae652b..0000000 --- a/src/components/courses/CourseList.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { Link } from "react-router-dom"; - -const CourseList = ({ courses, onDeleteClick }) => ( - - - - - - - - - - {courses.map((course) => { - return ( - - - - - - - - ); - })} - -
- TitleAuthorCategory -
- - Watch - - - {course.title} - {course.authorName}{course.category} - -
-); - -CourseList.propTypes = { - courses: PropTypes.array.isRequired, - onDeleteClick: PropTypes.func.isRequired -}; - -export default CourseList; diff --git a/src/components/courses/CoursesPage.js b/src/components/courses/CoursesPage.js deleted file mode 100644 index 973cfd2..0000000 --- a/src/components/courses/CoursesPage.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import * as courseActions from "../../redux/actions/courseActions"; -import * as authorActions from "../../redux/actions/authorActions"; -import PropTypes from "prop-types"; -import { bindActionCreators } from "redux"; -import CourseList from "./CourseList"; -import { Redirect } from "react-router-dom"; -import Spinner from "../common/Spinner"; -import { toast } from "react-toastify"; - -class CoursesPage extends React.Component { - state = { - redirectToAddCoursePage: false - }; - - componentDidMount() { - const { courses, authors, actions } = this.props; - - if (courses.length === 0) { - actions.loadCourses().catch((error) => { - alert("Loading courses failed. " + error); - }); - } - - if (authors.length === 0) { - actions.loadAuthors().catch((error) => { - alert("Loading authors failed. " + error); - }); - } - } - - handleDeleteCourse = async (course) => { - toast.success("Course deleted"); - try { - await this.props.actions.deleteCourse(course); - } catch (error) { - toast.error("Delete failed. " + error.message, { autoClose: false }); - } - }; - - render() { - return ( - <> - {this.state.redirectToAddCoursePage && } -

Courses

- {this.props.loading ? ( - - ) : ( - <> - - - - - )} - - ); - } -} - -CoursesPage.propTypes = { - courses: PropTypes.array.isRequired, - authors: PropTypes.array.isRequired, - actions: PropTypes.object.isRequired, - loading: PropTypes.bool.isRequired -}; - -function mapStateToProps(state) { - return { - courses: - state.authors.length === 0 - ? [] - : state.courses.map((course) => { - const author = state.authors.find((a) => a.id === course.authorId); - return { - ...course, - authorName: author.name - }; - }), - authors: state.authors, - loading: state.ajaxCallsInProgress > 0 - }; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators( - { - loadCourses: courseActions.loadCourses, - loadAuthors: authorActions.loadAuthors, - deleteCourse: courseActions.deleteCourse - }, - dispatch - ) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage); diff --git a/src/components/courses/ManageCoursePage.js b/src/components/courses/ManageCoursePage.js deleted file mode 100644 index 3141097..0000000 --- a/src/components/courses/ManageCoursePage.js +++ /dev/null @@ -1,122 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { loadCourses, saveCourse } from "../../redux/actions/courseActions"; -import { loadAuthors } from "../../redux/actions/authorActions"; -import PropTypes from "prop-types"; -import { bindActionCreators } from "redux"; -import CourseForm from "./CourseForm"; -import { newCourse } from "../../../tools/mockData"; -import Spinner from "../common/Spinner"; -import { toast } from "react-toastify"; - -function ManageCoursePage({ courses, authors, actions, history, ...props }) { - const [course, setCourse] = useState({ ...props.course }); - const [errors, setErrors] = useState({}); - const [saving, setSaving] = useState(false); - - useEffect(() => { - if (courses.length === 0) { - actions.loadCourses().catch((error) => { - alert("Loading courses failed. " + error); - }); - } else { - setCourse({ ...props.course }); - } - - if (authors.length === 0) { - actions.loadAuthors().catch((error) => { - alert("Loading authors failed. " + error); - }); - } - }, [props.course]); - - function handleChange(event) { - const { name, value } = event.target; - setCourse((prevCourse) => ({ - ...prevCourse, - [name]: name === "authorId" ? parseInt(value, 10) : value - })); - } - - function formIsValid() { - const { title, authorId, category } = course; - const errors = {}; - - if (!title) errors.title = "Title is required."; - if (!authorId) errors.author = "Author is required"; - if (!category) errors.category = "Category is required"; - - setErrors(errors); - // Form is valid if the errors object still has no properties - return Object.keys(errors).length === 0; - } - - function handleSave(event) { - event.preventDefault(); - if (!formIsValid()) return; - setSaving(true); - actions - .saveCourse(course) - .then(() => { - toast.success("Course saved."); - history.push("/courses"); - }) - .catch((error) => { - setSaving(false); - setErrors({ onSave: error.message }); - }); - } - - return authors.length === 0 || course.length === 0 ? ( - - ) : ( - - ); -} - -ManageCoursePage.propTypes = { - course: PropTypes.object.isRequired, - courses: PropTypes.array.isRequired, - authors: PropTypes.array.isRequired, - actions: PropTypes.object.isRequired, - history: PropTypes.object.isRequired -}; - -function mapStateToProps(state, ownProps) { - const slug = ownProps.match.params.slug; - const course = - slug && state.courses.length > 0 - ? getCourseBySlug(state.courses, slug) - : newCourse; - return { - course, - courses: state.courses, - authors: state.authors - }; -} - -function getCourseBySlug(courses, slug) { - return courses.find((course) => course.slug === slug) || null; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators( - { - loadCourses, - loadAuthors, - saveCourse - }, - dispatch - ) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(ManageCoursePage); diff --git a/src/features/session/components/SessionListComponent.js b/src/features/session/components/SessionListComponent.js index da9857c..e751a62 100644 --- a/src/features/session/components/SessionListComponent.js +++ b/src/features/session/components/SessionListComponent.js @@ -7,6 +7,7 @@ import Typography from "@material-ui/core/Typography"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import PropTypes from "prop-types"; import SessionSummary from "./SessionSummary"; +import Spinner from "../../../components/common/Spinner"; const useStyles = makeStyles((theme) => ({ root: { @@ -24,7 +25,10 @@ const SessionListComponent = ({ sessions }) => { return (

Sessions

- {sessions.loaded && + {sessions.loading ? ( + + ) : ( + sessions.loaded && sessions.map((session) => { return ( @@ -44,7 +48,8 @@ const SessionListComponent = ({ sessions }) => { ); - })} + }) + )}
); }; diff --git a/src/redux/actions/actionTypes.js b/src/redux/actions/actionTypes.js index dde65a7..09963a6 100644 --- a/src/redux/actions/actionTypes.js +++ b/src/redux/actions/actionTypes.js @@ -1,16 +1,3 @@ -export const CREATE_COURSE = "CREATE_COURSE"; -export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS"; -export const LOAD_AUTHORS_SUCCESS = "LOAD_AUTHORS_SUCCESS"; -export const CREATE_COURSE_SUCCESS = "CREATE_COURSE_SUCCESS"; -export const UPDATE_COURSE_SUCCESS = "UPDATE_COURSE_SUCCESS"; - -// By convention, actions that end in "_SUCCESS" are assumed to have been the result of a completed -// API call. But since we're doing an optimistic delete, we're hiding loading state. -// So this action name deliberately omits the "_SUCCESS" suffix. -// If it had one, our apiCallsInProgress counter would be decremented below zero -// because we're not incrementing the number of apiCallInProgress when the delete request begins. -export const DELETE_COURSE_OPTIMISTIC = "DELETE_COURSE_OPTIMISTIC"; - export const BEGIN_AJAX_CALL = "BEGIN_AJAX_CALL"; export const AJAX_CALL_ERROR = "AJAX_CALL_ERROR"; export const END_AJAX_CALL = "END_AJAX_CALL"; diff --git a/src/redux/actions/authorActions.js b/src/redux/actions/authorActions.js deleted file mode 100644 index 0e49bfc..0000000 --- a/src/redux/actions/authorActions.js +++ /dev/null @@ -1,18 +0,0 @@ -import * as types from "./actionTypes"; -import * as authorApi from "../../api/authorApi"; -import { sendHttpRequest } from "../actions/httpActions"; - -function loadAuthorsSuccess(authors) { - return { type: types.LOAD_AUTHORS_SUCCESS, authors }; -} - -export function loadAuthors() { - return async function (dispatch) { - try { - const authors = await dispatch(sendHttpRequest(authorApi.getAuthors())); - dispatch(loadAuthorsSuccess(authors)); - } catch (error) { - throw error; - } - }; -} diff --git a/src/redux/actions/courseActions.js b/src/redux/actions/courseActions.js deleted file mode 100644 index 59278c9..0000000 --- a/src/redux/actions/courseActions.js +++ /dev/null @@ -1,55 +0,0 @@ -import * as types from "./actionTypes"; -import * as courseApi from "../../api/courseApi"; -import { sendHttpRequest } from "../actions/httpActions"; - -function loadCoursesSuccess(courses) { - return { type: types.LOAD_COURSES_SUCCESS, courses }; -} - -export function createCourseSuccess(course) { - return { type: types.CREATE_COURSE_SUCCESS, course }; -} - -export function updateCourseSuccess(course) { - return { type: types.UPDATE_COURSE_SUCCESS, course }; -} - -export function deleteCourseOptimistic(course) { - return { type: types.DELETE_COURSE_OPTIMISTIC, course }; -} - -export function loadCourses() { - return function (dispatch) { - return dispatch(sendHttpRequest(courseApi.getCourses())) - .then((courses) => { - dispatch(loadCoursesSuccess(courses)); - }) - .catch((error) => { - throw error; - }); - }; -} - -export function saveCourse(course) { - //eslint-disable-next-line no-unused-vars - return function (dispatch, getState) { - return dispatch(sendHttpRequest(courseApi.saveCourse(course))) - .then((savedCourse) => { - course.id - ? dispatch(updateCourseSuccess(savedCourse)) - : dispatch(createCourseSuccess(savedCourse)); - }) - .catch((error) => { - throw error; - }); - }; -} - -export function deleteCourse(course) { - return function (dispatch) { - // Doing optimistic delete, so not dispatching begin/end api call - // actions, or apiCallError action since we're not showing the loading status for this. - dispatch(deleteCourseOptimistic(course)); - return courseApi.deleteCourse(course.id); - }; -} diff --git a/src/redux/reducers/authorReducer.js b/src/redux/reducers/authorReducer.js deleted file mode 100644 index 6fc82c7..0000000 --- a/src/redux/reducers/authorReducer.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as types from "../actions/actionTypes"; -import initialState from "./initialState"; - -export default function authorReducer(state = initialState.authors, action) { - switch (action.type) { - case types.LOAD_AUTHORS_SUCCESS: - return action.authors; - - default: - return state; - } -} diff --git a/src/redux/reducers/courseReducer.js b/src/redux/reducers/courseReducer.js deleted file mode 100644 index dcdbae5..0000000 --- a/src/redux/reducers/courseReducer.js +++ /dev/null @@ -1,23 +0,0 @@ -import * as types from "../actions/actionTypes"; -import initialState from "./initialState"; - -export default function courseReducer(state = initialState.courses, action) { - switch (action.type) { - case types.LOAD_COURSES_SUCCESS: - return action.courses; - - case types.CREATE_COURSE_SUCCESS: - return [...state, { ...action.course }]; - - case types.UPDATE_COURSE_SUCCESS: - return state.map((course) => - course.id === action.course.id ? action.course : course - ); - - case types.DELETE_COURSE_OPTIMISTIC: - return state.filter((course) => course.id !== action.course.id); - - default: - return state; - } -} diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index f2ae5ce..b3d37d0 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -1,6 +1,4 @@ import { combineReducers } from "redux"; -import courseReducer from "./courseReducer"; -import authorReducer from "./authorReducer"; import ajaxStatusReducer from "./ajaxStatusReducer"; import systemReducer from "../../features/system/reducer"; import sessionsReducer from "../../features/session/reducer"; @@ -8,8 +6,6 @@ import sessionsReducer from "../../features/session/reducer"; const rootReducer = combineReducers({ system: systemReducer, sessions: sessionsReducer, - courses: courseReducer, - authors: authorReducer, ajaxCallsInProgress: ajaxStatusReducer }); diff --git a/src/redux/reducers/initialState.js b/src/redux/reducers/initialState.js index 0bf616a..218f346 100644 --- a/src/redux/reducers/initialState.js +++ b/src/redux/reducers/initialState.js @@ -1,7 +1,5 @@ export default { system: {}, sessions: Object.assign([], { loading: false, loaded: false }), - courses: [], - authors: [], ajaxCallsInProgress: 0 };