From b401dc1e0ccfe44bb354e7c43e536eab813a9f32 Mon Sep 17 00:00:00 2001 From: Carlo Cruz Date: Sun, 26 Apr 2020 22:46:49 +1000 Subject: [PATCH] Coding Challenge --- package.json | 11 +- src/HOC/LayoutHOC.jsx | 76 ++++++ src/HOC/LayoutHOC.test.jsx | 17 ++ src/__tests__/Home.test.js | 3 +- src/actions/index.js | 52 +++++ src/components/product-detail/Book-Detail.jsx | 84 +++++++ .../product-detail/Book-Detail.test.jsx | 26 +++ .../product-list/Book-List-Props.jsx | 14 ++ src/components/product-list/Book-List.jsx | 81 +++++++ .../product-list/Book-List.test.jsx | 26 +++ src/index.css | 7 +- src/index.js | 13 +- src/reducers/books.js | 23 ++ src/reducers/index.js | 3 + src/routes.jsx | 32 +-- src/services/product.js | 18 ++ src/services/product.test.js | 30 +++ src/views/Home.jsx | 15 +- yarn.lock | 217 +++++++++++++++++- 19 files changed, 704 insertions(+), 44 deletions(-) create mode 100644 src/HOC/LayoutHOC.jsx create mode 100644 src/HOC/LayoutHOC.test.jsx create mode 100644 src/actions/index.js create mode 100644 src/components/product-detail/Book-Detail.jsx create mode 100644 src/components/product-detail/Book-Detail.test.jsx create mode 100644 src/components/product-list/Book-List-Props.jsx create mode 100644 src/components/product-list/Book-List.jsx create mode 100644 src/components/product-list/Book-List.test.jsx create mode 100644 src/reducers/books.js create mode 100644 src/reducers/index.js create mode 100644 src/services/product.js create mode 100644 src/services/product.test.js diff --git a/package.json b/package.json index e692922..543ec0c 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,23 @@ "description": "Coding Challenge for React developers", "private": false, "dependencies": { + "axios": "^0.19.2", + "babel-plugin-styled-components": "^1.10.7", + "dotenv": "^8.2.0", "env-cmd": "^9.0.3", "history": "^4.9.0", "prop-types": "^15.6.2", "query-string": "^6.7.0", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-redux": "^7.2.0", "react-router": "^4.3.1", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.1.2", "react-scripts": "3.0.1", - "sanitize.css": "^10.0.0" + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "sanitize.css": "^10.0.0", + "styled-components": "^5.1.0" }, "devDependencies": { "enzyme": "^3.6.0", diff --git a/src/HOC/LayoutHOC.jsx b/src/HOC/LayoutHOC.jsx new file mode 100644 index 0000000..e0b3894 --- /dev/null +++ b/src/HOC/LayoutHOC.jsx @@ -0,0 +1,76 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; + +import { BookListComponentPropTypes, BookComponentPropTypes } from '../components/product-list/Book-List-Props'; +import { fetchBooks } from '../actions'; + +const Layout = styled.div` + width: 80%; + margin: 0 auto; +`; + +const LayoutHOC = pageType => (WrapperComponent) => { + class HOC extends Component { + constructor(props) { + super(props); + + this.state = {}; + } + + componentWillMount() { + const { dispatch, match } = this.props; + + if (pageType === 'book-list') { + dispatch(fetchBooks()); + } + + if (pageType === 'book-detail') { + dispatch(fetchBooks(match.params.id)); + } + } + + render() { + return ( + +

Books

+ + +
+ ); + } + } + + HOC.propTypes = { + dispatch: PropTypes.func.isRequired, + isFetching: PropTypes.bool, + books: BookListComponentPropTypes.books, + book: BookComponentPropTypes, + + match: PropTypes.shape({ + params: PropTypes.shape({ + id: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + }; + + + HOC.defaultProps = { + books: [], + book: {}, + isFetching: false, + }; + + const mapStateToProps = state => ({ + isFetching: state.isFetching, + books: state.books, + book: state.book, + }); + + return connect(mapStateToProps)(HOC); +}; + + +export default LayoutHOC; diff --git a/src/HOC/LayoutHOC.test.jsx b/src/HOC/LayoutHOC.test.jsx new file mode 100644 index 0000000..7276dcd --- /dev/null +++ b/src/HOC/LayoutHOC.test.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + BookDetailComponent +} from '../components/product-detail/Book-Detail'; + +import LayoutHOC from './LayoutHOC'; + +// Couldn't get this to work. I think I made the HOC too complex. +describe('', () => { + it('renders', () => { + // const el = LayoutHOC('book-detail')(BookDetailComponent); + // const wrapper = shallow(el); + // expect(wrapper).toBeTruthy(); + }); +}); diff --git a/src/__tests__/Home.test.js b/src/__tests__/Home.test.js index 8ec88a1..7328e5f 100644 --- a/src/__tests__/Home.test.js +++ b/src/__tests__/Home.test.js @@ -6,7 +6,8 @@ import Home from '../views/Home'; describe('', () => { describe('renders', () => { it('without crashing', () => { - shallow(); + const wrapper = shallow(); + expect(wrapper).toBeTruthy(); }); }); }); diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 0000000..40af827 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,52 @@ +import getBooks from '../services/product'; + +export const requestBooks = () => ({ + type: 'REQUEST_BOOKS', +}); + +export const receiveBooks = books => ({ + type: 'RECEIVE_BOOKS', + books, +}); + +export const requestBook = bookId => ({ + type: 'REQUEST_BOOK', + bookId, +}); + +export const receiveBook = book => ({ + type: 'RECEIVE_BOOK', + book, +}); + +const getBook = (bookId, bookArray) => { + const output = bookArray.filter(curr => (parseInt(curr.book_id, 10) === parseInt(bookId, 10))); + if (output.length > 0) { + return output[0]; + } + + return []; +}; + +export const fetchBooks = bookId => (dispatch, getState) => { + const state = getState(); + if (!state.books) { + dispatch(requestBooks()); + return getBooks().then((books) => { + dispatch(receiveBooks(books)); + + if (bookId) { + dispatch(requestBook(bookId)); + dispatch(receiveBook(getBook(bookId, books))); + } + }); + } + + if (bookId) { + dispatch(requestBook(bookId)); + return dispatch(receiveBook(getBook(bookId, state.books))); + } + + dispatch(requestBooks()); + return dispatch(receiveBooks(state.books)); +}; diff --git a/src/components/product-detail/Book-Detail.jsx b/src/components/product-detail/Book-Detail.jsx new file mode 100644 index 0000000..7de55c8 --- /dev/null +++ b/src/components/product-detail/Book-Detail.jsx @@ -0,0 +1,84 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; + +import LayoutHOC from '../../HOC/LayoutHOC'; + +export const Detail = styled.div` + margin: 20px 0; + display: flex; + width: 30%; +`; + +export const Author = styled.span` + display: block; + font-size: 12px; +`; + +export const Title = styled.span` + display: block; + font-size: 14px; +`; + +export const Cover = styled.img` + display: block; + border: 1px solid black; + min-width: 50px; + max-width: 100px; + min-height: 50px; + max-height: 100px; + margin-right: 20px; + +`; + +export const ISBN = styled.p` + margin-bottom: 8px; + font-size: 10px; +`; + +export const Publish = styled.p` + margin-bottom: 8px; + font-size: 10px; + font-weight: bold; +`; + +export function BookDetailComponent({ book }) { + if (book) { + return ( + + +
+ {book.name} + {book.author} + + ISBN: + {book.isbn} + + + Published on: + {book.published_at} + +
+
+ ); + } + + return
No books found
; +} + +BookDetailComponent.propTypes = { + book: PropTypes.shape({ + book_id: PropTypes.number, + name: PropTypes.string, + isbn: PropTypes.string, + published_at: PropTypes.string, + author: PropTypes.string, + cover: PropTypes.string, + }), +}; + +BookDetailComponent.defaultProps = { + book: {}, +}; + +export default LayoutHOC('book-detail')(BookDetailComponent); diff --git a/src/components/product-detail/Book-Detail.test.jsx b/src/components/product-detail/Book-Detail.test.jsx new file mode 100644 index 0000000..84f9e98 --- /dev/null +++ b/src/components/product-detail/Book-Detail.test.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + BookDetailComponent, Author, Cover, ISBN, Publish, Title, +} from './Book-Detail'; + +describe('', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper).toBeTruthy(); + }); + + it('renders book detail properly', () => { + const book = { + book_id: 1, name: 'Book Title', author: 'Author', cover: 'Cover', + }; + const wrapper = shallow(); + + expect(wrapper.find(Author).length).toBe(1); + expect(wrapper.find(Cover).length).toBe(1); + expect(wrapper.find(Title).length).toBe(1); + expect(wrapper.find(ISBN).length).toBe(1); + expect(wrapper.find(Publish).length).toBe(1); + }); +}); diff --git a/src/components/product-list/Book-List-Props.jsx b/src/components/product-list/Book-List-Props.jsx new file mode 100644 index 0000000..1f555d9 --- /dev/null +++ b/src/components/product-list/Book-List-Props.jsx @@ -0,0 +1,14 @@ +import PropTypes from 'prop-types'; + +export const BookComponentPropTypes = { + book_id: PropTypes.number, + name: PropTypes.string, + isbn: PropTypes.string, + published_at: PropTypes.string, + author: PropTypes.string, + cover: PropTypes.string, +}; + +export const BookListComponentPropTypes = { + books: PropTypes.arrayOf(PropTypes.shape(BookComponentPropTypes)), +}; diff --git a/src/components/product-list/Book-List.jsx b/src/components/product-list/Book-List.jsx new file mode 100644 index 0000000..2203b8c --- /dev/null +++ b/src/components/product-list/Book-List.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +import LayoutHOC from '../../HOC/LayoutHOC'; +import { BookListComponentPropTypes } from './Book-List-Props'; + +export const List = styled.ul` + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; +`; + +export const StyledLink = styled(Link)` + display: flex; + color: #333; + + &:hover { + text-decoration: none; + } +`; + +export const ListItem = styled.li` + margin: 20px 0; + display: flex; + width: 30%; +`; + +export const Author = styled.span` + display: block; + font-size: 12px; +`; + +export const Title = styled.span` + display: block; + font-size: 14px; +`; + +export const Cover = styled.img` + display: block; + border: 1px solid #333; + min-width: 50px; + max-width: 100px; + min-height: 50px; + max-height: 100px; + margin-right: 20px; + +`; + + +export function BookListComponent({ books }) { + if (books && books.length) { + return ( + + {books.map(curr => ( + + + + +
+ {curr.name} + {curr.author} +
+
+
+ ))} +
+ ); + } + + return
No books found
; +} + +BookListComponent.propTypes = BookListComponentPropTypes; + +BookListComponent.defaultProps = { + books: [], +}; + +export default LayoutHOC('book-list')(BookListComponent); diff --git a/src/components/product-list/Book-List.test.jsx b/src/components/product-list/Book-List.test.jsx new file mode 100644 index 0000000..4373aad --- /dev/null +++ b/src/components/product-list/Book-List.test.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + BookListComponent as BL, Author, Cover, List, ListItem, Title, +} from './Book-List'; + +describe('', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper).toBeTruthy(); + }); + + it('renders book list properly', () => { + const books = [{ + book_id: 1, name: 'Book Title', author: 'Author', cover: 'Cover', + }]; + const wrapper = shallow(); + + expect(wrapper.find(Author).length).toBe(1); + expect(wrapper.find(List).length).toBe(1); + expect(wrapper.find(Cover).length).toBe(1); + expect(wrapper.find(ListItem).length).toBe(1); + expect(wrapper.find(Title).length).toBe(1); + }); +}); diff --git a/src/index.css b/src/index.css index c0a127a..cdb3c42 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,6 @@ body { - background-color: #54a0ff; - color: #341f97; - font-weight: 100; + background-color: #efefef; + color: #333; + font-weight: normal; + font-family: sans-serif; } diff --git a/src/index.js b/src/index.js index 585eea9..1060a1c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,25 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; import * as serviceWorker from './serviceWorker'; + +import productListApp from './reducers'; + +import './index.css'; import 'sanitize.css'; import Routes from './routes'; +// eslint-disable-next-line max-len +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; +const store = createStore(productListApp, /* preloadedState, */ composeEnhancers( + applyMiddleware(thunk) +)); ReactDOM.render( // TODO use jsx extension for this file, will require to eject the create-react-app // eslint-disable-next-line react/jsx-filename-extension - , + , document.getElementById('root'), ); diff --git a/src/reducers/books.js b/src/reducers/books.js new file mode 100644 index 0000000..854e60c --- /dev/null +++ b/src/reducers/books.js @@ -0,0 +1,23 @@ +const books = (state = [], action) => { + switch (action.type) { + case 'REQUEST_BOOKS': + return { + ...state, + isFetching: true, + }; + case 'RECEIVE_BOOKS': + return { + ...state, + isFetching: false, + books: action.books, + }; + case 'REQUEST_BOOK': + return { ...state, isFetching: true }; + case 'RECEIVE_BOOK': + return { ...state, isFetching: false, book: action.book }; + default: + return state; + } +}; + +export default books; diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 0000000..769f145 --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,3 @@ +import books from './books'; + +export default books; diff --git a/src/routes.jsx b/src/routes.jsx index 269ceb0..3eae44d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -1,18 +1,24 @@ +/* eslint-disable react/forbid-prop-types */ + import React from 'react'; -import { - Router, - Route, - Switch, -} from 'react-router-dom'; -import { createBrowserHistory } from 'history'; -import Home from './views/Home'; +import { Provider } from 'react-redux'; +import { PropTypes } from 'prop-types'; +import { BrowserRouter as Router, Route } from 'react-router-dom'; +import BookList from './components/product-list/Book-List'; +import BookDetail from './components/product-detail/Book-Detail'; -const Routes = () => ( - - - - - +const Routes = ({ store }) => ( + + + + + + + ); +Routes.propTypes = { + store: PropTypes.object.isRequired, +}; + export default Routes; diff --git a/src/services/product.js b/src/services/product.js new file mode 100644 index 0000000..58f7cf9 --- /dev/null +++ b/src/services/product.js @@ -0,0 +1,18 @@ +import axios from 'axios'; +import dotenv from 'dotenv'; + +dotenv.config(); + +export default async function getBooks() { + try { + const products = await axios + .get(process.env.REACT_APP_API_URL); + if (products && products.data) { + return products.data; + } + } catch (err) { + console.log(err); + } + + return []; +} diff --git a/src/services/product.test.js b/src/services/product.test.js new file mode 100644 index 0000000..cce07de --- /dev/null +++ b/src/services/product.test.js @@ -0,0 +1,30 @@ +import axios from 'axios'; +import dotenv from 'dotenv'; + +import { getBooks } from './product'; + +jest.mock('axios'); + +dotenv.config(); + +describe('Services', () => { + describe('getBooks', () => { + it('should return something', () => { + const id = Math.floor(Math.random() * 10); + const books = [ + { + book_id: id, + name: 'Becker West Arnoldo', + isbn: '3181444340', + published_at: '2000-01-01', + author: 'Mrs. John Doe', + cover: 'https://lorempixel.com/640/480/?82539', + }, + ]; + + axios.get.mockImplementation(() => Promise.resolve({ data: books })); + + return expect(getBooks()).resolves.toEqual(books); + }); + }); +}); diff --git a/src/views/Home.jsx b/src/views/Home.jsx index bc30ad0..fcd0cb9 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -7,25 +7,12 @@ const HomeStyle = { justifyContent: 'center', }; -const Home = ({ match }) => ( +const Home = () => (

Welcome!

- {(match.params.testRouting) && ( -

- {match.params.testRouting} -

- )}
); -Home.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - testRouting: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, -}; - export default Home; diff --git a/yarn.lock b/yarn.lock index 72e6d81..ed45089 100644 --- a/yarn.lock +++ b/yarn.lock @@ -859,6 +859,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.4.0", "@babel/runtime@^7.5.5": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -910,6 +917,28 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-9.0.1.tgz#c27b391d8457d1e893f1eddeaf5e5412d12ffbb5" integrity sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA== +"@emotion/is-prop-valid@^0.8.8": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@hapi/address@2.x.x": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" @@ -1863,6 +1892,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + axobject-query@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" @@ -1958,6 +1994,21 @@ babel-plugin-named-asset-import@^0.3.2: resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz#20978ed446b8e1bf4a2f42d0a94c0ece85f75f4f" integrity sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ== +"babel-plugin-styled-components@>= 1", babel-plugin-styled-components@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz#3494e77914e9989b33cc2d7b3b29527a949d635c" + integrity sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-module-imports" "^7.0.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -2360,6 +2411,11 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -2862,6 +2918,11 @@ css-blank-pseudo@^0.1.4: dependencies: postcss "^7.0.5" +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -2932,6 +2993,15 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@1.0.0-alpha.28: version "1.0.0-alpha.28" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" @@ -3103,6 +3173,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -3390,6 +3467,11 @@ dotenv@6.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -4199,6 +4281,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + follow-redirects@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" @@ -4478,6 +4567,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80" @@ -4658,6 +4752,13 @@ hoist-non-react-statics@^2.5.0: resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -6355,6 +6456,15 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + mini-css-extract-plugin@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" @@ -7904,6 +8014,11 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" @@ -8242,22 +8357,55 @@ react-error-overlay@^5.1.6: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d" integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q== +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== -react-router-dom@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" - integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== +react-redux@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== dependencies: - history "^4.7.2" - invariant "^2.2.4" + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" loose-envify "^1.3.1" - prop-types "^15.6.1" - react-router "^4.3.1" - warning "^4.0.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" react-router@^4.3.1: version "4.3.1" @@ -8431,6 +8579,19 @@ recursive-readdir@2.2.2: dependencies: minimatch "3.0.4" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -8458,6 +8619,11 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + regenerator-transform@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.0.tgz#2ca9aaf7a2c239dd32e4761218425b8c7a86ecaf" @@ -8951,6 +9117,11 @@ shallow-clone@^1.0.0: kind-of "^5.0.0" mixin-object "^2.0.1" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -9379,6 +9550,22 @@ style-loader@0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" +styled-components@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.0.tgz#2e3985b54f461027e1c91af3229e1c2530872a4e" + integrity sha512-0Qs2wEkFBXHFlysz6CV831VG6HedcrFUwChjnWylNivsx14MtmqQsohi21rMHZxzuTba063dEyoe/SR6VGJI7Q== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^0.8.8" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" @@ -9393,7 +9580,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -9427,6 +9614,11 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -9568,6 +9760,11 @@ tiny-warning@^1.0.0: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q== +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"