diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index 993b456..9a041da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14878,6 +14878,11 @@ "requires-port": "^1.0.0" } }, + "url-search-params-polyfill": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-8.1.1.tgz", + "integrity": "sha512-KmkCs6SjE6t4ihrfW9JelAPQIIIFbJweaaSLTh/4AO+c58JlDcb+GbdPt8yr5lRcFg4rPswRFRRhBGpWwh0K/Q==" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index a154f9e..9602062 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "sass": "^1.32.11", + "url-search-params-polyfill": "^8.1.1", "web-vitals": "^1.0.1" }, "devDependencies": { diff --git a/src/App.js b/src/App.js index 3a49c0e..21f456a 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 Home from "./pages/Home"; +import Episode from "./pages/Episode"; +import Character from "./pages/Character"; +import Location from "./pages/Location"; function App() { - return ; + return ( + + ( + + )} /> + + ( + + )} /> + + ( + + )} /> + + ( + + )} /> + + ); } export default App; diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..bb5e1a0 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,10 @@ +import axios from "axios"; + +export async function makeRequest(url) { + try { + const response = await axios.get(url); + return [response , false]; + } catch (error) { + return [null , error]; + } +} \ No newline at end of file diff --git a/src/components/CharacterCard/CharacterCard.js b/src/components/CharacterCard/CharacterCard.js index 9e7e4ab..84e6a92 100644 --- a/src/components/CharacterCard/CharacterCard.js +++ b/src/components/CharacterCard/CharacterCard.js @@ -5,23 +5,40 @@ import "./CharacterCard.scss"; import * as routes from "../../constants/routes"; -function CharacterCard({ id, name, image, species, status, origin, location }) { +function CharacterCard({ id, name, image, species, status, origin, location, showMoreDetails = false }) { return ( -
+

{name}

+

Origin: {origin.name} +

|

-

{status}

+

Status: {status}

+ + {showMoreDetails && ( +
+

Location: + + {location.name} + +

+

|

+

Specie: {species}

+
+ )}
); } diff --git a/src/pages/Character/Character.js b/src/pages/Character/Character.js new file mode 100644 index 0000000..c6498da --- /dev/null +++ b/src/pages/Character/Character.js @@ -0,0 +1,129 @@ +import React, { Component } from "react"; +import { Link } from 'react-router-dom'; +import * as routes from "../../constants/routes"; + +import Layout from "../../components/Layout"; +import EpisodeCard from "../../components/EpisodeCard"; +import { makeRequest } from '../../api'; + +class Character extends Component { + constructor(props) { + super(props); + + this.state = { + character: { + image: null, + name: null, + species: null, + status: null, + origin: null, + location: null + }, + episodes: [], + hasError: false, + hasLoaded: false + } + } + + async componentDidMount() { + // Get episode ID by regex, best would be by query param in URL + const path = window.location.pathname + const id = path.match(/(\d.*$)/)[0] + + const [response, error] = await makeRequest(`https://rickandmortyapi.com/api/character/${id}`) + + if (!error) { + const {data} = response + const promises = data.episode.map(url => makeRequest(url)) + + const characterLocation = await makeRequest(data.location.url) + + const character = { + image: data.image, + name: data.name, + species: data.species, + status: data.status, + origin: data.origin, + location: characterLocation[0].data + } + + Promise.all(promises) + .then(responses => ( + this.setState({ + character: character, + episodes: responses.map(res => res[0].data), + hasLoaded: true, + hasError: false + }) + )) + + } else { + this.setState({ + hasLoaded: false, + hasError: true + }) + } + } + + render() { + + const {character, episodes, hasError, hasLoaded} = this.state + return( + +
+
+ {!hasLoaded && (

Loading...

)} + {hasError && (

API error

)} + + {!hasError && hasLoaded && ( +
+ {character.name} +
+

{character.name}

+
+
+
Character
+

{character.species} | {character.status}

+
+
+
Origin
+

{character.origin.name}

+
+
+
Location
+

{character.location.name}

+
+
+
+ )} +
+ +
+
+
+ + {episodes.length > 0 && ( +
+
    + {episodes.map(episode => ( +
  • + +
  • + ))} +
+
+ )} + +
+
+ ) + } +} + +export default Character \ No newline at end of file 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..af9a56a 100644 --- a/src/pages/Episode/Episode.js +++ b/src/pages/Episode/Episode.js @@ -1,26 +1,61 @@ import React, { Component } from "react"; +import { makeRequest } from '../../api' import Layout from "../../components/Layout"; -// import CharacterCard from "../../components/CharacterCard"; +import CharacterCard from "../../components/CharacterCard"; 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, + }; + } + + async componentDidMount() { + // Get episode ID by regex, best would be by query param in URL + const path = window.location.pathname + const id = path.match(/(\d.*$)/)[0] + + const [response, error] = await makeRequest(`https://rickandmortyapi.com/api/episode/${id}`) + + if (!error) { + const {data} = response + const promises = data.characters.map(url => makeRequest(url)) + Promise.all(promises) + .then(responses => ( + this.setState({ + episode: data.episode, + characters: responses.map(res => res[0].data), + hasLoaded: true, + hasError: false + }) + )) + } else { + this.setState({ + hasLoaded: false, + hasError: true + }) + } } render() { + + const {characters, episode, hasLoaded, hasError, errorMessage} = this.state + return (
- {/* {characters.map((character) => ( + {!hasLoaded && (

Loading...

)} + {hasError && (

API error

)} + + {characters.length > 0 && hasLoaded && characters.map((character) => ( - ))} */} + ))}
diff --git a/src/pages/Home/Home.js b/src/pages/Home/Home.js index f86e93c..b18aef7 100644 --- a/src/pages/Home/Home.js +++ b/src/pages/Home/Home.js @@ -1,42 +1,70 @@ import React, { Component } from "react"; +import { Link } from "react-router-dom"; +import { makeRequest } 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 = { + episodes: [], + nextUrl: null, + prevUrl: null, + hasLoaded: false, + hasError: false + } + + this.loadEpisodes = this.loadEpisodes.bind(this) + this.handlePagination = this.handlePagination.bind(this) + } + + componentDidMount() { + this.loadEpisodes({}) } - async componentDidMount() { - // this.loadEpisodes(); + handlePagination(event) { + const { nextUrl, prevUrl } = this.state + event.preventDefault() + event.target.getAttribute('data-pagination') === 'next' ? + this.loadEpisodes({url: nextUrl}) : + this.loadEpisodes({url: prevUrl}) } - async loadEpisodes() { - console.log(this); + async loadEpisodes({url = undefined}) { + // Get episodes + const { currentPage } = this.state + const apiUrl = url || `https://rickandmortyapi.com/api/episode?page=${currentPage}` + + const [response, error] = await makeRequest(apiUrl) + + if (!error) this.setState({ + episodes: response.data.results, + hasLoaded: true, + hasError: false, + nextUrl: response.data.info.next, + prevUrl: response.data.info.prev, + }) + if (error) this.setState({hasError: true, hasLoaded: false}) } render() { + const {hasLoaded, hasError, episodes, nextUrl, prevUrl, currentPage} = this.state + return (
- {/* {hasLoaded && !hasError && ( + {hasLoaded && !hasError && (

Episodes loaded!

- )} */} + )}

- {/* {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..b6f2986 --- /dev/null +++ b/src/pages/Location/Location.js @@ -0,0 +1,115 @@ +import React, { Component } from "react"; + +import Layout from "../../components/Layout"; +import CharacterCard from "../../components/CharacterCard"; +import { makeRequest } from '../../api'; + +class Location extends Component { + constructor(props) { + super(props); + + this.state = { + id: null, + name: null, + type: null, + dimension: null, + characters: [], + hasLoaded: false, + hasError: false, + } + } + + async componentDidMount() { + // Get episode ID by regex, best would be by query param in URL + const path = window.location.pathname + const id = path.match(/(\d.*$)/)[0] + + const [response, error] = await makeRequest(`https://rickandmortyapi.com/api/location/${id}`) + + if (!error) { + const {data} = response + const promises = data.residents.map(url => makeRequest(url)) + + Promise.all(promises) + .then(responses => ( + this.setState({ + id: data.id, + name: data.name, + type: data.type, + dimension: data.dimension, + characters: responses.map(res => res[0].data), + hasError: false, + hasLoaded: true + }) + )) + + } else { + this.setState({ + hasLoaded: false, + hasError: true + }) + } + } + + render() { + + const {id, name, type, dimension, characters, hasError, hasLoaded} = this.state + console.log(characters) + return( + +
+
+ {!hasLoaded && (

Loading...

)} + {hasError && (

API error

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

{name}

+
+
+
Type
+

{type}

+
+
+
Dimension
+

{dimension}

+
+
+
+ )} +
+ +
+
+
+ + {characters.length > 0 && ( +
+
    + {characters.map(character => ( +
  • + +
  • + ))} +
+
+ )} + +
+
+ ) + } +} + +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