From e329230d027b2bab39c2366e5c7b1ab93310db74 Mon Sep 17 00:00:00 2001 From: Yago Moreira Date: Tue, 13 Feb 2024 14:06:35 -0300 Subject: [PATCH 1/2] add pokemon details modal --- assets/css/pokedex.css | 141 +++++++++++++++++++++++++++++++++++-- assets/js/main.js | 73 ++++++++++++++----- assets/js/poke-api.js | 48 ++++++++----- assets/js/pokemon-modal.js | 66 +++++++++++++++++ assets/js/pokemon-model.js | 2 + index.html | 10 ++- 6 files changed, 300 insertions(+), 40 deletions(-) create mode 100644 assets/js/pokemon-modal.js diff --git a/assets/css/pokedex.css b/assets/css/pokedex.css index 59eef2bde..36acaf04b 100644 --- a/assets/css/pokedex.css +++ b/assets/css/pokedex.css @@ -82,18 +82,16 @@ display: flex; flex-direction: column; margin: .5rem; - padding: 1rem; - border-radius: 1rem; } -.pokemon .number { +.pokemon .pokemonNameAndId .number { color: #000; opacity: .3; text-align: right; font-size: .625rem; } -.pokemon .name { +.pokemon .pokemonNameAndId .name { text-transform: capitalize; color: #fff; margin-bottom: .25rem; @@ -104,6 +102,7 @@ flex-direction: row; align-items: center; justify-content: space-between; + width: 100%; } .pokemon .detail .types { @@ -146,6 +145,140 @@ border-radius: 1rem; } +.pokemonButton { + display: flex; + flex-direction: column; + flex-wrap: wrap; + padding: 1rem; + width: 100%; + border: none; + border-radius: 1rem; + cursor: pointer; +} + +.pokemonNameAndId { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin: 0px; +} + +.modal { + display: none; + position: fixed; + z-index: 1; + padding-top: 100px; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + } + + .modal-content { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px; + margin: auto; + padding: 20px; + border: none; + border-radius: 2rem; + width: 30%; + height: 80%; + } + + .modal-content > span { + width: 15%; + } + + .stats-content { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + width: 100%; + } + + #pokemonName { + font-family: "Anton", sans-serif; + font-weight: 400; + font-style: normal; + color: white; + text-transform: uppercase; + margin: 1rem 0 0; + } + + .stats-content > img { + width: 100%; + background-color: #fff; + box-shadow: rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px; + border-radius: 50%; + } + + .stats-content > h2 { + color: white; + text-transform: capitalize; + font-family: "Anton", sans-serif; + font-weight: 400; + font-style: normal; + font-size: 1.25rem; + } + + .pokemon-stats { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-around; + width: 100%; + } + + .stats { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + background-color: #fff; + border-radius: 0.625rem; + min-width: 3.5rem; + padding: 5px + } + + .stats > span:first-child { + color: #6a6262; + font-size: 0.625rem; + } + + .stats > span:last-child { + color: #000; + font-size: 1.25rem; + font-weight: 700; + } + + /* The Close Button */ + .close { + display: flex; + align-items: center; + justify-content: center; + color: #4f4e4e; + float: right; + font-size: 28px; + border: 2px solid #4f4e4e; + border-radius: 50%; + font-weight: bold; + } + + .close:hover, + .close:focus { + color: #000; + text-decoration: none; + border: 2px solid #000; + cursor: pointer; + } + @media screen and (min-width: 380px) { .pokemons { grid-template-columns: 1fr 1fr; diff --git a/assets/js/main.js b/assets/js/main.js index bcaa24508..9ebe35364 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,5 +1,6 @@ const pokemonList = document.getElementById('pokemonList') const loadMoreButton = document.getElementById('loadMoreButton') +let pokemonButtons; const maxRecords = 151 const limit = 10 @@ -7,32 +8,64 @@ let offset = 0; function convertPokemonToLi(pokemon) { return ` -
  • - #${pokemon.number} - ${pokemon.name} - -
    -
      - ${pokemon.types.map((type) => `
    1. ${type}
    2. `).join('')} -
    - - ${pokemon.name} -
    +
  • +
  • ` } -function loadPokemonItens(offset, limit) { - pokeApi.getPokemons(offset, limit).then((pokemons = []) => { - const newHtml = pokemons.map(convertPokemonToLi).join('') - pokemonList.innerHTML += newHtml - }) +async function loadPokemonItens(offset, limit) { + try { + const pokemons = await pokeApi.getPokemons(offset, limit); + const newHtml = pokemons.map(convertPokemonToLi).join(''); + pokemonList.innerHTML += newHtml; + return pokemons; + } catch (error) { + console.error('Error loading Pokemon items:', error); + } +} + +function getPokemonButtons () { + const pokemonButtons = document.querySelectorAll('.pokemonButton') + return pokemonButtons } loadPokemonItens(offset, limit) -loadMoreButton.addEventListener('click', () => { +const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // Verifica se houve alterações no conteúdo do elemento
      + if (mutation.type === 'childList') { + // O conteúdo da lista ordenada foi modificado + pokemonButtons = getPokemonButtons() + pokemonButtons.forEach(addPokemonButtonClickHandler); + // Aqui você pode fazer o que precisar em resposta às alterações no conteúdo da lista + } + }); +}); + +// Configura as opções para observar alterações no conteúdo da lista ordenada +const config = { childList: true, subtree: true }; + +// Inicia a observação no elemento
        com as opções configuradas +observer.observe(pokemonList, config); + + +loadMoreButton.addEventListener('click', async () => { offset += limit const qtdRecordsWithNexPage = offset + limit @@ -44,4 +77,6 @@ loadMoreButton.addEventListener('click', () => { } else { loadPokemonItens(offset, limit) } -}) \ No newline at end of file +}) + + diff --git a/assets/js/poke-api.js b/assets/js/poke-api.js index 38fbfd465..4dac36684 100644 --- a/assets/js/poke-api.js +++ b/assets/js/poke-api.js @@ -13,23 +13,39 @@ function convertPokeApiDetailToPokemon(pokeDetail) { pokemon.type = type pokemon.photo = pokeDetail.sprites.other.dream_world.front_default + pokemon.alternativePhoto = pokeDetail.sprites.other.home.front_default + pokemon.stats = pokeDetail.stats; return pokemon } -pokeApi.getPokemonDetail = (pokemon) => { - return fetch(pokemon.url) - .then((response) => response.json()) - .then(convertPokeApiDetailToPokemon) -} - -pokeApi.getPokemons = (offset = 0, limit = 5) => { - const url = `https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}` - - return fetch(url) - .then((response) => response.json()) - .then((jsonBody) => jsonBody.results) - .then((pokemons) => pokemons.map(pokeApi.getPokemonDetail)) - .then((detailRequests) => Promise.all(detailRequests)) - .then((pokemonsDetails) => pokemonsDetails) -} +pokeApi.getPokemonDetail = async (pokemon) => { + try { + const response = await fetch(pokemon.url); + const pokeDetail = await response.json(); + const convertedPokemon = convertPokeApiDetailToPokemon(pokeDetail); + return convertedPokemon; + } catch (error) { + console.error('Error fetching Pokemon detail:', error); + return null; // Ou outra maneira de lidar com o erro, como lançar uma exceção + } +}; + + +pokeApi.getPokemons = async (offset = 0, limit = 5) => { + const url = `https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}`; + + try { + const response = await fetch(url); + const jsonBody = await response.json(); + const pokemons = jsonBody.results; + + const detailRequests = pokemons.map(pokemon => pokeApi.getPokemonDetail(pokemon)); + const pokemonsDetails = await Promise.all(detailRequests); + + return pokemonsDetails; + } catch (error) { + console.error('Error fetching Pokemon data:', error); + return []; + } +}; diff --git a/assets/js/pokemon-modal.js b/assets/js/pokemon-modal.js new file mode 100644 index 000000000..f3f3cd9e4 --- /dev/null +++ b/assets/js/pokemon-modal.js @@ -0,0 +1,66 @@ +const modal = document.getElementById("myModal"); +const span = document.getElementsByClassName("close")[0]; + +async function createModal(pokemonDetail) { + const modalContent = document.getElementsByClassName('modal-content'); + modalContent[0].classList.add(`${pokemonDetail.type}`); + modal.style.display = "block"; + + const content = document.createElement('div'); + content.classList.add('stats-content'); + content.innerHTML = ` +

        ${pokemonDetail.name}

        + ${pokemonDetail.name} +

        About

        +
        +
        + HP + ${pokemonDetail.stats[0].base_stat} +
        +
        + Attack + ${pokemonDetail.stats[1].base_stat} +
        +
        + Defense + ${pokemonDetail.stats[2].base_stat} +
        +
        + Speed + ${pokemonDetail.stats[5].base_stat} +
        +
        + ` + modalContent[0].appendChild(content); + + span.onclick = function() { + modal.style.display = "none"; + modalContent[0].removeChild(content) + modalContent[0].classList.remove(`${pokemonDetail.type}`); + } + + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + modalContent[0].removeChild(content) + modalContent[0].classList.remove(`${pokemonDetail.type}`); + } + } +} + +// Adiciona um manipulador de evento de clique a cada botão Pokémon +function addPokemonButtonClickHandler(pokemonButton) { + pokemonButton.addEventListener('click', async () => { + const pokemonName = pokemonButton.querySelector('.name').innerHTML; + try { + const pokemonDetail = await pokeApi.getPokemonDetail({ + url: `https://pokeapi.co/api/v2/pokemon/${pokemonName}` + }); + if (pokemonDetail) { + await createModal(pokemonDetail); + } + } catch (error) { + console.error('Error loading Pokemon detail:', error); + } + }); +} diff --git a/assets/js/pokemon-model.js b/assets/js/pokemon-model.js index b0d17bb90..3120639a8 100644 --- a/assets/js/pokemon-model.js +++ b/assets/js/pokemon-model.js @@ -5,4 +5,6 @@ class Pokemon { type; types = []; photo; + alternativePhoto; + stats = []; } diff --git a/index.html b/index.html index 1a017821d..2ba92f19b 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ - + @@ -37,9 +37,17 @@

        Pokedex

        + + + + From 6e1fe94d63d0d6ef5086e1a8fc65cc52b54e39cc Mon Sep 17 00:00:00 2001 From: Yago Moreira Date: Tue, 13 Feb 2024 14:39:22 -0300 Subject: [PATCH 2/2] fix: mobile design --- assets/css/pokedex.css | 45 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/assets/css/pokedex.css b/assets/css/pokedex.css index 36acaf04b..0af8a2cd3 100644 --- a/assets/css/pokedex.css +++ b/assets/css/pokedex.css @@ -184,10 +184,10 @@ justify-content: center; box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px; margin: auto; - padding: 20px; + padding: 1rem; border: none; border-radius: 2rem; - width: 30%; + width: 20%; height: 80%; } @@ -265,7 +265,7 @@ justify-content: center; color: #4f4e4e; float: right; - font-size: 28px; + font-size: 2rem; border: 2px solid #4f4e4e; border-radius: 50%; font-weight: bold; @@ -283,16 +283,55 @@ .pokemons { grid-template-columns: 1fr 1fr; } + + .modal-content { + width: 60%; + margin: 0 auto; + } + + #pokemonName { + font-size: 1.25rem; + } + + .stats-content > img { + width: 80%; + } } @media screen and (min-width: 576px) { .pokemons { grid-template-columns: 1fr 1fr 1fr; } + + .modal-content { + width: 40%; + margin: 0 auto; + } + + #pokemonName { + font-size: 1.5rem; + } + + .stats-content > img { + width: 70%; + } } @media screen and (min-width: 992px) { .pokemons { grid-template-columns: 1fr 1fr 1fr 1fr; } + + .modal-content { + width: 20%; + margin: 0 auto; + } + + #pokemonName { + font-size: 2rem; + } + + .stats-content > img { + width: 100%; + } } \ No newline at end of file