Skip to content

Commit

Permalink
feat: add modal and service to get episode information
Browse files Browse the repository at this point in the history
  • Loading branch information
katiaipduarte committed Apr 18, 2021
1 parent 17cd9be commit 0a7bf7e
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 36 deletions.
6 changes: 6 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import CharacterList from './components/CharacterList/CharacterList';
import Modal from './components/Modal/Modal';
import Search from './components/Search/Search';
import ApiProvider from './lib/api-provider';
import { GlobalState } from './store/store';

const App = () => {
const isOpen = useSelector((state: GlobalState) => state.charactersState).modalStatus;
const { getCharacters } = ApiProvider();

useEffect(() => {
Expand All @@ -12,6 +16,8 @@ const App = () => {

return (
<>
{isOpen ? <Modal /> : null}

<Search />
<main>
{/* <Filters /> */}
Expand Down
8 changes: 4 additions & 4 deletions src/components/CharacterList/CharacterList.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const CharacterListContainer = styled.section`
display: flex;
height: 174px;
justify-content: center;
cursor: pointer;
img {
align-items: center;
display: flex;
Expand All @@ -31,21 +31,21 @@ export const CharacterListContainer = styled.section`
.species,
.status {
color: #222;
font-size: 14rem;
font-size: 12rem;
font-weight: bold;
margin: 8rem 0;
opacity: 0.7;
text-transform: uppercase;
}
h1 {
h4 {
font-size: 16rem;
font-weight: bold;
}
.description {
color: #222;
font-size: 12rem;
font-size: 11rem;
margin-top: 8rem;
br {
Expand Down
18 changes: 13 additions & 5 deletions src/components/CharacterList/CharacterList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { memo, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Character } from '../../interfaces/character';
import { Info } from '../../interfaces/info';
import { modalStatus, selectCharacter } from '../../store/characters/action';
import { GlobalState } from '../../store/store';
import Modal from '../Modal/Modal';
import Pagination from '../Pagination/Pagination';
import { CharacterListContainer } from './CharacterList.style';

const CharacterList = (): JSX.Element => {
const store = useSelector((state: GlobalState) => state.charactersState);
const store = useSelector((state: GlobalState) => state.charactersState).response;
const dispatch = useDispatch();

const [list, setList] = useState<Character[]>([]);
const [pagination, setPagination] = useState<Info>();
Expand All @@ -32,16 +35,21 @@ const CharacterList = (): JSX.Element => {
return color;
};

const handleClick = (id: number): void => {
dispatch(selectCharacter(id));
dispatch(modalStatus(true));
};

return (
<CharacterListContainer>
{list.map((item: Character, i: number) => {
return (
<article key={i}>
<article key={i} onClick={() => handleClick(item.id)}>
<img src={item.image} alt={item.name} />

<div>
<p className="species">{item.species}</p>
<h1>{item.name}</h1>
<h4>{item.name}</h4>
<p className="description">
<span style={{ color: getStatusColor(item.status) }} className="status">
{item.status}
Expand All @@ -55,7 +63,7 @@ const CharacterList = (): JSX.Element => {
</article>
);
})}
{pagination !== undefined && <Pagination pagination={pagination} />}
{/* {pagination !== undefined && <Pagination pagination={pagination} />} */}
</CharacterListContainer>
);
};
Expand Down
48 changes: 48 additions & 0 deletions src/components/Modal/Modal.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from 'styled-components';

export const ModalContainer = styled.div`
position: fixed;
z-index: 1;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
cursor: default;
.content {
background-color: white;
position: absolute;
top: 20%;
left: 19%;
width: 60%;
padding: 20rem;
border-radius: 5rem;
.close {
cursor: pointer;
display: flex;
justify-content: flex-end;
}
.profile {
align-items: center;
display: flex;
justify-content: center;
img {
border-radius: 50%;
flex: 1 1;
margin-right: 15rem;
}
.profile-details {
flex: 2 1;
h1 {
font-weight: bold;
color: green;
font-size: 26rem;
margin-bottom: 25rem;
text-transform: uppercase;
}
}
}
}
`;
96 changes: 96 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useEffect, useState } from 'react';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ModalContainer } from './Modal.style';
import { useDispatch, useSelector } from 'react-redux';
import { modalStatus, selectCharacter } from '../../store/characters/action';
import { GlobalState } from '../../store/store';
import { Character } from '../../interfaces/character';
import ApiProvider from '../../lib/api-provider';
import { EpisodeInformation } from '../../interfaces/episode-information';

const Modal = (): JSX.Element => {
const dispatch = useDispatch();
const { getEpisodeInformation } = ApiProvider();
const [character, setCharacter] = useState<Character>();
const [episode, setEpisode] = useState<EpisodeInformation>();
const store = useSelector((state: GlobalState) => state.charactersState);

useEffect(() => {
const found = store.response.results.find((i) => i.id === store.selected);

if (found) {
if (found.episode.length > 0) {
getEpisodeInformation(found.episode[0]).then((res) => {
setEpisode(res);
});
}
setCharacter(found);
}
}, [store.selected]);

const handleClick = (): void => {
dispatch(modalStatus(false));
dispatch(selectCharacter(0));
};

const renderEpisodeInformation = (): JSX.Element => {
return (
<>
<li>
<strong>First episode:</strong> {episode?.name}
</li>
<li>
<strong>Episode Number:</strong> {episode?.episode}
</li>
<li>
<strong>Air date:</strong> {episode?.air_date}
</li>
</>
);
};

return (
<ModalContainer>
<div className="content">
<span className="close" onClick={() => handleClick()}>
<FontAwesomeIcon icon={faTimes} />
</span>
{character !== undefined && (
<div className="profile">
<img src={character.image} alt={character.name} />
<div className="profile-details">
<h1>Character Details</h1>
<ul>
<li>
<strong>Name:</strong> {character.name}
</li>
<li>
<strong>Status:</strong> {character.status}
</li>
<li>
<strong>Gender:</strong> {character.gender}
</li>
<li>
<strong>Species:</strong> {character.species}
</li>
<li>
<strong>Location:</strong> {character.location?.name}
</li>
<li>
<strong>Originally From:</strong> {character.origin?.name}
</li>
<li>
<strong>Episodes Count:</strong> {character.episode.length}
</li>
{episode !== undefined && renderEpisodeInformation()}
</ul>
</div>
</div>
)}
</div>
</ModalContainer>
);
};

export default Modal;
9 changes: 9 additions & 0 deletions src/interfaces/episode-information.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type EpisodeInformation = {
id: number;
name: string;
air_date: string;
episode: string;
characters: string[];
url: string;
created: string;
};
19 changes: 17 additions & 2 deletions src/lib/api-provider.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import axios, { AxiosResponse } from 'axios';
import { useDispatch } from 'react-redux';
import { CharacterResponse } from '../interfaces/character-response';
import { EpisodeInformation } from '../interfaces/episode-information';
import { updateData } from '../store/characters/action';

const ApiProvider = () => {
const API_KEY = process.env.REACT_APP_API || '';
const dispatch = useDispatch();

const getCharacters = async (searchTerm = ''): Promise<CharacterResponse> => {
const getCharacters = async (searchTerm = '', page = 1): Promise<void> => {
console.log('updating search', searchTerm);

const response = await axios
await axios
.get(`${API_KEY}/character/`, {
params: {
name: searchTerm,
page: page,
},
})
.then((res: AxiosResponse<any>) => {
Expand All @@ -24,12 +26,25 @@ const ApiProvider = () => {
.catch((error) => {
console.log(error);
});
};

const getEpisodeInformation = async (url: string): Promise<EpisodeInformation> => {
const response = await axios
.get(`${url}`)
.then((res: AxiosResponse<any>) => {
console.log(`Status: ${res.status}`);
return res.data;
})
.catch((error) => {
console.log(error);
});

return response;
};

return {
getCharacters,
getEpisodeInformation,
};
};

Expand Down
5 changes: 5 additions & 0 deletions src/store/characters/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ import { CharactersType } from './type';

export const updateData = (result: CharacterResponse): PayloadAction<string, CharacterResponse> =>
action(CharactersType.UPDATE, result);

export const modalStatus = (status: boolean): PayloadAction<string, boolean> =>
action(CharactersType.MODAL_STATUS, status);

export const selectCharacter = (id: number): PayloadAction<string, number> => action(CharactersType.SELECTED, id);
12 changes: 6 additions & 6 deletions src/store/characters/reducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ describe('Test Suite for Characters Store Reducer', () => {
payload: mock,
};

expect(charactersReducer(INITIAL_STATE, updateAction).results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).info.nextPage).toEqual(1);
expect(charactersReducer(INITIAL_STATE, updateAction).response.results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).response.info.nextPage).toEqual(1);
});

it('should update store by adding current characters list, and return 1 for next page if next does not contain page', () => {
Expand All @@ -48,8 +48,8 @@ describe('Test Suite for Characters Store Reducer', () => {
payload: mock,
};

expect(charactersReducer(INITIAL_STATE, updateAction).results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).info.nextPage).toEqual(1);
expect(charactersReducer(INITIAL_STATE, updateAction).response.results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).response.info.nextPage).toEqual(1);
});

it('should update store by adding current characters list, and return 3 for next page', () => {
Expand All @@ -71,7 +71,7 @@ describe('Test Suite for Characters Store Reducer', () => {
payload: mock,
};

expect(charactersReducer(INITIAL_STATE, updateAction).results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).info.nextPage).toEqual(3);
expect(charactersReducer(INITIAL_STATE, updateAction).response.results.length).toEqual(0);
expect(charactersReducer(INITIAL_STATE, updateAction).response.info.nextPage).toEqual(3);
});
});
22 changes: 13 additions & 9 deletions src/store/characters/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Reducer } from 'redux';
import { CharacterResponse } from '../../interfaces/character-response';
import { CharactersType, INITIAL_STATE } from './type';
import { CharacterStore, CharactersType, INITIAL_STATE } from './type';

type CharactersReducer = {
type: string;
Expand All @@ -19,21 +18,26 @@ const getNextPage = (nextUrl: string | null): number => {
return +split[1];
};

const charactersReducer: Reducer<CharacterResponse> = (
state: CharacterResponse = INITIAL_STATE,
const charactersReducer: Reducer<CharacterStore> = (
state: CharacterStore = INITIAL_STATE,
action: CharactersReducer,
) => {
switch (action.type) {
case CharactersType.UPDATE:
return {
...state,
results: action.payload.results,
info: {
...action.payload.info,
nextPage: getNextPage(action.payload.info.next),
response: {
results: action.payload.results,
info: {
...action.payload.info,
nextPage: getNextPage(action.payload.info.next),
},
},
};

case CharactersType.MODAL_STATUS:
return { ...state, modalStatus: action.payload };
case CharactersType.SELECTED:
return { ...state, selected: action.payload };
default:
return { ...state };
}
Expand Down
Loading

0 comments on commit 0a7bf7e

Please sign in to comment.