diff --git a/.eslintrc.json b/.eslintrc.json index a911dc8..e4dbb58 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "env": { "browser": true, - "es2021": true + "es2021": true, + "jest": true }, "extends": [ "plugin:react/recommended", @@ -26,7 +27,8 @@ "prettier/prettier":"error", "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": ["error"], - "react/prop-types": "off" + "react/prop-types": "off", + "camelcase": "off" }, "settings": { "import/resolver": "eslint-import-resolver-typescript" diff --git a/README.md b/README.md index b58e0af..feb6876 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,31 @@ -# Getting Started with Create React App -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +# [🐙Github Compare](https://github.com/filipemelo2002/github-compare) +Github Compare is a web application that allows users to show comparisons of repositories selected by them on Github. -## Available Scripts +This App uses github [**GitHub API V3**](https://docs.github.com/en/rest) -In the project directory, you can run: +## 📦 Packages +### Configuration +* [ESlint](https://eslint.org/) +* [Prettier](https://prettier.io/) +* [Typescript](https://www.typescriptlang.org/) +### CSS +* [ClayUI](https://clayui.com/) +* [styled-components](https://styled-components.com/) +## Tests +* [Jest](https://jestjs.io/) +* [Testing Library](https://testing-library.com/) -### `yarn start` +## ⚙️Getting started + + +Clone this project and install the dependencies: +```bash +git clone https://github.com/filipemelo2002/github-compare.git +cd github-compare +npm install # or yarn install +``` +### `npm run start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. @@ -29,18 +48,55 @@ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -### `yarn eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). +## 📸Screenshots +![Screenshot github-compare](https://i.imgur.com/N8J3cdz.png) + +![Screenshot github-compare](https://i.imgur.com/RKhMNqE.png) + +![Screenshot github-compare](https://i.imgur.com/xE6loky.png) + +![Screenshot github-compare](https://i.imgur.com/gBXkS8p.png) + +![Screenshot github-compare](https://i.imgur.com/Q3AjysV.png) + +## 🧑‍🏭Architecture +Below, you can see how the files are organized: +```bash +github-compare/ +┣ public/ +┃ ┣ favicon.ico +┃ ┣ index.html +┃ ┣ logo192.png +┃ ┗ logo512.png +┣ src/ +┃ ┣ @types/ +┃ ┣ __tests__/ +┃ ┣ api/ +┃ ┣ assets/ +┃ ┣ components/ +┃ ┣ pages/ +┃ ┣ redux/ +┃ ┣ services/ +┃ ┣ styles/ +┃ ┣ App.tsx +┃ ┣ index.tsx +┃ ┗ react-app-env.d.ts +┣ .eslintrc.json +┣ .gitignore +┣ .prettierrc +┣ README.md +┣ package.json +┣ tsconfig.json +┗ yarn.lock +``` +### Components +Are inside components folder and each of them work individually. + +### Pages +currently, there's only one page, Home, which is responsible for rendering all components. + +### Tests +Are declared inside *\_\_tests\_\_* folder. + +## 📃 License +The MIT License (MIT) \ No newline at end of file diff --git a/package.json b/package.json index 23c714c..ab5bce5 100644 --- a/package.json +++ b/package.json @@ -3,23 +3,35 @@ "version": "0.1.0", "private": true, "dependencies": { - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "@types/jest": "^26.0.15", - "@types/node": "^12.0.0", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", + "@clayui/button": "^3.6.0", + "@clayui/card": "^3.25.1", + "@clayui/css": "^3.25.3", + "@clayui/drop-down": "^3.25.1", + "@clayui/form": "^3.14.4", + "@clayui/label": "^3.4.1", + "@clayui/loading-indicator": "^3.2.0", + "@clayui/management-toolbar": "^3.3.0", + "@clayui/modal": "^3.8.5", + "add": "^2.0.6", + "axios": "^0.21.1", + "lodash.debounce": "^4.0.8", + "moment": "^2.29.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-icons": "^4.2.0", + "react-redux": "^7.2.3", "react-scripts": "4.0.3", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "styled-components": "^5.2.3", "typescript": "^4.1.2", - "web-vitals": "^1.0.1" + "web-vitals": "^1.0.1", + "yarn": "^1.22.10" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test", + "test": "react-scripts test --runInBand", "eject": "react-scripts eject" }, "eslintConfig": { @@ -41,6 +53,18 @@ ] }, "devDependencies": { + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.2.6", + "@testing-library/user-event": "^12.1.10", + "@types/jest": "^26.0.22", + "@types/lodash.debounce": "^4.0.6", + "@types/mocha": "^8.2.2", + "@types/node": "^14.14.37", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/redux-logger": "^3.0.8", + "@types/redux-mock-store": "^1.0.2", + "@types/styled-components": "^5.1.9", "@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/parser": "^4.21.0", "eslint": "^7.12.1", @@ -52,6 +76,9 @@ "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.23.2", - "prettier": "^2.2.1" + "prettier": "^2.2.1", + "redux-logger": "^3.0.6", + "redux-mock-store": "^1.5.4", + "ts-node": "^9.1.1" } } diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..d4d51ff 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index aa069f2..711f256 100644 --- a/public/index.html +++ b/public/index.html @@ -10,34 +10,10 @@ content="Web site created using create-react-app" /> - - - - React App + Github Compare
- diff --git a/public/logo192.png b/public/logo192.png index fc44b0a..bb17c8b 100644 Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png index a4e47a6..bb17c8b 100644 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 080d6c7..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index e9e57dc..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/src/@types/redux.d.ts b/src/@types/redux.d.ts new file mode 100644 index 0000000..3059d1d --- /dev/null +++ b/src/@types/redux.d.ts @@ -0,0 +1,16 @@ +interface IRepositoryState { + data: IRepository[]; + loading: boolean; + error: boolean; + filter: { + name: string; + data: IRepository[]; + starred: boolean; + sortBy: Sort; + }; + listType: ListType; +} + +interface State { + repository: IRepositoryState; +} diff --git a/src/@types/repository.d.ts b/src/@types/repository.d.ts new file mode 100644 index 0000000..720eaf1 --- /dev/null +++ b/src/@types/repository.d.ts @@ -0,0 +1,25 @@ +interface ILicense { + name: string; +} +interface IRepository { + id: number; + full_name: string; + stargazers_count: number; + forks: number; + open_issues: number; + created_at: string; + pushed_at: string; + license: ILicense | null; + starred?: boolean; + language: string; +} + +interface IRepositoryList { + items: IRepository[]; +} +type Sort = 'stars' | 'forks' | 'openIssues' | 'age' | 'lastCommit' | ''; +type ListType = 'grid' | 'row'; +interface FilterActionSort { + type: string; + payload: Sort; +} diff --git a/src/App.tsx b/src/App.tsx index 4cd93c8..5e2c08f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,14 @@ import React from 'react'; import Home from './pages/Home'; - -const App = () => () +import GlobalStyle from './styles/globalStyles'; +import '@clayui/css/lib/css/atlas.css'; +import { Provider } from 'react-redux'; +import store from './redux/store'; +const App: React.FC = () => ( + + + + +); export default App; diff --git a/src/__tests__/action/repository.spec.ts b/src/__tests__/action/repository.spec.ts new file mode 100644 index 0000000..ec84a88 --- /dev/null +++ b/src/__tests__/action/repository.spec.ts @@ -0,0 +1,83 @@ +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as actions from '../../redux/action/repository'; +import Api from '../../api/index'; +import axios from 'axios'; + +jest.mock('../../api/index'); +const mockedAxios = Api as jest.Mocked; +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); + +const TEMPLATE_NAME = 'REPOSITORY'; + +describe('Repository actions', () => { + const repository: IRepository = { + full_name: 'filipemelo2002/filipemelo2002', + stargazers_count: 79, + forks: 0, + open_issues: 0, + created_at: '', + pushed_at: '', + license: { + name: 'MIT', + }, + }; + it('should dispatch a new Repository', async () => { + mockedAxios.get.mockResolvedValue({ + data: repository, + }); + + const expectedActions = [ + { type: `${TEMPLATE_NAME}_PENDING` }, + { + type: `${TEMPLATE_NAME}_SUCCESS`, + payload: repository, + }, + ]; + + const store = mockStore({ repository: { data: [] } }); + await store.dispatch( + actions.getRepository('filipemelo2002/filipemelo2002'), + ); + + expect(store.getActions()).toEqual(expectedActions); + }); + + it('should dispatch a few new Repositories', async () => { + mockedAxios.get.mockResolvedValue({ + data: { + items: [repository], + }, + }); + + const expectedActions = [ + { type: `${TEMPLATE_NAME}_PENDING` }, + { + type: `${TEMPLATE_NAME}_FULFILLED`, + payload: [repository], + }, + ]; + + const store = mockStore({ repository: { data: [] } }); + await store.dispatch(actions.getAllUserRepositories('filipemelo2002')); + + expect(store.getActions()).toEqual(expectedActions); + }); + + it('should search by repository name', () => { + const response = actions.search('filipemelo2002'); + expect(response).toEqual({ + type: `${TEMPLATE_NAME}_SEARCH`, + payload: 'filipemelo2002', + }); + }); + + it('should filter by starred', () => { + const response = actions.filterByStar(true); + expect(response).toEqual({ + type: `${TEMPLATE_NAME}_FILTER_STARRED`, + payload: true, + }); + }); +}); diff --git a/src/__tests__/api/api.spec.ts b/src/__tests__/api/api.spec.ts new file mode 100644 index 0000000..d05eab5 --- /dev/null +++ b/src/__tests__/api/api.spec.ts @@ -0,0 +1,26 @@ +import * as Api from '../../api/repository'; + +describe('API Repository Requests', () => { + it('should return a single repository object', async () => { + const response = await Api.getRepository('filipemelo2002/filipemelo2002'); + expect(response).toHaveProperty('id'); + }); + + it('should return an error message', async () => { + try { + await Api.getRepository('filipemelo2002/invalid-repo'); + } catch (err) { + expect(err.response.status).toBe(404); + } + }); + + it("should return all user's repositories", async () => { + const response = await Api.getAllUserRepositories('filipemelo2002'); + expect(response.items.length).toBeGreaterThan(0); + }); + + it("should fail fetching for user's repositories", async () => { + const response = await Api.getAllUserRepositories('filipemelo2002INVALID'); + expect(response.items.length).toBe(0); + }); +}); diff --git a/src/__tests__/components/Card.spec.tsx b/src/__tests__/components/Card.spec.tsx new file mode 100644 index 0000000..85aac70 --- /dev/null +++ b/src/__tests__/components/Card.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import Card from '../../components/Card'; + +describe('Card Component', () => { + it('should test if Card renders', async function () { + const { findByText } = render( + , + ); + + await findByText('liferay/liferay-portal'); + }); +}); diff --git a/src/__tests__/components/CardRow.spec.tsx b/src/__tests__/components/CardRow.spec.tsx new file mode 100644 index 0000000..01fb814 --- /dev/null +++ b/src/__tests__/components/CardRow.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import CardRow from '../../components/CardRow'; + +describe('CardRow Component', () => { + it('should test if CardRow renders', async function () { + const { findByText } = render( + , + ); + + await findByText('liferay/liferay-portal'); + }); +}); diff --git a/src/__tests__/components/DeleteModal.spec.tsx b/src/__tests__/components/DeleteModal.spec.tsx new file mode 100644 index 0000000..fb4bacb --- /dev/null +++ b/src/__tests__/components/DeleteModal.spec.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import DeleteModal from '../../components/DeleteModal'; + +describe('Delete Confirmation Modal', () => { + it('should test if DeleteModal renders', function () { + const { debug } = render(); + debug(); + }); +}); diff --git a/src/__tests__/components/FilterDropDown.spec.tsx b/src/__tests__/components/FilterDropDown.spec.tsx new file mode 100644 index 0000000..7f7a05f --- /dev/null +++ b/src/__tests__/components/FilterDropDown.spec.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import FilterDropDown from '../../components/FilterDropDown'; + +describe('Filter Dropdown Component', () => { + it('should test if FilterDropDown renders', function () { + const { debug } = render(); + debug(); + }); +}); diff --git a/src/__tests__/components/ListTypeToggleButton.spec.tsx b/src/__tests__/components/ListTypeToggleButton.spec.tsx new file mode 100644 index 0000000..6713d3d --- /dev/null +++ b/src/__tests__/components/ListTypeToggleButton.spec.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import ListTypeToggleButton from '../../components/ListTypeToggleButton'; + +describe('List Type Toggle Button', () => { + it('should test if Button fires event', function () { + const { getByTestId } = render(); + fireEvent.click(getByTestId('btn-grid')); + expect(getByTestId('btn-row')).not.toBeNull(); + }); +}); diff --git a/src/__tests__/services/sort.spec.ts b/src/__tests__/services/sort.spec.ts new file mode 100644 index 0000000..7a0b468 --- /dev/null +++ b/src/__tests__/services/sort.spec.ts @@ -0,0 +1,75 @@ +import sort from '../../services/sort'; + +describe('Sort functions', () => { + const repositories = [ + { + full_name: 'filipemelo2002/STARS', + stargazers_count: 79, + forks: 45, + open_issues: 2, + created_at: '2021-04-12T21:37:12Z', + pushed_at: '2021-04-12T21:37:12Z', + license: { + name: 'MIT', + }, + }, + { + full_name: 'filipemelo2002/FORKS', + stargazers_count: 79, + forks: 759, + open_issues: 0, + created_at: '2021-04-12T21:37:12Z', + pushed_at: '2021-04-12T21:37:12Z', + license: { + name: 'MIT', + }, + }, + { + full_name: 'filipemelo2002/AGE', + stargazers_count: 759, + forks: 0, + open_issues: 0, + created_at: '2014-07-29T11:07:36Z', + pushed_at: '2021-04-12T21:37:12Z', + license: { + name: 'MIT', + }, + }, + { + full_name: 'filipemelo2002/COMMIT', + stargazers_count: 79, + forks: 0, + open_issues: 45, + created_at: '2021-04-12T21:37:12Z', + pushed_at: '2014-07-29T11:07:36Z', + license: { + name: 'MIT', + }, + }, + ]; + + it('should sort by stars', () => { + const response = sort.stars(repositories); + expect(response[0].full_name).toBe('filipemelo2002/AGE'); + }); + + it('should sort by forks', () => { + const response = sort.forks(repositories); + expect(response[0].full_name).toBe('filipemelo2002/FORKS'); + }); + + it('should sort by Open Issues', () => { + const response = sort.openIssues(repositories); + expect(response[1].full_name).toBe('filipemelo2002/STARS'); + }); + + it('should sort by age', () => { + const response = sort.age(repositories); + expect(response[0].full_name).toBe('filipemelo2002/AGE'); + }); + + it('should sort by last commit', () => { + const response = sort.lastCommit(repositories); + expect(response[0].full_name).toBe('filipemelo2002/COMMIT'); + }); +}); diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..667911a --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,5 @@ +import axios from 'axios'; + +export default axios.create({ + baseURL: 'https://api.github.com/', +}); diff --git a/src/api/repository.ts b/src/api/repository.ts new file mode 100644 index 0000000..6bf6215 --- /dev/null +++ b/src/api/repository.ts @@ -0,0 +1,15 @@ +import Api from '.'; + +export const getRepository = async (term: string): Promise => { + const response = await Api.get(`repos/${term}`); + return response.data; +}; + +export const getAllUserRepositories = async ( + user: string, +): Promise => { + const response = await Api.get( + `search/repositories?q=${user}`, + ); + return response.data.items; +}; diff --git a/src/assets/emptyList.svg b/src/assets/emptyList.svg new file mode 100644 index 0000000..eae0bb6 --- /dev/null +++ b/src/assets/emptyList.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..bb17c8b Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..203f383 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/components/Card/index.tsx b/src/components/Card/index.tsx new file mode 100644 index 0000000..3142779 --- /dev/null +++ b/src/components/Card/index.tsx @@ -0,0 +1,109 @@ +import React from 'react'; + +import moment from 'moment'; +import { CardHeader, CardBody } from './styles'; +import ClayCard from '@clayui/card'; +import { MdStarBorder, MdStar } from 'react-icons/md'; +import logo from '../../assets/logo.png'; +import ClayLabel from '@clayui/label'; +import DeleteModal from '../DeleteModal'; +import { useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; +interface Props { + id: number; + name: string; + stars: number; + forks: number; + openIssues: number; + age: string; + lastCommit: string; + license: string; + starred: boolean; + language: string; +} +const Card: React.FC = ({ + id, + name, + stars, + forks, + openIssues, + age, + lastCommit, + license, + language, + starred, +}) => { + const dispatch = useDispatch(); + function truncate(source: string, size: number): string { + return source.length > size ? source.slice(0, size - 1) + '…' : source; + } + return ( +
+ + +
+ +

{truncate(name, 30)}

+
+
+ + +
+
+ +
    +
  • +

    + Stars {stars} +

    +
  • + +
  • +

    + Forks {forks} +

    +
  • + +
  • +

    + Open Issues {openIssues} +

    +
  • +
  • +

    + Age {moment(new Date(age)).fromNow()} +

    +
  • +
  • +

    + Last commit + {moment(new Date(lastCommit)).fromNow()} +

    +
  • +
  • +

    + License {license} +

    +
  • +
  • + {language && ( + {language} + )} +
  • +
+
+
+
+ ); +}; + +export default Card; diff --git a/src/components/Card/styles.ts b/src/components/Card/styles.ts new file mode 100644 index 0000000..639dcca --- /dev/null +++ b/src/components/Card/styles.ts @@ -0,0 +1,52 @@ +import styled from 'styled-components'; + +export const CardHeader = styled.div` + display: flex; + align-items: center; + border-bottom: 1px solid #e5e5e5; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 5px; + h4 { + font-size: 14px; + margin-left: 4px; + font-weight: 500; + max-width: 70%; + } + section { + width: 70%; + display: flex; + align-items: center; + flex-wrap: wrap; + button { + width: 32px; + height: 32px; + background: transparent; + border: 0px; + border-radius: 4px; + transition: all 200ms ease; + :hover { + background: rgba(39, 40, 51, 0.04); + } + } + } + #float-left { + margin-left: auto; + width: auto; + } +`; + +export const CardBody = styled.div` + padding: 12px 16px; + ul { + li { + p { + font-weight: 600; + + span { + font-weight: 400; + } + } + } + } +`; diff --git a/src/components/CardRow/index.tsx b/src/components/CardRow/index.tsx new file mode 100644 index 0000000..648e4d2 --- /dev/null +++ b/src/components/CardRow/index.tsx @@ -0,0 +1,110 @@ +import React from 'react'; + +import moment from 'moment'; +import { CardHeader, CardBody } from './styles'; +import ClayCard from '@clayui/card'; +import { MdStarBorder, MdStar } from 'react-icons/md'; +import logo from '../../assets/logo.png'; +import ClayLabel from '@clayui/label'; +import DeleteModal from '../DeleteModal'; +import { useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; + +interface Props { + id: number; + name: string; + stars: number; + forks: number; + openIssues: number; + age: string; + lastCommit: string; + license: string; + starred: boolean; + language: string; +} +const Card: React.FC = ({ + id, + name, + stars, + forks, + openIssues, + age, + lastCommit, + license, + language, + starred, +}) => { + const dispatch = useDispatch(); + function truncate(source: string, size: number): string { + return source.length > size ? source.slice(0, size - 1) + '…' : source; + } + return ( +
+ + +
+ +

{truncate(name, 70)}

+
+
+ + +
+
+ +
    +
  • +

    + Stars {stars} +

    +
  • + +
  • +

    + Forks {forks} +

    +
  • + +
  • +

    + Open Issues {openIssues} +

    +
  • +
  • +

    + Age {moment(new Date(age)).fromNow()} +

    +
  • +
  • +

    + Last commit + {moment(new Date(lastCommit)).fromNow()} +

    +
  • +
  • +

    + License {license} +

    +
  • +
+ {language && ( + + {language} + + )} +
+
+
+ ); +}; + +export default Card; diff --git a/src/components/CardRow/styles.ts b/src/components/CardRow/styles.ts new file mode 100644 index 0000000..e4b1aa3 --- /dev/null +++ b/src/components/CardRow/styles.ts @@ -0,0 +1,62 @@ +import styled from 'styled-components'; + +export const CardHeader = styled.div` + display: flex; + align-items: center; + border-bottom: 1px solid #e5e5e5; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 5px; + h4 { + font-size: 14px; + margin-left: 4px; + font-weight: 500; + } + section { + display: flex; + align-items: center; + flex-wrap: wrap; + max-width: 90%; + } + #btn-section { + margin-left: auto; + display: flex; + align-items: center; + button { + width: 32px; + height: 32px; + background: transparent; + border: 0px; + border-radius: 4px; + transition: all 200ms ease; + :hover { + background: rgba(39, 40, 51, 0.04); + } + } + } +`; + +export const CardBody = styled.div` + padding: 12px 30px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + ul { + display: flex; + flex-wrap: wrap; + max-width: 90%; + li { + p { + font-weight: 500; + color: var(--secondary); + span { + font-weight: 400; + } + } + } + li + li { + margin-left: 30px; + } + } +`; diff --git a/src/components/DeleteModal/index.tsx b/src/components/DeleteModal/index.tsx new file mode 100644 index 0000000..9309588 --- /dev/null +++ b/src/components/DeleteModal/index.tsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; + +import { Button } from './styles'; +import { FiTrash2, FiAlertTriangle } from 'react-icons/fi'; +import ClayModal, { useModal } from '@clayui/modal'; +import ClayButton from '@clayui/button'; +import { useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; +interface Props { + id: number; +} +const DeleteModal: React.FC = ({ id }) => { + const dispatch = useDispatch(); + + const [visible, setVisible] = useState(false); + const { observer, onClose } = useModal({ + onClose: () => setVisible(false), + }); + + return ( + <> + {visible && ( + + + Delete Repository + + +

+ Are you sure you want to delete liferay/senna.js{' '} + repository? +

+
+ + + Cancel + + { + dispatch(Actions.deleteRepository(id)); + onClose(); + }} + > + Delete + + + } + /> +
+ )} + + + ); +}; + +export default DeleteModal; diff --git a/src/components/DeleteModal/styles.ts b/src/components/DeleteModal/styles.ts new file mode 100644 index 0000000..7f1020e --- /dev/null +++ b/src/components/DeleteModal/styles.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const Button = styled.button` + width: 32px; + height: 32px; + background: transparent; + border: 0px; + border-radius: 4px; + transition: all 200ms ease; + :hover { + background: rgba(39, 40, 51, 0.04); + } +`; diff --git a/src/components/EmptyList/index.tsx b/src/components/EmptyList/index.tsx new file mode 100644 index 0000000..97d6f6f --- /dev/null +++ b/src/components/EmptyList/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { Container } from './styles'; +import emptyImg from '../../assets/emptyList.svg'; + +const EmptyList: React.FC = () => { + return ( + + +

There is still nothing here

+

Add some repositories by clicking add new repository

+
+ ); +}; + +export default EmptyList; diff --git a/src/components/EmptyList/styles.ts b/src/components/EmptyList/styles.ts new file mode 100644 index 0000000..e8b4768 --- /dev/null +++ b/src/components/EmptyList/styles.ts @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + h4 { + margin-top: 40px; + font-size: 20px; + color: var(--dark); + } + p { + font-size: 16px; + line-height: 30px; + color: var(--secondary); + max-width: 340px; + text-align: center; + } +`; diff --git a/src/components/FilterDropDown/index.tsx b/src/components/FilterDropDown/index.tsx new file mode 100644 index 0000000..286266c --- /dev/null +++ b/src/components/FilterDropDown/index.tsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; + +import ClayDropDown from '@clayui/drop-down'; +import { IoMdArrowDropdown, IoMdArrowDropup } from 'react-icons/io'; +import { Container } from './styles'; +import { useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; +const FilterDropDown: React.FC = () => { + const dispatch = useDispatch(); + + const [openDropdown, setOpenDropdown] = useState(false); + + const handleOnSelectSortOption = (sort: Sort) => { + dispatch(Actions.sort(sort)); + setOpenDropdown(false); + }; + return ( + + + {`Filter and order `} + {!openDropdown ? ( + + ) : ( + + )} + + } + > + + + handleOnSelectSortOption('stars')} + > + Stars + + handleOnSelectSortOption('forks')} + > + Forks + + handleOnSelectSortOption('openIssues')} + > + Open Issues + + handleOnSelectSortOption('age')}> + Age + + handleOnSelectSortOption('lastCommit')} + > + Last commit + + + + + + ); +}; + +export default FilterDropDown; diff --git a/src/components/FilterDropDown/styles.ts b/src/components/FilterDropDown/styles.ts new file mode 100644 index 0000000..8a0d1bf --- /dev/null +++ b/src/components/FilterDropDown/styles.ts @@ -0,0 +1,24 @@ +import styled from 'styled-components'; + +export const Container = styled.section` + .dropdown-trigger { + background: transparent; + border: 1px solid #fff; + border-radius: 4px; + padding: 8px 12px; + color: var(--secondary); + + font-weight: 500; + margin-bottom: 2px; + transition: 200ms all ease; + display: flex; + align-items: center; + :hover { + background: #f1f2f5; + border: 1px solid #cdced9; + } + .icon { + margin-left: 10px; + } + } +`; diff --git a/src/components/ListTypeToggleButton/index.tsx b/src/components/ListTypeToggleButton/index.tsx new file mode 100644 index 0000000..21be147 --- /dev/null +++ b/src/components/ListTypeToggleButton/index.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import { Button } from '../commons/GlobalComponents'; +// import { Container } from './styles'; +import { BsFillGridFill } from 'react-icons/bs'; +import { FaThList } from 'react-icons/fa'; +import { useSelector, useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; + +const ListType: React.FC = () => { + const dispatch = useDispatch(); + const listType = useSelector((state: State) => state.repository.listType); + + return ( + <> + {listType === 'grid' ? ( + + ) : ( + + )} + + ); +}; + +export default ListType; diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx new file mode 100644 index 0000000..b5c7055 --- /dev/null +++ b/src/components/NavBar/index.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { Container } from './styles'; +import { Button } from '../commons/GlobalComponents'; + +import { FaGithub, FaAdjust } from 'react-icons/fa'; + +import SearchBar from '../SearchBar'; +import FilterDropDown from '../FilterDropDown'; +import StarFilter from '../StarFilter'; +import NewRepositoryForm from '../NewRepositoryForm'; +import ListTypeToggleButton from '../ListTypeToggleButton'; + +const NavBar: React.FC = () => { + return ( + +
+ + +
+ +
+ +
+ +
+ ); +}; + +export default NavBar; diff --git a/src/components/NavBar/styles.ts b/src/components/NavBar/styles.ts new file mode 100644 index 0000000..37cc043 --- /dev/null +++ b/src/components/NavBar/styles.ts @@ -0,0 +1,38 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-evenly; + background: #fff; + height: 64px; + padding: 0px 40px 0px 40px; + + section { + display: flex; + align-items: center; + justify-content: center; + label { + margin-left: 28px; + } + } + + #brand { + color: var(--secondary); + font-weight: 500; + } + + #menu-button { + width: 20%; + margin-left: 20px; + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-evenly; + li { + display: flex; + align-items: center; + } + } +`; diff --git a/src/components/NewRepositoryForm/index.tsx b/src/components/NewRepositoryForm/index.tsx new file mode 100644 index 0000000..92de6cf --- /dev/null +++ b/src/components/NewRepositoryForm/index.tsx @@ -0,0 +1,76 @@ +import React, { FormEvent, useState } from 'react'; + +import { Button, Container, Content } from './styles'; +import ClayForm, { ClayInput } from '@clayui/form'; +import { FiPlus } from 'react-icons/fi'; +import { useDispatch, useSelector } from 'react-redux'; +import ClayLoadingIndicator from '@clayui/loading-indicator'; +import * as Actions from '../../redux/action/repository'; +import { AiFillInfoCircle } from 'react-icons/ai'; + +const NewRepositoryForm: React.FC = () => { + const dispatch = useDispatch(); + const [term, setTerm] = useState(''); + const [openDropdown, setOpenDropdown] = useState(false); + const { loading, error } = useSelector((state: State) => state.repository); + + const onSubmitForm = (e: FormEvent) => { + e.preventDefault(); + if (term.length === 0) { + return false; + } + if (term.split('/').length > 1) { + dispatch(Actions.getRepository(term)); + setTerm(''); + return true; + } + dispatch(Actions.getAllUserRepositories(term)); + setTerm(''); + return true; + }; + return ( + + + {openDropdown && ( + + +

New repository

+ + setTerm(e.target.value)} + /> + {error && ( +

+ + This is an API-feedback-error +

+ )} +
+
+
+ + +
+
+ )} +
+ ); +}; + +export default NewRepositoryForm; diff --git a/src/components/NewRepositoryForm/styles.ts b/src/components/NewRepositoryForm/styles.ts new file mode 100644 index 0000000..6947e53 --- /dev/null +++ b/src/components/NewRepositoryForm/styles.ts @@ -0,0 +1,68 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + position: relative; + display: flex; + flex-direction: column; +`; + +export const Button = styled.button` + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--primary); + border: 0; + border-radius: 4px; + color: #fff; +`; + +export const Content = styled.form` + h4 { + font-size: 18px; + } + @keyframes FadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + border-radius: 4px; + background: #fff; + width: 448px; + position: absolute; + top: 40px; + right: 0px; + z-index: 9999; + box-shadow: 0 1px 5px -1px rgb(0 0 0 / 30%); + animation-name: FadeIn; + animation-duration: 300ms; + + .input-group { + padding: 9px 24px; + margin-top: 16px; + display: flex; + flex-direction: column; + + label { + color: var(--dark); + font-weight: 400; + span { + color: var(--warning); + } + } + } + footer { + padding: 9px 24px; + display: flex; + align-items: center; + justify-content: flex-end; + button + button { + margin-left: 16px; + } + } +`; diff --git a/src/components/RepositoryList/index.tsx b/src/components/RepositoryList/index.tsx new file mode 100644 index 0000000..9643c23 --- /dev/null +++ b/src/components/RepositoryList/index.tsx @@ -0,0 +1,65 @@ +import React from 'react'; + +import { useSelector } from 'react-redux'; +import { Container } from './styles'; +import Card from '../Card'; +import CardRow from '../CardRow'; +import EmptyList from '../EmptyList'; +const RepositoryList: React.FC = () => { + const listType = useSelector((state: State) => state.repository.listType); + const repository = useSelector( + (state: State) => state.repository.filter.data, + ); + + const renderGridList = () => { + return ( + + {repository.map(repo => ( + + ))} + + ); + }; + + const renderRowList = () => { + return ( + + {repository.map(repo => ( + + ))} + + ); + }; + + if (repository.length === 0) { + return ; + } + + return listType === 'grid' ? renderGridList() : renderRowList(); +}; + +export default RepositoryList; diff --git a/src/components/RepositoryList/styles.ts b/src/components/RepositoryList/styles.ts new file mode 100644 index 0000000..3f87d34 --- /dev/null +++ b/src/components/RepositoryList/styles.ts @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + width: 100%; +`; diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx new file mode 100644 index 0000000..96d4743 --- /dev/null +++ b/src/components/SearchBar/index.tsx @@ -0,0 +1,55 @@ +import React, { useState, useCallback } from 'react'; + +import { Container, Button } from './style'; +import { ClayInput } from '@clayui/form'; + +import ClayManagementToolbar from '@clayui/management-toolbar'; +import { FiSearch } from 'react-icons/fi'; +import debounce from 'lodash.debounce'; +import * as Actions from '../../redux/action/repository'; +import { useDispatch } from 'react-redux'; +const SearchBar: React.FC = () => { + const dispatch = useDispatch(); + const [term, setTerm] = useState(''); + + const debounceSearch = useCallback( + debounce(value => { + dispatch(Actions.search(value)); + }, 200), + [], + ); + + function onChangeTerm(value: string) { + setTerm(value); + debounceSearch(value); + } + return ( + + + + + + + onChangeTerm(e.target.value)} + value={term} + /> + + + + + + + + + + ); +}; + +export default SearchBar; diff --git a/src/components/SearchBar/style.ts b/src/components/SearchBar/style.ts new file mode 100644 index 0000000..6bab292 --- /dev/null +++ b/src/components/SearchBar/style.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + max-width: 419px; +`; + +export const Button = styled.button` + border: 0; + padding: 0px 12px 0px 12px; + display: flex; + height: 100%; + background-color: transparent; + align-items: center; + justify-content: center; +`; diff --git a/src/components/StarFilter/index.tsx b/src/components/StarFilter/index.tsx new file mode 100644 index 0000000..abe2a92 --- /dev/null +++ b/src/components/StarFilter/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { Button } from '../commons/GlobalComponents'; +import { MdStar, MdStarBorder } from 'react-icons/md'; +import { useSelector, useDispatch } from 'react-redux'; +import * as Actions from '../../redux/action/repository'; + +const StarFilter: React.FC = () => { + const dispatch = useDispatch(); + const filter = useSelector((state: State) => state.repository.filter); + return ( + + ); +}; + +export default StarFilter; diff --git a/src/components/commons/GlobalComponents.ts b/src/components/commons/GlobalComponents.ts new file mode 100644 index 0000000..854d1d6 --- /dev/null +++ b/src/components/commons/GlobalComponents.ts @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +export const Button = styled.button` + height: 32px; + width: 32px; + display: flex; + border: 1px solid #fff; + align-items: center; + justify-content: center; + background-color: transparent; + transition: 200ms all ease; + border-radius: 4px; + color: var(--secondary); + :hover { + background: #f1f2f5; + border: 1px solid #cdced9; + } +`; diff --git a/src/index.tsx b/src/index.tsx index c1f31c5..e978020 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,5 +6,5 @@ ReactDOM.render( , - document.getElementById('root') + document.getElementById('root'), ); diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index b105224..914fafa 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,9 +1,17 @@ import React from 'react'; -// import { Container } from './styles'; - +import { Container, Content } from './styles'; +import NavBar from '../../components/NavBar'; +import RepositoryList from '../../components/RepositoryList'; const Home: React.FC = () => { - return

Git-Hub compare

; + return ( + + + + + + + ); }; export default Home; diff --git a/src/pages/Home/styles.ts b/src/pages/Home/styles.ts new file mode 100644 index 0000000..3b75953 --- /dev/null +++ b/src/pages/Home/styles.ts @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + width: 100%; + position: relative; + height: 100vh; +`; + +export const Content = styled.div` + display: flex; + height: 100%; + margin-top: 1px; + height: 100%; + padding: 30px 64px; + overflow-y: auto; +`; diff --git a/src/redux/action/repository.ts b/src/redux/action/repository.ts new file mode 100644 index 0000000..4d049a9 --- /dev/null +++ b/src/redux/action/repository.ts @@ -0,0 +1,103 @@ +import Redux from 'redux'; +import * as Api from '../../api/repository'; +const TEMPLATE_NAME = 'REPOSITORY'; + +export const getRepository = (term: string) => { + return async (dispatch: Redux.Dispatch): Promise => { + try { + dispatch({ + type: `${TEMPLATE_NAME}_PENDING`, + }); + const response = await Api.getRepository(term); + dispatch({ + type: `${TEMPLATE_NAME}_SUCCESS`, + payload: response, + }); + } catch (err) { + dispatch({ + type: `${TEMPLATE_NAME}_REJECTED`, + payload: err, + }); + } + }; +}; + +export const getAllUserRepositories = (term: string) => { + return async (dispatch: Redux.Dispatch): Promise => { + try { + dispatch({ + type: `${TEMPLATE_NAME}_PENDING`, + }); + const response = await Api.getAllUserRepositories(term); + dispatch({ + type: `${TEMPLATE_NAME}_FULFILLED`, + payload: response, + }); + } catch (err) { + dispatch({ + type: `${TEMPLATE_NAME}_REJECTED`, + payload: err, + }); + } + }; +}; + +interface SearchAction { + type: string; + payload: string; +} +export const search = (term: string): SearchAction => { + return { + type: `${TEMPLATE_NAME}_SEARCH`, + payload: term, + }; +}; + +interface FilterStarAction { + type: string; + payload: boolean; +} +export const filterByStar = (shouldFilterByStar: boolean): FilterStarAction => { + return { + type: `${TEMPLATE_NAME}_FILTER_STARRED`, + payload: shouldFilterByStar, + }; +}; + +export const sort = (sortBy: Sort): FilterActionSort => { + return { + type: `${TEMPLATE_NAME}_SORT`, + payload: sortBy, + }; +}; + +interface DeleteAction { + type: string; + payload: number; +} + +type ToggleStarAction = DeleteAction; +export const deleteRepository = (id: number): DeleteAction => { + return { + type: `${TEMPLATE_NAME}_REMOVE_REPOSITORY`, + payload: id, + }; +}; + +export const toggleFavortiteRepository = (id: number): ToggleStarAction => { + return { + type: `${TEMPLATE_NAME}_TOGGLE_FAVORITE_REPOSITORY`, + payload: id, + }; +}; + +interface ListTypeAction { + type: string; + payload: ListType; +} +export const listType = (type: ListType): ListTypeAction => { + return { + type: `${TEMPLATE_NAME}_CHANGE_LIST_TYPE`, + payload: type, + }; +}; diff --git a/src/redux/reducer/index.ts b/src/redux/reducer/index.ts new file mode 100644 index 0000000..6b83024 --- /dev/null +++ b/src/redux/reducer/index.ts @@ -0,0 +1,6 @@ +import { combineReducers } from 'redux'; +import repository from './repository'; + +export default combineReducers({ + repository, +}); diff --git a/src/redux/reducer/repository.ts b/src/redux/reducer/repository.ts new file mode 100644 index 0000000..f8cfe7e --- /dev/null +++ b/src/redux/reducer/repository.ts @@ -0,0 +1,199 @@ +import sort from '../../services/sort'; +const TEMPLATE_NAME = 'REPOSITORY'; + +const initialState = (): IRepositoryState => ({ + data: [], + loading: false, + error: false, + filter: { + name: '', + data: [], + starred: false, + sortBy: '', + }, + listType: 'grid', +}); + +interface ActionGetRepositorySuccess { + type: string; + payload: IRepository; +} + +interface ActionListRepositoriesSuccess { + type: string; + payload: IRepository[]; +} + +interface SearchAction { + type: string; + payload: string; +} +interface DeleteAction { + type: string; + payload: number; +} +interface FilterStarAction { + type: string; + payload: boolean; +} + +type Action = + | ActionGetRepositorySuccess + | ActionListRepositoriesSuccess + | FilterActionSort + | FilterStarAction + | SearchAction + | DeleteAction; + +const applyFilter = (arr: IRepository[], filter: Sort) => { + if (filter) { + const sortFunc = sort[filter]; + const filteredArray = sortFunc(arr); + return filteredArray; + } + return arr; +}; + +const applySearch = (term: string, state: IRepositoryState) => { + const data = applyFilter(state.data, state.filter.sortBy); + if (term) { + const searchedTerm = data.filter(repository => + repository.full_name.includes(term), + ); + return searchedTerm; + } + return data; +}; + +const reducer = (state = initialState(), action: Action): IRepositoryState => { + switch (action.type) { + case `${TEMPLATE_NAME}_PENDING`: { + return { + ...state, + loading: true, + error: false, + }; + } + case `${TEMPLATE_NAME}_REJECTED`: { + return { + ...state, + loading: false, + error: true, + }; + } + case `${TEMPLATE_NAME}_FULFILLED`: { + const data = [...(action.payload as IRepository[]), ...state.data]; + return { + ...state, + data: data, + loading: false, + filter: { + ...state.filter, + data: data, + }, + error: false, + }; + } + case `${TEMPLATE_NAME}_SUCCESS`: { + const repository = action.payload as IRepository; + const data = [...state.data]; + data.unshift(repository); + const newData = [...state.filter.data]; + newData.unshift(repository); + return { + ...state, + data: data, + loading: false, + error: false, + filter: { + ...state.filter, + data: newData, + }, + }; + } + case `${TEMPLATE_NAME}_SEARCH`: { + const term = action.payload as string; + const newData = applySearch(term, state); + return { + ...state, + filter: { + ...state.filter, + name: term, + data: newData, + }, + }; + } + case `${TEMPLATE_NAME}_FILTER_STARRED`: { + const starred = action.payload as boolean; + const data = state.data.filter(d => d.starred === starred); + const newData = applyFilter(data, state.filter.sortBy); + + return { + ...state, + filter: { + ...state.filter, + data: starred ? newData : state.data, + starred, + }, + }; + } + case `${TEMPLATE_NAME}_SORT`: { + const orderBy = action.payload as Sort; + const sortFunc = sort[orderBy]; + const newData = sortFunc([...state.filter.data]); + return { + ...state, + filter: { + ...state.filter, + data: newData, + sortBy: orderBy, + }, + }; + } + case `${TEMPLATE_NAME}_TOGGLE_FAVORITE_REPOSITORY`: { + const id = action.payload as number; + const [newFavouriteRepository] = state.filter.data.filter( + repo => repo.id === id, + ); + newFavouriteRepository.starred = !newFavouriteRepository.starred; + const newData = state.filter.data.map(repo => + repo.id === id ? newFavouriteRepository : repo, + ); + + return { + ...state, + filter: { + ...state.filter, + data: newData, + }, + }; + } + case `${TEMPLATE_NAME}_REMOVE_REPOSITORY`: { + const newData = state.data.filter( + repository => repository.id !== (action.payload as number), + ); + const newFilterData = applyFilter(newData, state.filter.sortBy); + return { + ...state, + data: newData, + filter: { + ...state.filter, + data: newFilterData, + }, + }; + } + case `${TEMPLATE_NAME}_CHANGE_LIST_TYPE`: { + return { + ...state, + listType: action.payload as ListType, + }; + } + case 'RESET': { + return initialState(); + } + default: + return state; + } +}; + +export default reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..18e8757 --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,13 @@ +import { createStore, applyMiddleware } from 'redux'; +import { createLogger } from 'redux-logger'; + +import thunk from 'redux-thunk'; +import rootReducer from './reducer'; + +const middlewares = []; +middlewares.push(thunk); +middlewares.push(createLogger({ collapsed: true, duration: true, diff: true })); + +const store = createStore(rootReducer, applyMiddleware(...middlewares)); + +export default store; diff --git a/src/services/debounce.ts b/src/services/debounce.ts new file mode 100644 index 0000000..fd21cbb --- /dev/null +++ b/src/services/debounce.ts @@ -0,0 +1,15 @@ +const debounce = any>( + func: F, + waitFor: number, +) => { + let timeout: NodeJS.Timeout; + + const debounced = (...args: any) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), waitFor); + }; + + return debounced as (...args: Parameters) => ReturnType; +}; + +export default debounce; diff --git a/src/services/sort.ts b/src/services/sort.ts new file mode 100644 index 0000000..33de8ee --- /dev/null +++ b/src/services/sort.ts @@ -0,0 +1,40 @@ +const sortStars = (arr: IRepository[]): IRepository[] => { + return arr.sort( + (current, next) => next.stargazers_count - current.stargazers_count, + ); +}; + +const sortForks = (arr: IRepository[]): IRepository[] => { + return arr.sort((next, current) => current.forks - next.forks); +}; + +const sortOpenIssues = (arr: IRepository[]): IRepository[] => { + return arr.sort((next, current) => current.open_issues - next.open_issues); +}; + +const sortAge = (arr: IRepository[]): IRepository[] => { + return arr.sort((next, current) => { + const currentDate = new Date(current.created_at); + const nextDate = new Date(next.created_at); + return nextDate.getTime() - currentDate.getTime(); + }); +}; + +const sortLastCommit = (arr: IRepository[]): IRepository[] => { + return arr.sort((next, current) => { + const currentDate = new Date(current.pushed_at); + const nextDate = new Date(next.pushed_at); + return nextDate.getTime() - currentDate.getTime(); + }); +}; + +const empySort = (arr: IRepository[]): IRepository[] => arr; +const sorts = { + stars: sortStars, + forks: sortForks, + openIssues: sortOpenIssues, + age: sortAge, + lastCommit: sortLastCommit, + '': empySort, +}; +export default sorts; diff --git a/src/styles/globalStyles.ts b/src/styles/globalStyles.ts new file mode 100644 index 0000000..f803089 --- /dev/null +++ b/src/styles/globalStyles.ts @@ -0,0 +1,64 @@ +import { createGlobalStyle } from 'styled-components'; + +export default createGlobalStyle` + * { + margin: 0; + padding: 0; + box-sizing: border-box; + outline: none; + + } + body { + background-color: #F7F8F9; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-size: 14px; + } + + ul { + list-style: none; + margin: 0; + } + h4, p { + margin: 0; + } + #root { + position: relative; + overflow-y: hidden; + overflow-x: hidden; + } + code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; + } + + #errorLabel { + color: #DA1414; + font-weight: 600; + margin-top: 7px; + span { + margin-left: 7px; + } + } + + ::-webkit-scrollbar { + width: 4px; +} +::-webkit-scrollbar-track { + background: #fff; +} +::-webkit-scrollbar-thumb { + background: #6B6C7E; +} +::-webkit-scrollbar-thumb:hover { + width: 3px; +} + + --secondary: #6B6C7E; + --primary: #0B5FFF; + --dark: #272833; + --warning: #B95000; +`; diff --git a/yarn.lock b/yarn.lock index aab628d..c048f29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,7 +80,16 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13": +"@babel/generator@^7.13.9": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== @@ -278,6 +287,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.17.tgz#bc85d2d47db38094e5bb268fc761716e7d693848" integrity sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg== +"@babel/parser@^7.13.15": + version "7.13.15" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8" + integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ== + "@babel/plugin-proposal-async-generator-functions@^7.12.1", "@babel/plugin-proposal-async-generator-functions@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.13.tgz#d1c6d841802ffb88c64a2413e311f7345b9e66b5" @@ -1098,7 +1112,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== @@ -1129,6 +1143,20 @@ globals "^11.1.0" lodash "^4.17.19" +"@babel/traverse@^7.4.5": + version "7.13.15" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.15.tgz#c38bf7679334ddd4028e8e1f7b3aa5019f0dada7" + integrity sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.15" + "@babel/types" "^7.13.14" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.12.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.12.17" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.17.tgz#9d711eb807e0934c90b8b1ca0eb1f7230d150963" @@ -1138,11 +1166,147 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.13.0", "@babel/types@^7.13.14": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@clayui/button@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@clayui/button/-/button-3.6.0.tgz#ee9b53984abe0eb72ec9889db7aa644fc41212ad" + integrity sha512-ihijKf+Tqz750fJg5A56j1y1ibePjqFLotWOFK2r9xfagsAPb0TUxGuwJhg7RjMnvhgBmoNUZ06XO1BUThX3tA== + dependencies: + "@clayui/icon" "^3.1.0" + classnames "^2.2.6" + +"@clayui/card@^3.25.1": + version "3.25.1" + resolved "https://registry.yarnpkg.com/@clayui/card/-/card-3.25.1.tgz#e42cdd1a42295795f831b2188b3098f1aa7459d6" + integrity sha512-p9onzrFE9cGfX4BvD0IgXhKgfiQ1zWWlehaTyoaEDDBBLAMf1J6J8td23oNFnRXatHqdPLA9XLNlHH89ji4Axg== + dependencies: + "@clayui/button" "^3.6.0" + "@clayui/drop-down" "^3.25.1" + "@clayui/form" "^3.14.4" + "@clayui/icon" "^3.1.0" + "@clayui/label" "^3.4.1" + "@clayui/layout" "^3.3.0" + "@clayui/link" "^3.2.0" + "@clayui/shared" "^3.5.1" + "@clayui/sticker" "^3.3.0" + classnames "^2.2.6" + +"@clayui/css@^3.25.3": + version "3.25.3" + resolved "https://registry.yarnpkg.com/@clayui/css/-/css-3.25.3.tgz#5c68e4f98ddf40664d06e8517fbdcf65493ca3d0" + integrity sha512-BWMkPn0nMpqhog3iCc9ktazoKKG3oChoNr2zzAzF9LusDGZVVo2pamQjdlAZfkfD/daAC5Bo5rlOLazHc45oKg== + +"@clayui/drop-down@^3.25.1": + version "3.25.1" + resolved "https://registry.yarnpkg.com/@clayui/drop-down/-/drop-down-3.25.1.tgz#33cf55885de9982ea10321fb96c1ade71cbcf239" + integrity sha512-teIMLEvs8CZY7oYwJuaTsUQIu7/veyqDhMqhi0oYxk0mwTkstJZEsAbyQCX50veXu11armincjxjqo/fcn3eMQ== + dependencies: + "@clayui/button" "^3.6.0" + "@clayui/form" "^3.14.4" + "@clayui/icon" "^3.1.0" + "@clayui/link" "^3.2.0" + "@clayui/shared" "^3.5.1" + classnames "^2.2.6" + dom-align "^1.10.2" + react-transition-group "^4.4.1" + warning "^4.0.3" + +"@clayui/form@^3.14.4": + version "3.14.4" + resolved "https://registry.yarnpkg.com/@clayui/form/-/form-3.14.4.tgz#50725d4647a3e1fba3fc5449429f8baec694b2df" + integrity sha512-R5pm55qw0ooH5ItepehAebUwLrJoebe1xnbA5Pkk/rbL+SzbJRtXv1igMNjvT1hUhjbSG7s2ZKKnXnVdhnmm3w== + dependencies: + "@clayui/button" "^3.6.0" + "@clayui/icon" "^3.1.0" + "@clayui/shared" "^3.5.1" + classnames "^2.2.6" + +"@clayui/icon@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@clayui/icon/-/icon-3.1.0.tgz#9e600f87194f8dacfb49ace121fbab25a2e82f03" + integrity sha512-rxFih7ktNoYx7jJMYWA5GiEh6bJ5W3mB5fWRlSAw8sVyDvSH8rIdaqR77SmvAJDTygfKuKJlyeHnhTmWZgJbCg== + dependencies: + classnames "^2.2.6" + warning "^4.0.3" + +"@clayui/label@^3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@clayui/label/-/label-3.4.1.tgz#af235c32c006e0d12d3bb3a3556706033aef9608" + integrity sha512-ReGAldU4JkNzvAO0GkmdRy6JIKVVxMF2/L2caG2RxQDVyLsWVB3uituqHCOp3PC3va4KVR0KYQJ7hb317vN15g== + dependencies: + "@clayui/icon" "^3.1.0" + "@clayui/link" "^3.2.0" + classnames "^2.2.6" + +"@clayui/layout@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@clayui/layout/-/layout-3.3.0.tgz#89b54741787b2d9efaced088e5478fd7c0cb935e" + integrity sha512-bIOt0zAyWpg0YHDVc52cMaCS75q3CkZRmvgzj/h9wq4xVbUvogesm8OD1iwHc+ILJzfkKqzgujCrDGZvzOBf+w== + dependencies: + classnames "^2.2.6" + warning "^4.0.3" + +"@clayui/link@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@clayui/link/-/link-3.2.0.tgz#1d5fc80f574f18d3880bc3584cd2d83761b6ec78" + integrity sha512-UjuEGGwE3X/XnB3Ul+JPPAveHj3d76OyRu8rwhKuw5VwyfQVadb7p8owqpohOPaD29ytljOfce8a6X0vGoXJig== + dependencies: + classnames "^2.2.6" + +"@clayui/loading-indicator@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@clayui/loading-indicator/-/loading-indicator-3.2.0.tgz#7a5565c5543b345d9e57d8b94b72b7ba83c4667f" + integrity sha512-c0R4NEa+5gITbsAgf3IxP6O4TjMzaWCT7rv3N4EFWT23lbChRFE/XCq5utXbQIM1M6b+1lYX2qoqsn9o4+63SA== + dependencies: + classnames "^2.2.6" + +"@clayui/management-toolbar@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@clayui/management-toolbar/-/management-toolbar-3.3.0.tgz#bedf513b56e5b5756e0287149c1cd90f8b8f3043" + integrity sha512-O0pAmcyWxP+j80TZtCokyPL88sfNzq9mzBHqsnKlc+nsCgMMZlxTPmR7O/T4nrA3jWaIsQH4lsRBQReCz00IHQ== + dependencies: + "@clayui/layout" "^3.3.0" + classnames "^2.2.6" + +"@clayui/modal@^3.8.5": + version "3.8.5" + resolved "https://registry.yarnpkg.com/@clayui/modal/-/modal-3.8.5.tgz#6eacec122c9ecd8af0b100d34cb30c884d33e5cd" + integrity sha512-U8o4eub5ujCUeFrVcfAgkxXv4ZX14LyUT5yYj/lDr7ihOE1LRXREnGmEWnINgP18TkUYobeQSj5F3YFnPXXnEg== + dependencies: + "@clayui/button" "^3.6.0" + "@clayui/icon" "^3.1.0" + "@clayui/shared" "^3.5.1" + classnames "^2.2.6" + warning "^4.0.3" + +"@clayui/shared@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@clayui/shared/-/shared-3.5.1.tgz#6daee67b300f346381dd126472802f0a83472c4c" + integrity sha512-p7vDmc+AcUnk7R/3+Xr+WNm7A5sUcmLbYlgK0GkXHUDwe+aVWwWYH1/mBL9aHCt9RFKc6WyM3zZv8cvOzlhFLA== + dependencies: + "@clayui/button" "^3.6.0" + "@clayui/link" "^3.2.0" + +"@clayui/sticker@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@clayui/sticker/-/sticker-3.3.0.tgz#77fa3606688050bf9e731b49f319320c3718f00b" + integrity sha512-Qmu3jarLiyeQmTZYikIAk9g/npl8bPxN1xhaMWP4C95yUZJgCbvqwzDCSEPHYCR+QiedKizBVKG9kQMoqdmcPA== + dependencies: + classnames "^2.2.6" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1161,6 +1325,28 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@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== + "@eslint/eslintrc@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" @@ -1633,7 +1819,7 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^11.1.0": +"@testing-library/react@^11.2.6": version "11.2.6" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ== @@ -1724,6 +1910,14 @@ dependencies: "@types/node" "*" +"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/html-minifier-terser@^5.0.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" @@ -1748,7 +1942,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^26.0.15": +"@types/jest@*", "@types/jest@^26.0.22": version "26.0.22" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== @@ -1766,20 +1960,37 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash.debounce@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60" + integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/mocha@^8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" + integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== + "@types/node@*": version "14.14.31" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== -"@types/node@^12.0.0": - version "12.20.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.7.tgz#1cb61fd0c85cb87e728c43107b5fd82b69bc9ef8" - integrity sha512-gWL8VUkg8VRaCAUgG9WmhefMqHmMblxe2rVpMF86nZY/+ZysU+BkAp+3cz03AixWDSSz0ks5WX59yAhv/cDwFA== +"@types/node@^14.14.37": + version "14.14.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" + integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1813,6 +2024,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.16": + version "7.1.16" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21" + integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react@*", "@types/react@^17.0.0": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79" @@ -1822,6 +2043,20 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/redux-logger@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.8.tgz#1fb6d26917bb198792bb1cf57feb31cae1532c5d" + integrity sha512-zM+cxiSw6nZtRbxpVp9SE3x/X77Z7e7YAfHD1NkxJyJbAGSXJGF0E9aqajZfPOa/sTYnuwutmlCldveExuCeLw== + dependencies: + redux "^4.0.0" + +"@types/redux-mock-store@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz#c27d5deadfb29d8514bdb0fc2cadae6feea1922d" + integrity sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw== + dependencies: + redux "^4.0.5" + "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -1844,6 +2079,15 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/styled-components@^5.1.9": + version "5.1.9" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.9.tgz#00d3d84b501420521c4db727e3c195459f87a6cf" + integrity sha512-kbEG6YlwK8rucITpKEr6pA4Ho9KSQHUUOzZ9lY3va1mtcjvS3D0wDciFyHEiNHKLL/npZCKDQJqm0x44sPO9oA== + dependencies: + "@types/hoist-non-react-statics" "*" + "@types/react" "*" + csstype "^3.0.2" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -2269,6 +2513,11 @@ acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +add@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235" + integrity sha1-JI8Kn25aUo7yKV2+7DBTITCuIjU= + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -2397,6 +2646,11 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -2609,6 +2863,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -2700,6 +2961,21 @@ babel-plugin-named-asset-import@^0.3.7: resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" integrity sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw== +"babel-plugin-styled-components@>= 1.12.0": + version "1.12.0" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz#1dec1676512177de6b827211e9eda5a30db4f9b9" + integrity sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA== + 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" @@ -3176,6 +3452,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +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" @@ -3322,6 +3603,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.6: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" @@ -3665,6 +3951,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -3714,6 +4005,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" @@ -3775,6 +4071,15 @@ css-select@^2.0.0, css-select@^2.0.2: 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.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -4007,6 +4312,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -4125,6 +4435,11 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -4188,6 +4503,11 @@ dom-accessibility-api@^0.5.4: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== +dom-align@^1.10.2: + version "1.12.0" + resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.0.tgz#56fb7156df0b91099830364d2d48f88963f5a29c" + integrity sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -4195,6 +4515,14 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5230,6 +5558,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5642,6 +5975,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + 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" + hoopy@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" @@ -7118,6 +7458,16 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7158,7 +7508,7 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7213,6 +7563,11 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -7468,6 +7823,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -9000,7 +9360,7 @@ prompts@2.4.0, prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2: +prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9220,7 +9580,12 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== -react-is@^16.8.1: +react-icons@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0" + integrity sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ== + +react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9230,6 +9595,18 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-redux@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.3.tgz#4c084618600bb199012687da9e42123cca3f0be9" + integrity sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/react-redux" "^7.1.16" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -9301,6 +9678,16 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" +react-transition-group@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -9398,6 +9785,33 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + +redux-mock-store@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872" + integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA== + dependencies: + lodash.isplainobject "^4.0.6" + +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.0, 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" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -9991,6 +10405,11 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.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" @@ -10147,7 +10566,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: +source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -10494,6 +10913,22 @@ style-loader@1.3.0: loader-utils "^2.0.0" schema-utils "^2.7.0" +styled-components@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.3.tgz#752669fd694aac10de814d96efc287dde0d11385" + integrity sha512-BlR+KrLW3NL1yhvEB+9Nu9Dt51CuOnHoxd+Hj+rYPdtyR8X11uIW9rvhpy3Dk4dXXBsiW1u5U78f00Lf/afGoA== + 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.12.0" + 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" @@ -10503,7 +10938,7 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -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== @@ -10556,6 +10991,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.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -10783,6 +11223,18 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-node@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-pnp@1.2.0, ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -11174,6 +11626,13 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack-chokidar2@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" @@ -11679,6 +12138,16 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yarn@^1.22.10: + version "1.22.10" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.10.tgz#c99daa06257c80f8fa2c3f1490724e394c26b18c" + integrity sha512-IanQGI9RRPAN87VGTF7zs2uxkSyQSrSPsju0COgbsKQOOXr5LtcVPeyXWgwVa0ywG3d8dg6kSYKGBuYK021qeA== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"