diff --git a/src/components/common/Spinner.css b/src/components/common/Spinner.css new file mode 100644 index 0000000..07c1c0b --- /dev/null +++ b/src/components/common/Spinner.css @@ -0,0 +1,42 @@ +/* via https://projects.lukehaas.me/css-loaders/ */ +.loader, +.loader:after { + border-radius: 50%; + width: 10em; + height: 10em; +} +.loader { + margin: 60px auto; + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 1.1em solid rgba(255, 151, 0, 0.2); + border-right: 1.1em solid rgba(255, 151, 0, 0.2); + border-bottom: 1.1em solid rgba(255, 151, 0, 0.2); + border-left: 1.1em solid #ff9700; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load8 1.1s infinite linear; + animation: load8 1.1s infinite linear; +} +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/src/components/common/Spinner.js b/src/components/common/Spinner.js new file mode 100644 index 0000000..a8989d2 --- /dev/null +++ b/src/components/common/Spinner.js @@ -0,0 +1,8 @@ +import React from "react"; +import "./Spinner.css"; + +const Spinner = () => { + return
Loading...
; +}; + +export default Spinner; diff --git a/src/components/courses/CoursesPage.js b/src/components/courses/CoursesPage.js index beea601..0b7cf5c 100644 --- a/src/components/courses/CoursesPage.js +++ b/src/components/courses/CoursesPage.js @@ -6,6 +6,7 @@ import PropTypes from "prop-types"; import { bindActionCreators } from "redux"; import CourseList from "./CourseList"; import { Redirect } from "react-router-dom"; +import Spinner from "../common/Spinner"; class CoursesPage extends React.Component { state = { @@ -33,16 +34,21 @@ class CoursesPage extends React.Component { <> {this.state.redirectToAddCoursePage && }

Courses

+ {this.props.loading ? ( + + ) : ( + <> + - - - + + + )} ); } @@ -51,7 +57,8 @@ class CoursesPage extends React.Component { CoursesPage.propTypes = { courses: PropTypes.array.isRequired, authors: PropTypes.array.isRequired, - actions: PropTypes.object.isRequired + actions: PropTypes.object.isRequired, + loading: PropTypes.bool.isRequired }; function mapStateToProps(state) { @@ -66,7 +73,8 @@ function mapStateToProps(state) { authorName: author.name }; }), - authors: state.authors + authors: state.authors, + loading: state.apiCallsInProgress > 0 }; } diff --git a/src/components/courses/ManageCoursePage.js b/src/components/courses/ManageCoursePage.js index 0dd7235..e1b163a 100644 --- a/src/components/courses/ManageCoursePage.js +++ b/src/components/courses/ManageCoursePage.js @@ -6,6 +6,7 @@ import PropTypes from "prop-types"; import { bindActionCreators } from "redux"; import CourseForm from "./CourseForm"; import { newCourse } from "../../../tools/mockData"; +import Spinner from "../common/Spinner"; function ManageCoursePage({ courses, authors, actions, history, ...props }) { const [course, setCourse] = useState({ ...props.course }); @@ -42,7 +43,9 @@ function ManageCoursePage({ courses, authors, actions, history, ...props }) { }); } - return ( + return authors.length === 0 || course.length === 0 ? ( + + ) : ( { diff --git a/src/redux/actions/courseActions.js b/src/redux/actions/courseActions.js index 261bac1..d3f6731 100644 --- a/src/redux/actions/courseActions.js +++ b/src/redux/actions/courseActions.js @@ -1,5 +1,6 @@ import * as types from "./actionTypes"; import * as courseApi from "../../api/courseApi"; +import { beginApiCall } from "./apiStatusActions"; function loadCoursesSuccess(courses) { return { type: types.LOAD_COURSES_SUCCESS, courses }; @@ -15,6 +16,7 @@ export function updateCourseSuccess(course) { export function loadCourses() { return function (dispatch) { + dispatch(beginApiCall()); return courseApi .getCourses() .then((courses) => { @@ -29,6 +31,7 @@ export function loadCourses() { export function saveCourse(course) { //eslint-disable-next-line no-unused-vars return function (dispatch, getState) { + dispatch(beginApiCall()); return courseApi .saveCourse(course) .then((savedCourse) => { diff --git a/src/redux/reducers/apiStatusReducer.js b/src/redux/reducers/apiStatusReducer.js new file mode 100644 index 0000000..b56e888 --- /dev/null +++ b/src/redux/reducers/apiStatusReducer.js @@ -0,0 +1,19 @@ +import * as types from "../actions/actionTypes"; +import initialState from "./initialState"; + +function actionTypeEndsInSuccess(type) { + return type.substring(type.length - 8) === "_SUCCESS"; +} + +export default function apiCallStatusReducer( + state = initialState.apiCallsInProgress, + action +) { + if (action.type == types.BEGIN_API_CALL) { + return state + 1; + } else if (actionTypeEndsInSuccess(action.type)) { + return state - 1; + } + + return state; +} diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index 5ecd3dc..beef339 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -1,10 +1,12 @@ import { combineReducers } from "redux"; import courseReducer from "./courseReducer"; import authorReducer from "./authorReducer"; +import apiStatusReducer from "./apiStatusReducer"; const rootReducer = combineReducers({ courses: courseReducer, - authors: authorReducer + authors: authorReducer, + apiCallsInProgress: apiStatusReducer }); export default rootReducer; diff --git a/src/redux/reducers/initialState.js b/src/redux/reducers/initialState.js index 8d2e4a3..5ca860a 100644 --- a/src/redux/reducers/initialState.js +++ b/src/redux/reducers/initialState.js @@ -1,4 +1,5 @@ export default { courses: [], - authors: [] + authors: [], + apiCallsInProgress: 0 };