diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index d8b83df..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -package-lock.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3480d73..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - jest: true, - jquery: true, - "jest/globals": true, - }, - extends: [ - "airbnb", - "eslint:recommended", - "plugin:compat/recommended", - "plugin:react/recommended", - "plugin:jest/recommended", - "prettier", - "prettier/prettier", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:jsx-a11y/recommended", - "react-app", - "react-app/jest", - ], - parser: "@babel/eslint-parser", - parserOptions: { - ecmaVersion: 12, - sourceType: "module", - requireConfigFile: "false", - jsx: true, - }, - plugins: [ - "@html-eslint", - "jest", - "react", - "jsx-a11y", - "markdown", - "react-hooks", - "import", - ], - settings: { - react: { - version: "detect", - }, - }, - overrides: [ - { - files: ["*.html"], - parser: "@html-eslint/parser", - extends: ["plugin:@html-eslint/recommended"], - }, - ], - rules: { - "react/jsx-filename-extension": "off", - "import/prefer-default-export": "off", - "prefer-destructuring": "off", - "object-shorthand": "off", - "react/jsx-props-no-spreading": "off", - "arrow-body-style": "off", - "no-underscore-dangle": "off", - "react/forbid-prop-types": "off", - "react/prop-types": "off", - }, -}; diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 8c4b2fa..0000000 --- a/.prettierrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "arrowParens": "always", - "bracketSpacing": true, - "embeddedLanguageFormatting": "auto", - "htmlWhitespaceSensitivity": "css", - "insertPragma": false, - "jsxBracketSameLine": false, - "jsxSingleQuote": false, - "printWidth": 80, - "proseWrap": "always", - "quoteProps": "as-needed", - "requirePragma": false, - "semi": true, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "all", - "useTabs": false, - "endOfLine": "lf" -} diff --git a/Rick-and-morty.postman_collection b/Rick-and-morty.postman_collection new file mode 100644 index 0000000..58f121f --- /dev/null +++ b/Rick-and-morty.postman_collection @@ -0,0 +1,54 @@ +{ + "info": { + "_postman_id": "d874b8b8-3eaa-4a7c-9f74-fc3aaaae1e2b", + "name": "New Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": null + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": null + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": null + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": null + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": null + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index a154f9e..7df4f94 100644 --- a/package.json +++ b/package.json @@ -29,28 +29,8 @@ "@babel/plugin-syntax-jsx": "^7.12.13", "@babel/preset-env": "^7.13.10", "@babel/preset-react": "^7.12.13", - "@html-eslint/eslint-plugin": "^0.8.0", - "@html-eslint/parser": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^4.17.0", - "@typescript-eslint/parser": "^4.17.0", "cross-env": "^7.0.3", - "eslint": "^7.21.0", - "eslint-config-airbnb": "^18.2.1", - "eslint-config-airbnb-babel": "^0.2.1", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-babel": "^5.3.1", - "eslint-plugin-compat": "^3.9.0", - "eslint-plugin-html": "^6.1.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.2.1", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-markdown": "^2.0.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-react": "^7.22.0", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-testing-library": "^3.10.1", "jest": "^26.6.0", - "prettier": "^2.2.1", "typescript": "^4.2.3" }, "scripts": { @@ -61,18 +41,8 @@ "test:watch": "react-scripts test --watchAll", "test:ci:all": "cross-env CI=true react-scripts test --all --env=jsdom", "test:related": "cross-env CI=true react-scripts test --bail --findRelatedTests --env=jsdom", - "lint:js": "eslint . --ext .js", - "lint:js:fix": "npm run lint:js -- --fix", - "lint:format": "prettier --write .", - "lint:format:check": "prettier --check .", "pre:push": "npm run lint:js && npm run lint:format:check && npm run test:ci:all" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, "browserslist": { "production": [ ">0.2%", diff --git a/src/Api/Api.js b/src/Api/Api.js new file mode 100644 index 0000000..520b3dd --- /dev/null +++ b/src/Api/Api.js @@ -0,0 +1,27 @@ +import axios from "axios"; + +export function makeApi() { + return axios.create({ + baseURL: "https://rickandmortyapi.com/api", + }); +} + +export function getEpisodes(page = 1, api = makeApi()) { + return api.get(`/episode?page=${page}`); +} + +export function getEpisode(episodeId, api = makeApi()) { + return api.get(`/episode/${episodeId}`); +} + +export function getUrl(url, api = makeApi()) { + return api.get(url); +} + +export function getLocation(locationId, api = makeApi()) { + return api.get(`/location/${locationId}`); +} + +export function getCharacter(characterId, api = makeApi()) { + return api.get(`/character/${characterId}`); +} diff --git a/src/Api/index.js b/src/Api/index.js new file mode 100644 index 0000000..b8b158f --- /dev/null +++ b/src/Api/index.js @@ -0,0 +1 @@ +export * from "./Api" \ No newline at end of file diff --git a/src/App.js b/src/App.js index 3a49c0e..7e0408a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,30 @@ import React from "react"; +import { Switch, Route } from "react-router-dom"; +import { EPISODE, HOME, LOCATION, CHARACTER } from "./constants/routes"; import Home from "./pages/Home"; +import Episode from "./pages/Episode"; +import Location from "./pages/Location"; +import Character from "./pages/Character"; function App() { - return ; + return ( + + } + /> + } + /> + } + /> + } /> + + ); } -export default App; +export default App; \ No newline at end of file diff --git a/src/components/CharacterCard/CharacterCard.js b/src/components/CharacterCard/CharacterCard.js index 9e7e4ab..544f1a9 100644 --- a/src/components/CharacterCard/CharacterCard.js +++ b/src/components/CharacterCard/CharacterCard.js @@ -5,7 +5,8 @@ import "./CharacterCard.scss"; import * as routes from "../../constants/routes"; -function CharacterCard({ id, name, image, species, status, origin, location }) { +function CharacterCard({ id, name, image, status, location }) { + const locationId = location.url.split("/").pop(); return (
@@ -15,9 +16,9 @@ function CharacterCard({ id, name, image, species, status, origin, location }) {
- {origin.name} + {location.name}

|

{status}

diff --git a/src/pages/Character/Character.js b/src/pages/Character/Character.js new file mode 100644 index 0000000..43bc921 --- /dev/null +++ b/src/pages/Character/Character.js @@ -0,0 +1,122 @@ +import React, { Component } from "react"; +import axios from "axios"; + +import Layout from "../../components/Layout"; + +import { getCharacter } from "../../Api"; +import EpisodeCard from "../../components/EpisodeCard"; + +class Character extends Component { + constructor(props) { + super(props); + + this.state = { + character: null, + episodes: [], + hasLoaded: false, + hasError: false, + errorMessage: null, + }; + this.loadCharacter = this.loadCharacter.bind(this); + } + + componentDidMount() { + const { match } = this.props; + const { characterId } = match.params; + + this.loadCharacter(characterId); + } + + async loadCharacter(characterId) { + try { + const { data } = await getCharacter(characterId); + + const promises = data.episode.map((eachEpisode) => + axios.get(eachEpisode), + ); + + const episodesResponse = await Promise.all(promises); + + const episodes = episodesResponse.map((episode) => episode.data); + + this.setState({ + hasLoaded: true, + character: data, + episodes: episodes, + }); + } catch (e) { + this.setState({ + hasLoaded: true, + hasError: true, + errorMessage: e.message, + }); + } + } + + render() { + const { + character, + episodes, + hasLoaded, + hasError, + errorMessage, + } = this.state; + return ( + +
+ {!hasLoaded && ( +
+

Character not loaded...

+
+ )} + {hasLoaded && ( +
+
+ +
+

{character.name}

+
+
CHARACTER
+

{`${character.species} | ${character.status}`}

+
+
+
ORIGIN
+

{`${character.origin.name}`}

+
+
+
LOCATION
+

{`${character.location.name}`}

+
+
+
+
+
+

Episodes

+
+
+ )} + + {hasError && ( +
+

Character error...

+

{errorMessage}

+
+ )} + {episodes.length > 0 && + episodes.map((episode) => ( + + ))} + +
+
+ ); + } +} + +export default Character; diff --git a/src/pages/Character/index.js b/src/pages/Character/index.js new file mode 100644 index 0000000..4562dce --- /dev/null +++ b/src/pages/Character/index.js @@ -0,0 +1 @@ +export { default } from "./Character"; \ No newline at end of file diff --git a/src/pages/Episode/Episode.js b/src/pages/Episode/Episode.js index 8255df5..30c7ac5 100644 --- a/src/pages/Episode/Episode.js +++ b/src/pages/Episode/Episode.js @@ -1,26 +1,88 @@ import React, { Component } from "react"; +import axios from "axios"; import Layout from "../../components/Layout"; -// import CharacterCard from "../../components/CharacterCard"; +import CharacterCard from "../../components/CharacterCard"; + +import { getEpisode } from "../../Api"; class Episode extends Component { constructor(props) { super(props); - this.state = {}; - // episode: null, - // characters: [], - // hasLoaded: false, - // hasError: false, - // errorMessage: null, + this.state = { + episode: null, + characters: [], + hasLoaded: false, + hasError: false, + errorMessage: null, + } + + this.loadEpisode = this.loadEpisode.bind(this) + } + + componentDidMount() { + const { match } = this.props; + const { episodeId } = match.params; + + this.loadEpisode(episodeId); } + async loadEpisode(episodeId) { + try { + const { data } = await getEpisode(episodeId); + + const promises = data.characters.map((character) => axios.get(character)); + + const charactersResponse = await Promise.all(promises); + + const characters = charactersResponse.map((character) => character.data); + + this.setState({ + hasLoaded: true, + episode: data, + characters: characters, + }); + } catch (e) { + this.setState({ + hasLoaded: true, + hasError: true, + errorMessage: e.message, + }); + } + } render() { + const { + episode, + characters, + hasLoaded, + hasError, + errorMessage, + } = this.state; return (
-
- {/* {characters.map((character) => ( + {!hasLoaded && ( +
+

Episode not loaded...

+
+ )} + {hasLoaded && ( +
+
{episode.name}
+
+

{`${episode.episode} | ${episode.air_date}`}

+
+
+ )} + {hasError && ( +
+

Episode error...

+

{errorMessage}

+
+ )} + {characters.length > 0 && + characters.map((character) => ( - ))} */} -
+ ))}
); diff --git a/src/pages/Home/Home.js b/src/pages/Home/Home.js index f86e93c..c2b6b06 100644 --- a/src/pages/Home/Home.js +++ b/src/pages/Home/Home.js @@ -1,42 +1,98 @@ import React, { Component } from "react"; +import { getEpisodes } from "../../Api"; + import Layout from "../../components/Layout"; -// import EpisodeCard from "../../components/EpisodeCard"; +import EpisodeCard from "../../components/EpisodeCard"; class Home extends Component { constructor(props) { super(props); - this.state = {}; - // page: 1, - // paginationInfo: null, - // episodes: [], - // hasLoaded: false, - // hasError: false, - // errorMessage: null, + this.state = { + page: 1, + paginationInfo: null, + episodes: [], + hasLoaded: false, + hasError: false, + errorMessage: null, } + this.loadEpisodes = this.loadEpisodes.bind(this); + this.loadNextPage = this.loadNextPage.bind(this); + this.loadPrevPage = this.loadPrevPage.bind(this); +} async componentDidMount() { - // this.loadEpisodes(); + const { page } = this.state; + this.loadEpisodes(page); + } + + componentDidUpdate(_prevProps, prevState) { + const { page: prevPage } = prevState; + const { page } = this.state; + if (prevPage !== page) { + this.loadEpisodes(page); + } } - async loadEpisodes() { - console.log(this); + async loadEpisodes(page) { + try { + const { data } = await getEpisodes(page); + this.setState({ + paginationInfo: data.info, + episodes: data.results, + hasLoaded: true, + }); + } catch (e) { + this.setState({ + hasLoaded: true, + hasError: true, + errorMessage: e.message, + }); + } + } + loadPrevPage() { + this.setState((prevState) => ({ + page: prevState.page - 1, + })); } + loadNextPage() { + this.setState((prevState) => ({ + page: prevState.page + 1, + })); + } render() { + const { + paginationInfo, + episodes, + hasLoaded, + hasError, + errorMessage, + } = this.state; return (
- {/* {hasLoaded && !hasError && ( + {!hasLoaded && ( +
+

Location not loaded...

+
+ )} + {hasLoaded && !hasError && (

Episodes loaded!

- )} */} + )} + {hasError && ( +
+

Location error...

+

{errorMessage}

+
+ )}

- {/* {episodes.map((episode) => ( + {episodes.map((episode) => ( - ))} */} + ))}

+
+ + +
); diff --git a/src/pages/Location/Location.js b/src/pages/Location/Location.js new file mode 100644 index 0000000..2c3167a --- /dev/null +++ b/src/pages/Location/Location.js @@ -0,0 +1,101 @@ +import React, { Component } from "react"; +import axios from "axios"; +import Layout from "../../components/Layout"; +import CharacterCard from "../../components/CharacterCard"; + +import { getLocation } from "../../Api"; + +class Location extends Component { + constructor(props) { + super(props); + + this.state = { + location: null, + hasLoaded: false, + hasError: false, + errorMessage: null, + }; + this.loadLocation = this.loadLocation.bind(this); + } + + componentDidMount() { + const { match } = this.props; + const { locationId } = match.params; + + this.loadLocation(locationId); + } + + async loadLocation(locationId) { + try { + const { data } = await getLocation(locationId); + + const promises = data.residents.map((resident) => axios.get(resident)); + + const residentsResponse = await Promise.all(promises); + + const residents = residentsResponse.map((resident) => resident.data); + + this.setState({ + hasLoaded: true, + location: data, + residents: residents, + }); + } catch (e) { + this.setState({ + hasLoaded: true, + hasError: true, + errorMessage: e.message, + }); + } + } + + render() { + const { location, residents, hasLoaded, hasError, errorMessage } = this.state; + return ( + +
+ {!hasLoaded && ( +
+

Location not loaded...

+
+ )} + {hasLoaded && !hasError && ( + <> +
+
+ Location: + {` ${location.name}`} +
+
+

Residents

+
+
+ { residents.map((resident) => ( + + ))} + + )} + + + {hasError && ( +
+

Location error...

+

{errorMessage}

+
+ )} + + +
+
+ )}} + +export default Location; \ No newline at end of file diff --git a/src/pages/Location/index.js b/src/pages/Location/index.js new file mode 100644 index 0000000..e267389 --- /dev/null +++ b/src/pages/Location/index.js @@ -0,0 +1 @@ +export { default } from "./Location"; \ No newline at end of file