├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── src
├── assets
│ ├── picture.png
│ ├── microverse.png
│ └── screenshot.png
├── reducers
│ ├── index.js
│ ├── filter.js
│ └── pokes.js
├── setupTests.js
├── store
│ └── store.js
├── index.js
├── tests
│ ├── components
│ │ ├── PokemonFilter.test.js
│ │ ├── Pokemon.test.js
│ │ ├── PokemonCard.test.js
│ │ └── PokemonList.test.js
│ ├── actions
│ │ └── index.test.js
│ └── api
│ │ └── pokemonApi.test.js
├── styles
│ ├── index.css
│ └── reset.css
├── components
│ ├── App.js
│ ├── PokemonMain.js
│ ├── PokemonFilter.js
│ └── Pokemon.js
├── actions
│ └── index.js
├── api
│ └── pokemonApi.js
└── containers
│ ├── PokemonCard.js
│ └── PokemonsList.js
├── .stylelintrc.json
├── .gitignore
├── .eslintrc.json
├── LICENSE
├── .github
└── workflows
│ └── linters.yml
├── package.json
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/assets/picture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/src/assets/picture.png
--------------------------------------------------------------------------------
/src/assets/microverse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/src/assets/microverse.png
--------------------------------------------------------------------------------
/src/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rammazzoti2000/react_capstone/HEAD/src/assets/screenshot.png
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { pokemonsReducer } from './pokes';
3 | import { filterType } from './filter';
4 |
5 | const rootReducer = combineReducers({
6 | data: pokemonsReducer,
7 | filter: filterType,
8 | });
9 |
10 | export default rootReducer;
11 |
--------------------------------------------------------------------------------
/src/reducers/filter.js:
--------------------------------------------------------------------------------
1 | const FILTER_TYPE = 'FILTER_TYPE';
2 | export const filterType = (state = 'normal', action) => {
3 | switch (action.type) {
4 | case FILTER_TYPE:
5 | return action.category;
6 | default:
7 | return state;
8 | }
9 | };
10 |
11 | export const getPokemonType = category => category;
12 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"],
3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"],
4 | "rules": {
5 | "at-rule-no-unknown": null,
6 | "scss/at-rule-no-unknown": true,
7 | "csstree/validator": true
8 | },
9 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css"]
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | /* eslint-disable import/no-extraneous-dependencies */
6 | import '@testing-library/jest-dom/extend-expect';
7 | import { configure } from 'enzyme';
8 | import Adapter from 'enzyme-adapter-react-16';
9 |
10 | configure({ adapter: new Adapter() });
11 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers/index';
4 |
5 | const middleware = [thunk];
6 |
7 | const initialState = {
8 | pending: true,
9 | pendingPokemon: true,
10 | pokemons: [],
11 | error: null,
12 | };
13 |
14 | const store = createStore(rootReducer, {
15 | data: initialState,
16 | filter: 'normal',
17 | }, applyMiddleware(...middleware));
18 |
19 | export default store;
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import App from './components/App';
5 | import store from './store/store';
6 | import './styles/index.css';
7 | import './styles/reset.css';
8 | import 'bootstrap/dist/css/bootstrap.min.css';
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | ,
16 | document.getElementById('root'),
17 | );
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true
6 | },
7 | "parserOptions": {
8 | "ecmaFeatures": {
9 | "jsx": true
10 | },
11 | "ecmaVersion": 2018,
12 | "sourceType": "module"
13 | },
14 | "extends": ["airbnb", "plugin:react/recommended"],
15 | "plugins": ["react"],
16 | "rules": {
17 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }],
18 | "import/no-unresolved": "off",
19 | "no-shadow": "off",
20 | "arrow-parens": ["error", "as-needed"]
21 | },
22 | "ignorePatterns": [
23 | "dist/",
24 | "build/"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/tests/components/PokemonFilter.test.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 | import PokemonFilter from '../../components/PokemonFilter';
4 |
5 | describe('Fylter Type', () => {
6 | let wrapper;
7 | const mockOnChangeFn = jest.fn();
8 | beforeEach(() => {
9 | wrapper = shallow( );
10 | });
11 |
12 | it('onChange calls onClick handler ', () => {
13 | wrapper.find('select').simulate('change');
14 | expect(mockOnChangeFn.mock.calls.length).toBe(1);
15 | });
16 |
17 | it('renders 18 filter types', () => {
18 | expect(wrapper.find('option')).toHaveLength(18);
19 | });
20 |
21 | it('Select category as default', () => {
22 | expect(wrapper.find('option').at(0).text()).toBe('Select category');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family:
4 | -apple-system,
5 | BlinkMacSystemFont,
6 | 'Segoe UI',
7 | 'Roboto',
8 | 'Oxygen',
9 | 'Ubuntu',
10 | 'Cantarell',
11 | 'Fira Sans',
12 | 'Droid Sans',
13 | 'Helvetica Neue',
14 | sans-serif;
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | width: 100%;
18 | background-color: rgb(220, 220, 220) !important;
19 | }
20 |
21 | code {
22 | font-family:
23 | source-code-pro,
24 | Menlo,
25 | Monaco,
26 | Consolas,
27 | 'Courier New',
28 | monospace;
29 | }
30 |
31 | #container-list {
32 | margin-bottom: 20px;
33 | }
34 |
35 | #inner-list {
36 | width: 18em;
37 | height: 14em;
38 | background-color: white;
39 | box-shadow: 2px 2px 50px rgba(0, 0, 0, 0.2);
40 | border-radius: 5px;
41 | margin: auto;
42 | }
43 |
44 | .pokemon-pic {
45 | width: 135px;
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-named-as-default */
2 | import React from 'react';
3 | import {
4 | BrowserRouter as Router, Route, Switch, Link,
5 | } from 'react-router-dom';
6 | import PokemonList from '../containers/PokemonsList';
7 | import PokemonCard from '../containers/PokemonCard';
8 |
9 | const App = () => (
10 |
11 |
12 |
13 |
18 |
19 | {' '}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Alexandru Bangau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/PokemonMain.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 | import picture from '../assets/picture.png';
5 |
6 | const generateID = () => Math.floor((Math.random() * 10000) + 1);
7 |
8 | const PokemonMain = ({ pokemon }) => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{pokemon.name}
16 |
17 |
18 |
19 | );
20 |
21 | PokemonMain.defaultProps = {
22 | pokemon: {
23 | name: 'pikachu',
24 | },
25 | };
26 |
27 | PokemonMain.propTypes = {
28 | pokemon: PropTypes.shape({
29 | name: PropTypes.string,
30 | image: PropTypes.string,
31 | }),
32 | };
33 |
34 | export default PokemonMain;
35 |
--------------------------------------------------------------------------------
/src/components/PokemonFilter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const PokemonFilter = props => {
5 | const pokemonCategories = [
6 | 'normal',
7 | 'fire',
8 | 'water',
9 | 'grass',
10 | 'electric',
11 | 'ice',
12 | 'fighting',
13 | 'poison',
14 | 'ground',
15 | 'psychic',
16 | 'bug',
17 | 'rock',
18 | 'ghost',
19 | 'dark',
20 | 'dragon',
21 | 'steel',
22 | 'fairy',
23 | ];
24 | const { onClick, category } = props;
25 | return (
26 |
27 |
28 | Pokemon Type:
29 |
30 | onClick(e)}>
31 | Select category
32 | {pokemonCategories.map(type => ({type} ))}
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | PokemonFilter.defaultProps = {
41 | category: 'normal',
42 | };
43 |
44 | PokemonFilter.propTypes = {
45 | onClick: PropTypes.func.isRequired,
46 | category: PropTypes.string,
47 | };
48 |
49 | export default PokemonFilter;
50 |
--------------------------------------------------------------------------------
/.github/workflows/linters.yml:
--------------------------------------------------------------------------------
1 | name: Linters
2 |
3 | on: pull_request
4 |
5 | env:
6 | FORCE_COLOR: 1
7 |
8 | jobs:
9 | eslint:
10 | name: ESLint
11 | runs-on: ubuntu-18.04
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: "12.x"
17 | - name: Setup ESLint
18 | run: |
19 | npm install --save-dev eslint@6.8.x eslint-config-airbnb@18.1.x eslint-plugin-import@2.20.x eslint-plugin-jsx-a11y@6.2.x eslint-plugin-react@7.20.x eslint-plugin-react-hooks@2.5.x
20 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.eslintrc.json
21 | - name: ESLint Report
22 | run: npx eslint .
23 | stylelint:
24 | name: Stylelint
25 | runs-on: ubuntu-18.04
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: actions/setup-node@v1
29 | with:
30 | node-version: "12.x"
31 | - name: Setup Stylelint
32 | run: |
33 | npm install --save-dev stylelint@13.3.x stylelint-scss@3.17.x stylelint-config-standard@20.0.x stylelint-csstree-validator
34 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.stylelintrc.json
35 | - name: Stylelint Report
36 | run: npx stylelint "**/*.{css,scss}"
37 |
--------------------------------------------------------------------------------
/src/tests/components/Pokemon.test.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'enzyme';
2 | import React from 'react';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import Pokemon from '../../components/Pokemon';
5 |
6 | describe('itemDetailedView test', () => {
7 | let wrapper;
8 | const pokemon = {
9 | name: 'charizard',
10 | abilities: [
11 | {
12 | ability: {
13 | name: 'solar-power',
14 | url: 'https://pokeapi.co/api/v2/ability/94/',
15 | },
16 | is_hidden: true,
17 | slot: 3,
18 | },
19 | ],
20 | sprites: {
21 | front_default: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/6.png',
22 | },
23 | stats: [
24 | {
25 | base_stat: 100,
26 | effort: 0,
27 | stat: {
28 | name: 'speed',
29 | url: 'https://pokeapi.co/api/v2/stat/6/',
30 | },
31 | },
32 | ],
33 | };
34 |
35 | beforeEach(() => {
36 | wrapper = mount(
37 |
38 |
39 | ,
40 | );
41 | });
42 |
43 | it('renders pokemon image', () => {
44 | expect(wrapper.find({ alt: pokemon.name })).toHaveLength(1);
45 | });
46 |
47 | it('renders pokemon abilities', () => {
48 | expect(wrapper.find('.abilities')).toHaveLength(1);
49 | });
50 |
51 | it('renders pokemon stats', () => {
52 | expect(wrapper.find('.stats')).toHaveLength(1);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/reducers/pokes.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_POKEMONS_PENDING,
3 | GET_POKEMONS_SUCCESS,
4 | GET_POKEMONS_ERROR,
5 | GET_SINGLE_POKEMON_ERROR,
6 | GET_SINGLE_POKEMON_PENDING,
7 | GET_SINGLE_POKEMON_SUCCESS,
8 | } from '../actions/index';
9 |
10 | export const pokemonsReducer = (state = {}, action) => {
11 | switch (action.type) {
12 | case GET_POKEMONS_PENDING:
13 | return {
14 | ...state,
15 | pending: true,
16 | };
17 | case GET_POKEMONS_SUCCESS:
18 | return {
19 | ...state,
20 | pending: false,
21 | pokemons: action.pokemons,
22 | };
23 | case GET_POKEMONS_ERROR:
24 | return {
25 | ...state,
26 | pending: false,
27 | error: action.error,
28 | };
29 | case GET_SINGLE_POKEMON_PENDING:
30 | return {
31 | ...state,
32 | pendingPokemon: true,
33 | };
34 | case GET_SINGLE_POKEMON_SUCCESS:
35 | return {
36 | ...state,
37 | pendingPokemon: false,
38 | pokemons: [action.pokemons],
39 | };
40 | case GET_SINGLE_POKEMON_ERROR:
41 | return {
42 | ...state,
43 | pendingPokemon: false,
44 | error: action.error,
45 | };
46 | default:
47 | return state;
48 | }
49 | };
50 |
51 | export const getPokemons = state => state.pokemons;
52 | export const getPokemonsPending = state => state.pending;
53 | export const getPokemonPending = state => state.pendingPokemon;
54 | export const getPokemonsError = state => state.error;
55 |
--------------------------------------------------------------------------------
/src/tests/actions/index.test.js:
--------------------------------------------------------------------------------
1 | import configureMockStore from 'redux-mock-store';
2 | import thunk from 'redux-thunk';
3 | import fetchMock from 'fetch-mock';
4 | import * as actions from '../../actions/index';
5 | import pokeApi from '../../api/pokemonApi';
6 |
7 | const middlewares = [thunk];
8 | const mockStore = configureMockStore(middlewares);
9 |
10 | describe('Actions', () => {
11 | afterEach(() => {
12 | fetchMock.restore();
13 | });
14 |
15 | it('creates FETCH_POKEMONS_SUCCESS when fetching pokemons has been done', () => {
16 | fetchMock.get('https://pokeapi.co/api/v2/type/normal', {
17 | pokemons: [
18 | {
19 | pokemon: {
20 | name: 'pidgey',
21 | url: 'https://pokeapi.co/api/v2/pokemon/16/',
22 | },
23 | slot: 1,
24 | },
25 | ],
26 | },
27 | { delay: 1000 });
28 |
29 | const expectedActions = [
30 | {
31 | type: actions.GET_POKEMONS_PENDING,
32 | },
33 | {
34 | type: actions.GET_POKEMONS_SUCCESS,
35 | pokemons: [],
36 | },
37 | {
38 | type: actions.FILTER_TYPE,
39 | category: 'normal',
40 | },
41 | ];
42 | const store = mockStore({
43 | data: {
44 | pokemons: [],
45 | pending: true,
46 | error: null,
47 | },
48 | filter: 'normal',
49 | });
50 |
51 | return store.dispatch(pokeApi.fetchPokemons('normal')).then(() => {
52 | expect(store.getActions()).not.toBe(expectedActions);
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | const GET_POKEMONS_PENDING = 'GET_POKEMONS_PENDING';
2 | const GET_POKEMONS_SUCCESS = 'GET_POKEMONS_SUCCESS';
3 | const GET_POKEMONS_ERROR = 'GET_POKEMONS_ERROR';
4 | const GET_SINGLE_POKEMON_PENDING = 'GET_SINGLE_POKEMON_PENDING';
5 | const GET_SINGLE_POKEMON_SUCCESS = 'GET_SINGLE_POKEMON_SUCCESS';
6 | const GET_SINGLE_POKEMON_ERROR = 'GET_SINGLE_POKEMON_ERROR';
7 | const FILTER_TYPE = 'FILTER_TYPE';
8 |
9 | const fetchPokemonsPending = () => ({
10 | type: GET_POKEMONS_PENDING,
11 | });
12 |
13 | const fetchPokemonsSuccess = data => ({
14 | type: GET_POKEMONS_SUCCESS,
15 | pokemons: data,
16 | });
17 |
18 | const fetchPokemonsError = error => ({
19 | type: GET_POKEMONS_ERROR,
20 | error,
21 | });
22 |
23 | const fetchSinglePokemonPending = () => ({
24 | type: GET_SINGLE_POKEMON_PENDING,
25 | });
26 |
27 | const fetchSinglePokemonSuccess = pokemon => ({
28 | type: GET_SINGLE_POKEMON_SUCCESS,
29 | pokemons: pokemon,
30 | });
31 |
32 | const fetchSinglePokemonError = error => ({
33 | type: GET_SINGLE_POKEMON_ERROR,
34 | error,
35 | });
36 |
37 | const changeType = type => ({
38 | type: FILTER_TYPE,
39 | category: type,
40 | });
41 |
42 | export {
43 | GET_POKEMONS_PENDING,
44 | GET_POKEMONS_SUCCESS,
45 | GET_POKEMONS_ERROR,
46 | GET_SINGLE_POKEMON_ERROR,
47 | GET_SINGLE_POKEMON_PENDING,
48 | GET_SINGLE_POKEMON_SUCCESS,
49 | FILTER_TYPE,
50 | fetchPokemonsPending,
51 | fetchPokemonsSuccess,
52 | fetchPokemonsError,
53 | fetchSinglePokemonError,
54 | fetchSinglePokemonPending,
55 | fetchSinglePokemonSuccess,
56 | changeType,
57 | };
58 |
--------------------------------------------------------------------------------
/src/api/pokemonApi.js:
--------------------------------------------------------------------------------
1 | import {
2 | fetchPokemonsPending,
3 | fetchPokemonsSuccess,
4 | fetchPokemonsError,
5 | fetchSinglePokemonError,
6 | fetchSinglePokemonPending,
7 | fetchSinglePokemonSuccess,
8 | changeType,
9 | } from '../actions/index';
10 |
11 | const pokemonsType = async type => {
12 | const response = await fetch(`https://pokeapi.co/api/v2/type/${type}`);
13 |
14 | if (response.ok) return response.json();
15 |
16 | throw new Error(response.status);
17 | };
18 |
19 | const pokemonProps = async name => {
20 | const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
21 |
22 | if (response.ok) return response.json();
23 |
24 | throw new Error(response.status);
25 | };
26 |
27 | const fetchPokemons = type => async dispatch => {
28 | dispatch(fetchPokemonsPending());
29 | try {
30 | const response = await pokemonsType(type);
31 | const pokemons = response.pokemon.map(async pok => {
32 | const res = await fetch(pok.pokemon.url);
33 | return res.json();
34 | });
35 | const pokemonsData = await Promise.all(pokemons);
36 | const payload = pokemonsData.map(data => ({
37 | name: data.name,
38 | image: data.sprites.front_default,
39 | }));
40 | dispatch(fetchPokemonsSuccess(payload));
41 | dispatch(changeType(type));
42 | return response;
43 | } catch (e) {
44 | dispatch(fetchPokemonsError(e));
45 | return e;
46 | }
47 | };
48 |
49 | const fetchPokemon = name => async dispatch => {
50 | dispatch(fetchSinglePokemonPending());
51 | try {
52 | const response = await pokemonProps(name);
53 | const pokemon = {
54 | name: response.name,
55 | abilities: response.abilities,
56 | sprites: response.sprites,
57 | stats: response.stats,
58 | };
59 | dispatch(fetchSinglePokemonSuccess(pokemon));
60 | return pokemon;
61 | } catch (e) {
62 | dispatch(fetchSinglePokemonError(e));
63 | return e;
64 | }
65 | };
66 |
67 | export default {
68 | fetchPokemons,
69 | fetchPokemon,
70 | };
71 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | React App
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/tests/components/PokemonCard.test.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'enzyme';
2 | import React from 'react';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import { PokemonCard } from '../../containers/PokemonCard';
5 | import store from '../../store/store';
6 |
7 | describe('Detailed View testing', () => {
8 | let wrapper;
9 | const mockFetchPokemonFn = jest.fn();
10 | let data = {
11 | error: null,
12 | pending: true,
13 | pokemons: [],
14 | };
15 | beforeEach(() => {
16 | wrapper = mount(
17 |
18 |
19 | ,
20 | );
21 | });
22 |
23 | it('should call the mock fetch pokemon function to populate data', () => {
24 | expect(mockFetchPokemonFn.mock.calls.length).toBe(1);
25 | });
26 |
27 | it('renders error when fetching fails', () => {
28 | data = {
29 | error: 'Not Found',
30 | };
31 | const wrapper = mount(
32 |
33 |
34 | ,
35 | );
36 | expect(wrapper.find('.error').text()).toBe('Not Found');
37 | });
38 |
39 | it('renders spinner while waiting for data', () => {
40 | data = {
41 | pending: true,
42 | };
43 | const wrapper = mount(
44 |
45 |
46 | ,
47 | );
48 | expect(wrapper.find('.spinner-grow')).toHaveLength(1);
49 | });
50 |
51 | it('renders single pokemon data', () => {
52 | data = {
53 | error: null,
54 | pending: false,
55 | pokemons: [{
56 | name: 'charizard',
57 | sprites: { front_default: '' },
58 | abilities: [],
59 | stats: [],
60 | }],
61 | };
62 |
63 | wrapper = mount(
64 |
65 |
66 | ,
67 | );
68 |
69 | expect(wrapper.find('h1').text()).toBe('charizard');
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/src/containers/PokemonCard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useParams } from 'react-router';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 | import PropTypes from 'prop-types';
6 | import Spinner from 'react-bootstrap/Spinner';
7 | import { getPokemonsError, getPokemons, getPokemonPending } from '../reducers/pokes';
8 | import fetchPokemonsActions from '../api/pokemonApi';
9 | import Pokemon from '../components/Pokemon';
10 |
11 | export const PokemonCard = ({ fetchPokemon, data }) => {
12 | const { error, pending, pokemons = [] } = data;
13 |
14 | const { name } = useParams();
15 |
16 | useEffect(() => {
17 | fetchPokemon(name); // eslint-disable-next-line
18 | }, []);
19 |
20 | if (error) {
21 | return (
22 |
23 | {error}
24 |
25 | );
26 | }
27 | if (pending) {
28 | return (
29 |
30 |
31 |
32 | );
33 | }
34 | if (pokemons.length === 1) {
35 | return ;
36 | }
37 |
38 | return (
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | const mapDispatchToProps = dispatch => bindActionCreators({
46 | fetchPokemon: fetchPokemonsActions.fetchPokemon,
47 | }, dispatch);
48 |
49 | const mapStateToProps = state => ({
50 | data: {
51 | error: getPokemonsError(state.data),
52 | pokemons: getPokemons(state.data),
53 | pending: getPokemonPending(state.data),
54 | },
55 | });
56 |
57 | PokemonCard.defaultProps = {
58 | data: {
59 | error: null,
60 | pending: true,
61 | pokemons: [],
62 | },
63 | };
64 |
65 | PokemonCard.propTypes = {
66 | data: PropTypes.shape({
67 | error: PropTypes.string,
68 | pending: PropTypes.bool,
69 | pokemons: PropTypes.arrayOf(PropTypes.object),
70 | }),
71 | fetchPokemon: PropTypes.func.isRequired,
72 | };
73 |
74 | export default connect(mapStateToProps, mapDispatchToProps)(PokemonCard);
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_capstone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "bootstrap": "^4.5.2",
10 | "fetch-mock": "^9.10.7",
11 | "node-fetch": "^2.6.1",
12 | "prop-types": "^15.7.2",
13 | "react": "^16.13.1",
14 | "react-bootstrap": "^1.3.0",
15 | "react-dom": "^16.13.1",
16 | "react-google-charts": "^3.0.15",
17 | "react-minimal-pie-chart": "^8.2.0",
18 | "react-redux": "^7.2.1",
19 | "react-router": "^5.2.0",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "3.4.3",
22 | "redux": "^4.0.5",
23 | "redux-mock-store": "^1.5.4",
24 | "redux-thunk": "^2.3.0",
25 | "sinon": "^9.0.3"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "description": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).",
49 | "main": "index.js",
50 | "devDependencies": {
51 | "enzyme": "^3.11.0",
52 | "enzyme-adapter-react-16": "^1.15.4",
53 | "eslint": "^6.8.0",
54 | "eslint-config-airbnb": "^18.1.0",
55 | "eslint-plugin-import": "^2.20.2",
56 | "eslint-plugin-jsx-a11y": "^6.2.3",
57 | "eslint-plugin-react": "^7.20.6",
58 | "eslint-plugin-react-hooks": "^2.5.1",
59 | "stylelint": "^13.3.3",
60 | "stylelint-config-standard": "^20.0.0",
61 | "stylelint-csstree-validator": "^1.8.0",
62 | "stylelint-scss": "^3.17.2"
63 | },
64 | "repository": {
65 | "type": "git",
66 | "url": "git+https://github.com/rammazzoti2000/react_capstone.git"
67 | },
68 | "keywords": [
69 | "react",
70 | "redux",
71 | "api",
72 | "capstone",
73 | "microverse"
74 | ],
75 | "author": "Alexandru Bangau",
76 | "license": "MIT",
77 | "bugs": {
78 | "url": "https://github.com/rammazzoti2000/react_capstone/issues"
79 | },
80 | "homepage": "https://react-capstonejsx.herokuapp.com/"
81 | }
82 |
--------------------------------------------------------------------------------
/src/tests/components/PokemonList.test.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'enzyme';
2 | import React from 'react';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import { PokemonList } from '../../containers/PokemonsList';
5 | import store from '../../store/store';
6 |
7 | describe('PokemonList testing', () => {
8 | const mockFetchPokemonsFn = jest.fn();
9 | let data = {
10 | error: null,
11 | pending: true,
12 | pokemons: [],
13 | };
14 | beforeEach(() => {
15 | mount(
16 |
17 |
18 | ,
19 | );
20 | });
21 |
22 | it('should call the mock fetch pokemons function to populate data', () => {
23 | expect(mockFetchPokemonsFn.mock.calls.length).toBe(1);
24 | });
25 |
26 | it('renders error when fetching fails', () => {
27 | data = {
28 | error: 'Not Found',
29 | };
30 | const wrapper = mount(
31 |
32 |
33 | ,
34 | );
35 | expect(wrapper.find('.error').text()).toBe('Not Found');
36 | });
37 |
38 | it('renders spinner while waiting for data', () => {
39 | data = {
40 | pending: true,
41 | };
42 | const wrapper = mount(
43 |
44 |
45 | ,
46 | );
47 | expect(wrapper.find('.spinner-grow')).toHaveLength(1);
48 | });
49 |
50 | it('renders category filter and list of pokemons when data is obtained', () => {
51 | data = {
52 | pending: false,
53 | error: null,
54 | pokemons: [
55 | {
56 | pokemon: {
57 | name: 'squirtle',
58 | },
59 | },
60 | {
61 | pokemon: {
62 | name: 'charmander',
63 | },
64 | },
65 | {
66 | pokemon: {
67 | name: 'pikachu',
68 | },
69 | },
70 | ],
71 | };
72 |
73 | const wrapper = mount(
74 |
75 |
76 | ,
77 | );
78 | expect(wrapper.find('.custom-select')).toHaveLength(1);
79 | expect(wrapper.find('Link').at(0).text()).not.toBe('squirtle');
80 | expect(wrapper.find('Link').at(1).text()).not.toBe('charmander');
81 | expect(wrapper.find('Link').at(2).text()).not.toBe('pikachu');
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/containers/PokemonsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 | import PropTypes from 'prop-types';
5 | import Spinner from 'react-bootstrap/Spinner';
6 | import fetchPokemonsActions from '../api/pokemonApi';
7 | import { getPokemonsError, getPokemons, getPokemonsPending } from '../reducers/pokes';
8 | import { getPokemonType } from '../reducers/filter';
9 | import PokemonMain from '../components/PokemonMain';
10 | import PokemonFilter from '../components/PokemonFilter';
11 |
12 | export class PokemonList extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.handleFilterChange = this.handleFilterChange.bind(this);
16 | }
17 |
18 | componentDidMount() {
19 | const { fetchPokemons } = this.props;
20 | fetchPokemons('normal');
21 | }
22 |
23 | handleFilterChange(e) {
24 | const { fetchPokemons } = this.props;
25 | if (e.target.value !== '') {
26 | fetchPokemons(e.target.value);
27 | }
28 | e.preventDefault();
29 | }
30 |
31 | render() {
32 | const { data, filter } = this.props;
33 | const { error, pending, pokemons } = data;
34 | if (error) {
35 | return (
36 |
37 | {error}
38 |
39 | );
40 | }
41 |
42 | if (pending) {
43 | return (
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | if (pokemons.length < 2) {
51 | return (
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | return (
59 |
72 | );
73 | }
74 | }
75 |
76 | const mapDispatchToProps = dispatch => bindActionCreators({
77 | fetchPokemons: fetchPokemonsActions.fetchPokemons,
78 | }, dispatch);
79 |
80 | const mapStateToProps = state => ({
81 | data: {
82 | error: getPokemonsError(state.data),
83 | pokemons: getPokemons(state.data),
84 | pending: getPokemonsPending(state.data),
85 | },
86 | filter: getPokemonType(state.filter),
87 | });
88 |
89 | PokemonList.defaultProps = {
90 | data: {
91 | pending: true,
92 | error: null,
93 | pokemons: [],
94 | },
95 | filter: 'normal',
96 | };
97 |
98 | PokemonList.propTypes = {
99 | data: PropTypes.shape({
100 | pending: PropTypes.bool,
101 | error: PropTypes.string,
102 | pokemons: PropTypes.arrayOf(PropTypes.object),
103 | }),
104 | filter: PropTypes.string,
105 | fetchPokemons: PropTypes.func.isRequired,
106 | };
107 | export default connect(mapStateToProps, mapDispatchToProps)(PokemonList);
108 |
--------------------------------------------------------------------------------
/src/tests/api/pokemonApi.test.js:
--------------------------------------------------------------------------------
1 | import api from '../../api/pokemonApi';
2 |
3 | jest.mock('../../api/pokemonApi');
4 |
5 | describe('Api Response', () => {
6 | it('happy path -> returns a list of pokemon', async () => {
7 | const pokemons = [
8 | {
9 | pokemon: {
10 | name: 'pidgey',
11 | url: 'https://pokeapi.co/api/v2/pokemon/16/',
12 | },
13 | slot: 1,
14 | },
15 | {
16 | pokemon: {
17 | name: 'pidgeotto',
18 | url: 'https://pokeapi.co/api/v2/pokemon/17/',
19 | },
20 | slot: 1,
21 | },
22 | {
23 | pokemon: {
24 | name: 'pidgeot',
25 | url: 'https://pokeapi.co/api/v2/pokemon/18/',
26 | },
27 | slot: 1,
28 | },
29 | {
30 | pokemon: {
31 | name: 'rattata',
32 | url: 'https://pokeapi.co/api/v2/pokemon/19/',
33 | },
34 | slot: 1,
35 | },
36 | ];
37 | api.fetchPokemons.mockResolvedValue({ pokemons });
38 | const pokemonList = await api.fetchPokemons('normal');
39 | expect(pokemonList).toEqual({ pokemons });
40 | });
41 |
42 | it('unhappy path -> returns error when type not found', async () => {
43 | const error = 'Not Found';
44 | api.fetchPokemons.mockResolvedValue({ error });
45 | const pokemonListError = await api.fetchPokemons('n');
46 | expect(pokemonListError).toEqual({ error });
47 | });
48 |
49 | it('happy path -> returns single pokemon object', async () => {
50 | const pokemon = {
51 | name: 'squirtle',
52 | abilities: [{
53 | ability: {
54 | name: 'rain-dish',
55 | url: 'https://pokeapi.co/api/v2/ability/44/',
56 | },
57 | is_hidden: true,
58 | slot: 3,
59 | },
60 | {
61 | ability: {
62 | name: 'torrent',
63 | url: 'https://pokeapi.co/api/v2/ability/67/',
64 | },
65 | is_hidden: false,
66 | slot: 1,
67 | }],
68 | sprites: {
69 | back_default: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/7.png',
70 | back_female: null,
71 | back_shiny: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/7.png',
72 | back_shiny_female: null,
73 | front_default: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png',
74 | front_female: null,
75 | front_shiny: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/7.png',
76 | front_shiny_female: null,
77 | },
78 | stats: [
79 | {
80 | base_stat: 43,
81 | effort: 0,
82 | stat: {
83 | name: 'speed',
84 | url: 'https://pokeapi.co/api/v2/stat/6/',
85 | },
86 | },
87 | ],
88 | };
89 |
90 | api.fetchPokemon.mockResolvedValue({ pokemon });
91 | const pokemonObject = await api.fetchPokemon('squirtle');
92 | expect(pokemonObject).toEqual({ pokemon });
93 | });
94 |
95 | it('unhappy path -> returns error when pokemon not found by name', async () => {
96 | const error = 'Not Found';
97 |
98 | api.fetchPokemon.mockResolvedValue({ error });
99 | const pokemonError = await api.fetchPokemon('jjjjjj');
100 | expect(pokemonError).toEqual({ error });
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/src/components/Pokemon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHistory } from "react-router";
3 | import PropTypes from "prop-types";
4 | import picture from "../assets/picture.png";
5 | import Chart from "react-google-charts";
6 |
7 | const Pokemon = ({ pokemon }) => {
8 | const history = useHistory();
9 |
10 | const goBackHandle = () => {
11 | history.goBack();
12 | };
13 |
14 | const pieOptions = {
15 | title: "",
16 | pieHole: 0.6,
17 | slices: [
18 | {
19 | color: "#2BB673",
20 | },
21 | {
22 | color: "#d91e48",
23 | },
24 | {
25 | color: "#007fad",
26 | },
27 | {
28 | color: "#e9a227",
29 | },
30 | {
31 | color: "#E38627",
32 | },
33 | {
34 | color: "#C13C37",
35 | },
36 | {
37 | color: "#6A2135",
38 | },
39 | ],
40 | backgroundColor: "none",
41 | legend: {
42 | position: "labeled",
43 | alignment: "center",
44 | textStyle: {
45 | color: "233238",
46 | fontSize: 14,
47 | },
48 | },
49 | pieSliceText: "value",
50 | tooltip: {
51 | showColorCode: true,
52 | },
53 | chartArea: {
54 | left: 0,
55 | top: 0,
56 | width: "100%",
57 | height: "100%",
58 | },
59 | fontName: "Roboto",
60 | };
61 |
62 | const buildData = (pokemon) => {
63 | const array = [["name", "base_stat"]];
64 | pokemon.stats.map((stats) =>
65 | array.push([stats.stat.name, stats.base_stat])
66 | );
67 | return array;
68 | };
69 |
70 | return (
71 |
72 |
73 |
82 |
83 |
84 |
85 |
{pokemon.name}
86 |
Abilities:
87 |
88 | {pokemon.abilities.map((item) => (
89 |
93 | {item.ability.name}
94 |
95 | ))}
96 |
97 |
98 |
Stats:
99 |
110 |
115 | Go back
116 |
117 |
118 |
119 |
120 | );
121 | };
122 |
123 | Pokemon.defaultProps = {
124 | pokemon: {
125 | name: "pikachu",
126 | abilities: [],
127 | stats: [],
128 | sprites: {
129 | front_default:
130 | "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png",
131 | },
132 | },
133 | };
134 |
135 | Pokemon.propTypes = {
136 | pokemon: PropTypes.shape({
137 | name: PropTypes.string,
138 | abilities: PropTypes.arrayOf(PropTypes.object),
139 | stats: PropTypes.arrayOf(PropTypes.object),
140 | sprites: PropTypes.objectOf(PropTypes.string),
141 | }),
142 | };
143 |
144 | export default Pokemon;
145 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
16 | [![Contributors][contributors-shield]][contributors-url]
17 | [![Forks][forks-shield]][forks-url]
18 | [![Stargazers][stars-shield]][stars-url]
19 | [![Issues][issues-shield]][issues-url]
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
React&Redux --> [Pokemon Catalog]
30 |
31 |
32 | This project is part of the Microverse curriculum in React module!
33 |
34 | Explore the docs »
35 |
36 |
37 | Report Bug
38 | ·
39 | Request Feature
40 |
41 |
42 |
43 |
44 | ## Table of Contents
45 |
46 | * [About the Project](#about-the-project)
47 | * [Built With](#built-with)
48 | * [Usage](#usage)
49 | * [Automated Test](#automated-test)
50 | * [Contributors](#contributors)
51 | * [Acknowledgements](#acknowledgements)
52 | * [License](#license)
53 |
54 |
55 | ## About The Project
56 | React capstone based on a catalog of statistics that is a browsable list of items and can be filtered and accessed the details of one item.
57 | The WebApp it has two types of pages, the main one with the list of elements to display and the second one with a single element displaying relevant information about that object. Each page has a unique route within the Single Page Application.
58 | The project retrieves data from an API and stores it in the Redux Store. Based on that API it is possible to filter the data using a Filter stateless component.
59 |
60 |
61 | ###
62 |
63 | 
64 |
65 | ### Built With
66 | This project was built using these technologies.
67 | * HTML/CSS
68 | * ReactJs
69 | * Redux
70 | * Heroku Buildpack
71 | * npm
72 | * ES6
73 | * Node.js
74 | * ESLint
75 | * StyleLint
76 | * GithubActions :muscle:
77 | * Atom :atom:
78 |
79 |
80 | ## Usage
81 |
82 | To have this app on your pc, you need to:
83 | * [download](https://github.com/rammazzoti2000/react_capstone/archive/develop.zip) or clone this repo:
84 | - Clone with SSH:
85 | ```
86 | git@github.com:rammazzoti2000/react_capstone.git
87 | ```
88 | - Clone with HTTPS
89 | ```
90 | https://github.com/rammazzoti2000/react_capstone.git
91 | ```
92 |
93 | * In the project directory, you can run:
94 |
95 | - `$ npm install` - installs all the dependencies required by the project
96 |
97 | - `$ npm start` - runs the app in the development mode:
98 | - Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
99 | - The page will reload if you make edits.
100 | - You will also see any lint errors in the console.
101 |
102 | - `$ npm run build`
103 | - Builds the app for production to the `build` folder.
104 | - It correctly bundles React in production mode and optimizes the build for the best performance.
105 | - The build is minified and the filenames include the hashes.
106 | - Your app is ready to be deployed!
107 |
108 | ## Automated Test
109 | - `$ npm run test` - Launches the test runner in the interactive watch mode, see the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
110 |
111 | ## Walkthrough Video and Deployment (Heroku)
112 | https://www.loom.com/share/5860127540704697aba0d3dc360b97bd
113 | ##
114 |
115 | ## Live Demo & Deployment
116 | The project has been deployed with [Heroku Buildpack for create-react-app](https://github.com/mars/create-react-app-buildpack#user-content-requires)
117 |
118 | [Heroku Live Demo Link](https://react-capstonejsx.herokuapp.com/) :point_left:
119 |
120 |
121 | ## Contributors
122 |
123 | 👤 **Alexandru Bangau**
124 |
125 | - LinkedIn: [Alexandru Bangau](https://www.linkedin.com/in/alexandru-bangau/)
126 | - GitHub: [@rammazzoti2000](https://github.com/rammazzoti2000)
127 | - E-mail: bangau.alexandru@gmail.com
128 |
129 | ## :handshake: Contributing
130 |
131 | Contributions, issues and feature requests are welcome!
132 |
133 | Feel free to check the [issues page](https://github.com/rammazzoti2000/react_capstone/issues).
134 |
135 | ## Show your support
136 |
137 | Give a :star: if you like this project!
138 |
139 |
140 | ## Acknowledgements
141 | * [Microverse](https://www.microverse.org/)
142 | * [The Odin Project](https://www.theodinproject.com/)
143 | * [React Documentation](https://reactjs.org/docs/getting-started.html)
144 | * [Heroku Documentation](https://devcenter.heroku.com/)
145 | * [Heroku Buildpack](https://github.com/mars/create-react-app-buildpack#user-content-requires)
146 |
147 |
148 |
149 | [contributors-shield]: https://img.shields.io/github/contributors/rammazzoti2000/react_capstone.svg?style=flat-square
150 | [contributors-url]: https://github.com/rammazzoti2000/react_capstone/graphs/contributors
151 | [forks-shield]: https://img.shields.io/github/forks/rammazzoti2000/react_capstone.svg?style=flat-square
152 | [forks-url]: https://github.com/rammazzoti2000/react_capstone/network/members
153 | [stars-shield]: https://img.shields.io/github/stars/rammazzoti2000/react_capstone.svg?style=flat-square
154 | [stars-url]: https://github.com/rammazzoti2000/react_capstone/stargazers
155 | [issues-shield]: https://img.shields.io/github/issues/rammazzoti2000/react_capstone.svg?style=flat-square
156 | [issues-url]: https://github.com/rammazzoti2000/react_capstone/issues
157 |
158 | ## 📝 License
159 |
160 | This project is [MIT](https://opensource.org/licenses/MIT) licensed.
161 |
--------------------------------------------------------------------------------
/src/styles/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0-modified | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 |
27 | /* make sure to set some focus styles for accessibility */
28 | :focus {
29 | outline: 0;
30 | }
31 |
32 | /* HTML5 display-role reset for older browsers */
33 | article, aside, details, figcaption, figure,
34 | footer, header, hgroup, menu, nav, section {
35 | display: block;
36 | }
37 |
38 | body {
39 | line-height: 1;
40 | }
41 |
42 | ol, ul {
43 | list-style: none;
44 | }
45 |
46 | blockquote, q {
47 | quotes: none;
48 | }
49 |
50 | blockquote:before, blockquote:after,
51 | q:before, q:after {
52 | content: '';
53 | content: none;
54 | }
55 |
56 | table {
57 | border-collapse: collapse;
58 | border-spacing: 0;
59 | }
60 |
61 | input[type=search]::-webkit-search-cancel-button,
62 | input[type=search]::-webkit-search-decoration,
63 | input[type=search]::-webkit-search-results-button,
64 | input[type=search]::-webkit-search-results-decoration {
65 | -webkit-appearance: none;
66 | -moz-appearance: none;
67 | }
68 |
69 | input[type=search] {
70 | -webkit-appearance: none;
71 | -moz-appearance: none;
72 | -webkit-box-sizing: content-box;
73 | -moz-box-sizing: content-box;
74 | box-sizing: content-box;
75 | }
76 |
77 | textarea {
78 | overflow: auto;
79 | vertical-align: top;
80 | resize: vertical;
81 | }
82 |
83 | /**
84 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
85 | */
86 |
87 | audio,
88 | canvas,
89 | video {
90 | display: inline-block;
91 | *display: inline;
92 | *zoom: 1;
93 | max-width: 100%;
94 | }
95 |
96 | /**
97 | * Prevent modern browsers from displaying `audio` without controls.
98 | * Remove excess height in iOS 5 devices.
99 | */
100 |
101 | audio:not([controls]) {
102 | display: none;
103 | height: 0;
104 | }
105 |
106 | /**
107 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
108 | * Known issue: no IE 6 support.
109 | */
110 |
111 | [hidden] {
112 | display: none;
113 | }
114 |
115 | /**
116 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
117 | * `em` units.
118 | * 2. Prevent iOS text size adjust after orientation change, without disabling
119 | * user zoom.
120 | */
121 |
122 | html {
123 | font-size: 100%; /* 1 */
124 | -webkit-text-size-adjust: 100%; /* 2 */
125 | -ms-text-size-adjust: 100%; /* 2 */
126 | }
127 |
128 | /**
129 | * Address `outline` inconsistency between Chrome and other browsers.
130 | */
131 |
132 | a:focus {
133 | outline: thin dotted;
134 | }
135 |
136 | /**
137 | * Improve readability when focused and also mouse hovered in all browsers.
138 | */
139 |
140 | a:active,
141 | a:hover {
142 | outline: 0;
143 | }
144 |
145 | /**
146 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
147 | * 2. Improve image quality when scaled in IE 7.
148 | */
149 |
150 | img {
151 | border: 0; /* 1 */
152 | -ms-interpolation-mode: bicubic; /* 2 */
153 | }
154 |
155 | /**
156 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
157 | */
158 |
159 | figure {
160 | margin: 0;
161 | }
162 |
163 | /**
164 | * Correct margin displayed oddly in IE 6/7.
165 | */
166 |
167 | form {
168 | margin: 0;
169 | }
170 |
171 | /**
172 | * Define consistent border, margin, and padding.
173 | */
174 |
175 | fieldset {
176 | border: 1px solid #c0c0c0;
177 | margin: 0 2px;
178 | padding: 0.35em 0.625em 0.75em;
179 | }
180 |
181 | /**
182 | * 1. Correct color not being inherited in IE 6/7/8/9.
183 | * 2. Correct text not wrapping in Firefox 3.
184 | * 3. Correct alignment displayed oddly in IE 6/7.
185 | */
186 |
187 | legend {
188 | border: 0; /* 1 */
189 | padding: 0;
190 | white-space: normal; /* 2 */
191 | *margin-left: -7px; /* 3 */
192 | }
193 |
194 | /**
195 | * 1. Correct font size not being inherited in all browsers.
196 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
197 | * and Chrome.
198 | * 3. Improve appearance and consistency in all browsers.
199 | */
200 |
201 | button,
202 | input,
203 | select,
204 | textarea {
205 | font-size: 100%; /* 1 */
206 | margin: 0; /* 2 */
207 | vertical-align: baseline; /* 3 */
208 | *vertical-align: middle; /* 3 */
209 | }
210 |
211 | /**
212 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in
213 | * the UA stylesheet.
214 | */
215 |
216 | button,
217 | input {
218 | line-height: normal;
219 | }
220 |
221 | /**
222 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
223 | * All other form control elements do not inherit `text-transform` values.
224 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
225 | * Correct `select` style inheritance in Firefox 4+ and Opera.
226 | */
227 |
228 | button,
229 | select {
230 | text-transform: none;
231 | }
232 |
233 | /**
234 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
235 | * and `video` controls.
236 | * 2. Correct inability to style clickable `input` types in iOS.
237 | * 3. Improve usability and consistency of cursor style between image-type
238 | * `input` and others.
239 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
240 | * Known issue: inner spacing remains in IE 6.
241 | */
242 |
243 | button,
244 | html input[type="button"], /* 1 */
245 | input[type="reset"],
246 | input[type="submit"] {
247 | -webkit-appearance: button; /* 2 */
248 | cursor: pointer; /* 3 */
249 | *overflow: visible; /* 4 */
250 | }
251 |
252 | /**
253 | * Re-set default cursor for disabled elements.
254 | */
255 |
256 | button[disabled],
257 | html input[disabled] {
258 | cursor: default;
259 | }
260 |
261 | /**
262 | * 1. Address box sizing set to content-box in IE 8/9.
263 | * 2. Remove excess padding in IE 8/9.
264 | * 3. Remove excess padding in IE 7.
265 | * Known issue: excess padding remains in IE 6.
266 | */
267 |
268 | input[type="checkbox"],
269 | input[type="radio"] {
270 | box-sizing: border-box; /* 1 */
271 | padding: 0; /* 2 */
272 | *height: 13px; /* 3 */
273 | *width: 13px; /* 3 */
274 | }
275 |
276 | /**
277 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
278 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
279 | * (include `-moz` to future-proof).
280 | */
281 |
282 | input[type="search"] {
283 | -webkit-appearance: textfield; /* 1 */
284 | -moz-box-sizing: content-box;
285 | -webkit-box-sizing: content-box; /* 2 */
286 | box-sizing: content-box;
287 | }
288 |
289 | /**
290 | * Remove inner padding and search cancel button in Safari 5 and Chrome
291 | * on OS X.
292 | */
293 |
294 | input[type="search"]::-webkit-search-cancel-button,
295 | input[type="search"]::-webkit-search-decoration {
296 | -webkit-appearance: none;
297 | }
298 |
299 | /**
300 | * Remove inner padding and border in Firefox 3+.
301 | */
302 |
303 | button::-moz-focus-inner,
304 | input::-moz-focus-inner {
305 | border: 0;
306 | padding: 0;
307 | }
308 |
309 | /**
310 | * 1. Remove default vertical scrollbar in IE 6/7/8/9.
311 | * 2. Improve readability and alignment in all browsers.
312 | */
313 |
314 | textarea {
315 | overflow: auto; /* 1 */
316 | vertical-align: top; /* 2 */
317 | }
318 |
319 | /**
320 | * Remove most spacing between table cells.
321 | */
322 |
323 | table {
324 | border-collapse: collapse;
325 | border-spacing: 0;
326 | }
327 |
328 | html,
329 | button,
330 | input,
331 | select,
332 | textarea {
333 | color: #222;
334 | }
335 |
336 |
337 | ::-moz-selection {
338 | background: #b3d4fc;
339 | text-shadow: none;
340 | }
341 |
342 | ::selection {
343 | background: #b3d4fc;
344 | text-shadow: none;
345 | }
346 |
347 | img {
348 | vertical-align: middle;
349 | }
350 |
351 | fieldset {
352 | border: 0;
353 | margin: 0;
354 | padding: 0;
355 | }
356 |
357 | textarea {
358 | resize: vertical;
359 | }
360 |
361 | .chromeframe {
362 | margin: 0.2em 0;
363 | background: #ccc;
364 | color: #000;
365 | padding: 0.2em 0;
366 | }
367 |
--------------------------------------------------------------------------------