diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6f3a2913e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/README.md b/README.md index 0c237c1c3..625ab5720 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # Trilha JS Developer - Pokedex + +Esse projeto da Pokédex foi desenvolvido durante o Bootcamp da [Digital Innovation One - DIO]([URL](https://web.dio.me/home)) sobre Desenvolvimento Frontend com Angular. Trata-se de uma aplicação que utiliza a [PokeApi](https://pokeapi.co/) para apresentar detalhes sobre os Pokémon, incluindo suas habilidades. O desenvolvimento da aplicação foi utilizando JavaScript, HTML e CSS. A Pokédex proporciona aos usuários a oportunidade de selecionar e explorar informações sobre um Pokémon específico. Esse projeto teve como inspiração o projeto da [Camila Correia](https://github.com/Camesis/js-developer-pokedex), onde foi compreendido e aplicando conforme desejado. + +### * Homepage +![Home](/assets/img/pokemons-home.png) + +### * Detalhe do Pokémon +![Detalhes do Pokémon](/assets/img/details-pokemon.png) + diff --git a/assets/css/details-pokemon.css b/assets/css/details-pokemon.css new file mode 100644 index 000000000..003db85d6 --- /dev/null +++ b/assets/css/details-pokemon.css @@ -0,0 +1,267 @@ +body { + overflow: hidden; +} + +h1 { + text-transform: capitalize; +} + +.poke-wrapper { + max-width: 992px; + margin: 0 auto; +} + +.content-pokemon { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} + +.content-pokemon .normal { + background-color: #a6a877; +} + +.content-pokemon .grass { + background-color: #77c850; +} + +.content-pokemon .fire { + background-color: #ee7f30; +} + +.content-pokemon .water { + background-color: #678fee; +} + +.content-pokemon .electric { + background-color: #f7cf2e; +} + +.content-pokemon .ice { + background-color: #98d5d7; +} + +.content-pokemon .ground { + background-color: #dfbf69; +} + +.content-pokemon .flying { + background-color: #a98ff0; +} + +.content-pokemon .poison { + background-color: #a040a0; +} + +.content-pokemon .fighting { + background-color: #bf3029; +} + +.content-pokemon .psychic { + background-color: #f65687; +} + +.content-pokemon .dark { + background-color: #725847; +} + +.content-pokemon .rock { + background-color: #b8a137; +} + +.content-pokemon .bug { + background-color: #a8b720; +} + +.content-pokemon .ghost { + background-color: #6e5896; +} + +.content-pokemon .steel { + background-color: #b9b7cf; +} + +.content-pokemon .dragon { + background-color: #6f38f6; +} + +.content-pokemon .fairy { + background-color: #f9aec7; +} + +.content-pokemon img { + width: 15rem; + height: 15rem; + padding: 1rem; +} + +.content-pokemon .types { + padding: 0; + margin: 0; + list-style: none; +} + +.content-pokemon .description { + margin: 2rem 3rem; + text-align: center; + padding: 0; + font-size: 0.9rem; + font-weight: 400; + color: #2d2d2d; +} + +.content-pokemon .types .type { + color: #fff; + padding: 0.25rem 0.5rem; + margin: 0.25rem 0; + min-width: 5rem; + font-size: 0.9rem; + border-radius: 1rem; + filter: brightness(1.1); + text-align: center; +} + +.pokemon-detail-footer { + width: 100%; + margin: 0rem 3rem; + background-color: #ffff; + border-radius: 50px; + height: 100dvh; +} + +.pokemon-detail-footer ul { + margin: 0 3rem; + padding: 0; + list-style: none; + text-transform: uppercase; +} + +.pokemon-detail-footer .stats-container .stats { + display: flex; + align-items: center; + gap: 1rem; +} +.pokemon-detail-footer .stats-container .stats p { + min-width: 30px; +} + +.pokemon-detail-footer .stats-container .progress-bar { + width: 100%; + height: 2rem; + background-color: #dddddd; + border-radius: 6px; + position: relative; +} +.pokemon-detail-footer .stats-container .progress-bar .progress { + height: 100%; + border-radius: 6px; + position: absolute; +} + +.pokemon-detail-footer .stats-container .stats :first-child { + flex: 1; +} + +.pokemon-detail-footer .stats-container .stats :nth-child(2) { + flex: 1; +} + +.pokemon-detail-footer .stats-container .stats :nth-child(3) { + flex: 9; +} + +#nav { + display: flex; + justify-content: space-around; + align-items: center; + gap: 1rem; + padding: 1rem; +} + +.nav-button { + background-color: #2d2d2d; + color: #ffffff; + padding: .4rem; + border-radius: 6rem; + height: 50px; + width: 100px; + display: flex; + justify-content: center; + align-items: center; + font-weight: 700; + font-size: 18px; + cursor: pointer; +} + +.navbar .normal { + background-color: #a6a877; +} + +.navbar .grass { + background-color: #77c850; +} + +.navbar .fire { + background-color: #ee7f30; +} + +.navbar .water { + background-color: #678fee; +} + +.navbar .electric { + background-color: #f7cf2e; +} + +.navbar .ice { + background-color: #98d5d7; +} + +.navbar .ground { + background-color: #dfbf69; +} + +.navbar .flying { + background-color: #a98ff0; +} + +.navbar .poison { + background-color: #a040a0; +} + +.navbar .fighting { + background-color: #bf3029; +} + +.navbar .psychic { + background-color: #f65687; +} + +.navbar .dark { + background-color: #725847; +} + +.navbar .rock { + background-color: #b8a137; +} + +.navbar .bug { + background-color: #a8b720; +} + +.navbar .ghost { + background-color: #6e5896; +} + +.navbar .steel { + background-color: #b9b7cf; +} + +.navbar .dragon { + background-color: #6f38f6; +} + +.navbar .fairy { + background-color: #f9aec7; +} \ No newline at end of file diff --git a/assets/css/global.css b/assets/css/global.css index 980e87861..08f62270b 100644 --- a/assets/css/global.css +++ b/assets/css/global.css @@ -1,8 +1,13 @@ * { - font-family: 'Roboto', sans-serif; + font-family: "Roboto", sans-serif; box-sizing: border-box; } +a { + text-decoration: none; + color: #2d2d2d; +} + body { background-color: #f6f8fc; } @@ -14,6 +19,10 @@ body { background-color: #fff; } +.content h1 { + color: #2d2d2d; +} + @media screen and (min-width: 992px) { .content { max-width: 992px; diff --git a/assets/css/pokedex.css b/assets/css/pokedex.css index 59eef2bde..784c1e27f 100644 --- a/assets/css/pokedex.css +++ b/assets/css/pokedex.css @@ -1,14 +1,21 @@ .pokemons { display: grid; grid-template-columns: 1fr; - margin: 0; - padding: 0; list-style: none; + padding: 0; + margin: 0; +} + +.logo { + display: flex; + padding: 1rem; + justify-content: center; + } .normal { background-color: #a6a877; -} +} .grass { background-color: #77c850; @@ -50,14 +57,14 @@ background-color: #f65687; } -.dark { - background-color: #725847; -} - .rock { background-color: #b8a137; } +.dark { + background-color: #725847; +} + .bug { background-color: #a8b720; } @@ -86,7 +93,7 @@ border-radius: 1rem; } -.pokemon .number { +.pokemon .numPokemon { color: #000; opacity: .3; text-align: right; @@ -94,9 +101,10 @@ } .pokemon .name { - text-transform: capitalize; + text-transform: capitalize; color: #fff; margin-bottom: .25rem; + } .pokemon .detail { @@ -139,7 +147,7 @@ .pagination button { padding: .25rem .5rem; margin: .25rem 0; - font-size: .625rem; + font-size: .900rem; color: #fff; background-color: #6c79db; border: none; @@ -149,6 +157,7 @@ @media screen and (min-width: 380px) { .pokemons { grid-template-columns: 1fr 1fr; + } } @@ -160,6 +169,7 @@ @media screen and (min-width: 992px) { .pokemons { + max-width: 992px; grid-template-columns: 1fr 1fr 1fr 1fr; } } \ No newline at end of file diff --git "a/assets/img/Pok\303\251dex_logo.png" "b/assets/img/Pok\303\251dex_logo.png" new file mode 100644 index 000000000..fbc6730a9 Binary files /dev/null and "b/assets/img/Pok\303\251dex_logo.png" differ diff --git a/assets/img/details-pokemon.png b/assets/img/details-pokemon.png new file mode 100644 index 000000000..73e0f4a35 Binary files /dev/null and b/assets/img/details-pokemon.png differ diff --git a/assets/img/pokemons-home.png b/assets/img/pokemons-home.png new file mode 100644 index 000000000..e0ef77e78 Binary files /dev/null and b/assets/img/pokemons-home.png differ diff --git a/assets/js/details-pokemon.js b/assets/js/details-pokemon.js new file mode 100644 index 000000000..8a289b293 --- /dev/null +++ b/assets/js/details-pokemon.js @@ -0,0 +1,106 @@ +const previousButton = document.getElementById('previous') +const nextButton = document.querySelector('#next') +const content = document.querySelector('.content-pokemon') +const navbar = document.querySelector('navbar') +const maxRecords = 151; + +document.addEventListener('DOMContentLoaded', (event) => { + event.preventDefault() + const pokemonIdString = new URLSearchParams(window.location.search).get('id') + const pokemonId = pokemonIdString ? parseInt(pokemonIdString, 10) : null + const id = pokemonId + + if(!id) return (window.location.pathname = '') + if(id > maxRecords) return (window.location.pathname = '') + + pokemonCurrent = id + + pokeApi.getPokemonById(id) +}) + +nextButton.addEventListener('click', (event) => { + event.preventDefault() + const pokemonIdString = new URLSearchParams(window.location.search).get('id') + const pokemonId = pokemonIdString ? parseInt(pokemonIdString, 10) : null + + if (pokemonId !== null) { + const nextPokemon = pokemonId < maxRecords ? pokemonId + 1 : maxRecords + const basePath = window.location.pathname + const newPath = `?id=${nextPokemon}` + window.location.href = basePath + newPath + } else { + console.error('O ID do Pokémon na URL não é válido ou não foi encontrado') + } +}) + +previousButton.addEventListener('click', (event) => { + event.preventDefault() + const pokemonIdString = new URLSearchParams(window.location.search).get('id') + const pokemonId = pokemonIdString ? parseInt(pokemonIdString, 10) : null + + if (pokemonId !== null) { + const nextPokemon = pokemonId > 1 ? pokemonId - 1 : 1 + const basePath = window.location.pathname + const newPath = `?id=${nextPokemon}` + window.location.href = basePath + newPath + } else { + console.error('O ID do Pokémon na URL não é válido ou não foi encontrado') + } +}) + +function convertPokemonToLi(pokemon) { + return ` + ${pokemon.name} +

${pokemon.name} #${pokemon.numPokemon}

+ +
    ${pokemon.types.map((type) => `
  1. ${type}
  2. `).join("")}
+ + + + ` +} + +pokeApi.getPokemonById = async (pokeId) => { + const url = `https://pokeapi.co/api/v2/pokemon/${pokeId}` + + try { + const response = await fetch(url) + const pokemonDetails = await response.json() + const infoSpecies = await pokeApi.getInfoSpecies(pokeId) + const pokemon = convertPokeApiDetailToPokemon(pokemonDetails, infoSpecies) + const html = convertPokemonToLi(pokemon) + + content.innerHTML += html + content.classList.add(pokemon.type) + navbar.classList.add(pokemon.type) + } catch (error) { + console.error('Falha ao recuperar informações detalhadas do Pokémon:', error) + } + } + + pokeApi.getInfoSpecies = (pokeId) => { + const url = `https://pokeapi.co/api/v2/pokemon-species/${pokeId}` + + return fetch(url) + .then((response) => response.json()) + .then((pokemon) => pokemon) + }; + + function loadPokemonItens(offset, limit) { + pokeApi.getPokemons(offset, limit).then((pokemons = []) => { + const newHtml = pokemons.map(convertPokemonToLi).join('') + content.innerHTML += newHtml + }) + } \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index bcaa24508..ab164a1af 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -3,22 +3,23 @@ const loadMoreButton = document.getElementById('loadMoreButton') const maxRecords = 151 const limit = 10 -let offset = 0; +let offset = 0 function convertPokemonToLi(pokemon) { return `
  • - #${pokemon.number} + #${pokemon.numPokemon} ${pokemon.name} + -
    -
      - ${pokemon.types.map((type) => `
    1. ${type}
    2. `).join('')} -
    +
    +
      + ${pokemon.types.map((type) => `
    1. ${type}
    2. `).join('')} +
    - ${pokemon.name} -
    + ${pokemon.name} +
    +
  • ` } @@ -44,4 +45,4 @@ 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..597c47563 100644 --- a/assets/js/poke-api.js +++ b/assets/js/poke-api.js @@ -1,35 +1,53 @@ - const pokeApi = {} -function convertPokeApiDetailToPokemon(pokeDetail) { +function convertPokeApiDetailToPokemon(pokeDetail, infoSpecies) { const pokemon = new Pokemon() - pokemon.number = pokeDetail.id + pokemon.numPokemon = pokeDetail.id pokemon.name = pokeDetail.name + pokemon.description = infoSpecies?.flavor_text_entries[0]?.flavor_text + + pokemon.stats.hp = pokeDetail.stats.find((item) => item.stat.name === 'hp') + pokemon.stats.atk = pokeDetail.stats.find((item) => item.stat.name === 'attack') + + pokemon.stats.def = pokeDetail.stats.find((item) => item.stat.name === 'defense') + pokemon.stats.sAtk = pokeDetail.stats.find((item) => item.stat.name === 'special-attack') + pokemon.stats.sDef = pokeDetail.stats.find((item) => item.stat.name === 'special-defense') + pokemon.stats.spd = pokeDetail.stats.find((item) => item.stat.name === 'speed') + const types = pokeDetail.types.map((typeSlot) => typeSlot.type.name) const [type] = types - + pokemon.types = types pokemon.type = type - + pokemon.photo = pokeDetail.sprites.other.dream_world.front_default - + return pokemon -} +} pokeApi.getPokemonDetail = (pokemon) => { return fetch(pokemon.url) - .then((response) => response.json()) - .then(convertPokeApiDetailToPokemon) + .then((response) => response.json()) + .then(convertPokeApiDetailToPokemon) } -pokeApi.getPokemons = (offset = 0, limit = 5) => { +pokeApi.getPokemons = async (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) -} + + 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("Falha ao recuperar informações sobre os Pokémon:", error) + throw error + } +} \ No newline at end of file diff --git a/assets/js/pokemon-model.js b/assets/js/pokemon-model.js index b0d17bb90..781173de1 100644 --- a/assets/js/pokemon-model.js +++ b/assets/js/pokemon-model.js @@ -1,8 +1,17 @@ class Pokemon { - number; + numPokemon; name; type; types = []; photo; -} + description; + stats = { + hp: 0, + atk: 0, + def: 0, + sAtk: 0, + sDef: 0, + spd: 0, + } +} \ No newline at end of file diff --git a/details-pokemon.html b/details-pokemon.html new file mode 100644 index 000000000..179a781bb --- /dev/null +++ b/details-pokemon.html @@ -0,0 +1,46 @@ + + + + + + + + + Pokedex | Details + + + + + + + + + + + + + + + +
    + +
    +
    + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 1a017821d..bb73d50dc 100644 --- a/index.html +++ b/index.html @@ -5,26 +5,33 @@ + + + Pokedex + - + integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w==" + crossorigin="anonymous" referrerpolicy="no-referrer" /> + - - + + +
    -

    Pokedex

    +
      @@ -32,11 +39,17 @@

      Pokedex

    + + + + + +