├── public ├── images │ ├── pokeball.png │ ├── pokenext.png │ ├── unknown-pokemon.png │ ├── instagram.svg │ └── github.svg └── gifs │ └── pikachu-suprised.gif ├── jsconfig.json ├── postcss.config.js ├── src ├── hooks │ ├── capitalize.jsx │ └── getType.jsx ├── pages │ ├── _app.jsx │ ├── _document.jsx │ ├── about.jsx │ ├── index.jsx │ └── pokemon │ │ └── [pokemonId].jsx ├── components │ ├── UnkownPokemon.jsx │ ├── PokemonDetails.jsx │ └── Navbar.jsx └── styles │ └── globals.css ├── next.config.js ├── tailwind.config.js ├── .gitignore ├── package.json └── README.md /public/images/pokeball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinLyf/pokenext/HEAD/public/images/pokeball.png -------------------------------------------------------------------------------- /public/images/pokenext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinLyf/pokenext/HEAD/public/images/pokenext.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/gifs/pikachu-suprised.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinLyf/pokenext/HEAD/public/gifs/pikachu-suprised.gif -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/images/unknown-pokemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinLyf/pokenext/HEAD/public/images/unknown-pokemon.png -------------------------------------------------------------------------------- /src/hooks/capitalize.jsx: -------------------------------------------------------------------------------- 1 | export default function capitalize(string) { 2 | return string.charAt(0).toUpperCase() + string.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | images: { 5 | domains: ["raw.githubusercontent.com"], 6 | }, 7 | } 8 | 9 | module.exports = nextConfig 10 | -------------------------------------------------------------------------------- /src/pages/_app.jsx: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css' 2 | import Navbar from "../components/Navbar"; 3 | export default function App({ Component, pageProps }) { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | ) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx}", 5 | "./pages/**/*.{js,ts,jsx,tsx}", 6 | "./components/**/*.{js,ts,jsx,tsx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | } -------------------------------------------------------------------------------- /public/images/instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/_document.jsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | Pokenext 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /public/images/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pokenext", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@headlessui/react": "^1.7.13", 13 | "@heroicons/react": "^2.0.17", 14 | "next": "13.3.0", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "react-loader-spinner": "^5.3.4" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^10.4.14", 21 | "postcss": "^8.4.21", 22 | "tailwindcss": "^3.3.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/UnkownPokemon.jsx: -------------------------------------------------------------------------------- 1 | export default function UnknownPokemon() { 2 | return ( 3 |
4 | unknown pokemon 9 |
10 |

unknown

11 |
12 | 13 |
14 |

unknown

15 | 16 |
17 |

unknown

18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/getType.jsx: -------------------------------------------------------------------------------- 1 | export default function getType(type) { 2 | switch (type) { 3 | case "water": 4 | return "water"; 5 | case "ice": 6 | return "ice"; 7 | case "fire": 8 | return "fire"; 9 | case "electric": 10 | return "electric"; 11 | case "grass": 12 | return "grass"; 13 | case "poison": 14 | return "poison"; 15 | case "rock": 16 | return "rock"; 17 | case "ground": 18 | return "ground"; 19 | case "steel": 20 | return "steel"; 21 | case "fairy": 22 | return "fairy"; 23 | case "bug": 24 | return "bug"; 25 | case "normal": 26 | return "normal"; 27 | case "fighting": 28 | return "fighting"; 29 | case "psychic": 30 | return "psychic"; 31 | case "ghost": 32 | return "ghost"; 33 | case "dark": 34 | return "dark"; 35 | case "dragon": 36 | return "dragon"; 37 | default: 38 | return "unknown"; 39 | } 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Pokedex

2 | 3 | ![pokenext](/public/images/pokenext.png) 4 | 5 | ## 💻 Description 6 | 7 |

I have created an amazing Pokedex using Next.js and Tailwind CSS technologies, and integrating the powerful PokeAPI to provide real-time information on all Pokemon species.

8 | 9 | ## 📖 About 10 | 11 |

Creating this Pokedex was a great way to improve my knowledge of Next.js and Tailwind CSS, as well as learn more about integrating APIs into web applications. By using PokeAPI, I was able to deepen my understanding of how to work with external data sources in web development.

12 | 13 | ## 📌 Technologies Used 14 | 15 | - React, version: 18.2.0 16 | - Next, version: 13.3.0 17 | - Tailwind, version: 3.3.1 18 | 19 | ## ⌨ How to use? 20 | 21 | 1. In this first part you will clone the repository: 22 | ```bash 23 | git clone https://github.com/kevinLyf/pokenext.git 24 | ``` 25 | 2. Next you will enter the directory: 26 | ```bash 27 | cd pokenext 28 | ``` 29 | 3. The next step is to install the dependencies: 30 | ```bash 31 | npm i 32 | ``` 33 | 34 | 4. Run an application for development mode: 35 | ```bash 36 | npm run dev 37 | ``` 38 | 39 | ## 🧑‍🚀🚀 Contribution 40 | 41 |

Pull requests are welcome. For major changes, open a competition question first what you would like to change.

42 | 43 | ## 👩‍💻 Author 44 | 45 |

Developed by Kevin Feitosa

46 | -------------------------------------------------------------------------------- /src/pages/about.jsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return ( 3 |
4 | pokemon gif 9 |
10 |

Hello Everyone!

11 |

12 | I created this Pokédex using Next.js and Tailwind CSS. To populate the 13 | Pokédex with data, I used the PokeAPI API. By using the PokeAPI API, I 14 | was able to get all the information I needed for each Pokemon, 15 | including name, type, and stats. This information has been organized 16 | and displayed on the Pokédex page, allowing users to easily navigate 17 | and learn more about each Pokémon. Overall, building a Pokédex using 18 | Next.js and Tailwind CSS was a fun and rewarding project. It allowed 19 | me to showcase my web development skills while satisfying my thirst 20 | for knowledge.😎 21 |

22 |
23 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/PokemonDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import capitalize from "../hooks/capitalize"; 3 | import getType from "@/hooks/getType"; 4 | import UnknownPokemon from "./UnkownPokemon"; 5 | import Link from "next/link"; 6 | 7 | function PokemonDetails({ pokemonName, isSearch, setIsSearch }) { 8 | const [pokemonData, setPokemonData] = useState(null); 9 | 10 | useEffect(() => { 11 | if (isSearch) { 12 | fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`) 13 | .then((response) => response.json()) 14 | .then((data) => setPokemonData(data)) 15 | .catch((error) => console.error(error)); 16 | 17 | setIsSearch(false); 18 | } 19 | }, [pokemonName, isSearch]); 20 | 21 | if (!pokemonData) { 22 | return ; 23 | } 24 | 25 | return ( 26 | 27 |
30 | e.target.src = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemonData.id}.png`} 34 | alt={pokemonData.name} 35 | /> 36 | 37 |
38 |

{capitalize(pokemonData.name)}

39 |
40 | 41 |
42 |

{pokemonData.id}

43 | 44 |
45 |

50 | {pokemonData.types[0].type.name} 51 |

52 | 53 | {pokemonData.types[1]?.type.name && ( 54 |

59 | {pokemonData.types[1].type.name} 60 |

61 | )} 62 |
63 |
64 |
65 | 66 | ); 67 | } 68 | 69 | export default PokemonDetails; 70 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .icon-search { 6 | @apply absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none; 7 | } 8 | 9 | .input-search { 10 | @apply block w-full p-4 pl-10 text-sm outline-none text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-gray-400 focus:border-gray-400; 11 | } 12 | 13 | .button-search { 14 | @apply text-white absolute right-2.5 bottom-2.5 bg-red-500 hover:bg-red-600 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-4 py-2 cursor-pointer; 15 | } 16 | 17 | .pokemon-wrapper { 18 | @apply mx-auto max-w-7xl grid place-items-center gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 p-4 sm:px-6 lg:px-8; 19 | } 20 | 21 | .pokemon-card { 22 | @apply w-full h-60 flex items-center justify-center bg-green-400 flex-col p-3 text-white hover:brightness-95 cursor-pointer drop-shadow rounded; 23 | } 24 | .pokemon-card-body { 25 | @apply w-full flex items-center justify-between; 26 | } 27 | 28 | .pokemon-card-id { 29 | @apply text-xl font-medium text-white; 30 | } 31 | 32 | .pokemon-card-wrapper { 33 | @apply flex items-center gap-3; 34 | } 35 | .pokemon-card-type { 36 | @apply truncate max-w-full py-1 px-2 text-lg shadow-sm text-white bg-white brightness-105 rounded; 37 | } 38 | 39 | .div-btn-load { 40 | @apply w-full h-full flex items-center justify-center my-4 px-4; 41 | } 42 | 43 | .btn-load { 44 | @apply w-full md:w-2/6 flex items-center justify-center py-3 sm:py-2 px-4 text-xl text-white font-medium bg-red-500 rounded-sm hover:bg-red-600 whitespace-nowrap transition-all; 45 | } 46 | 47 | .grass { 48 | @apply bg-green-400; 49 | } 50 | 51 | .electric { 52 | @apply bg-yellow-400; 53 | } 54 | 55 | .water { 56 | @apply bg-blue-400; 57 | } 58 | 59 | .ice { 60 | @apply bg-blue-600; 61 | } 62 | 63 | .fire { 64 | @apply bg-red-500; 65 | } 66 | 67 | .rock { 68 | @apply bg-orange-400; 69 | } 70 | 71 | .ground { 72 | @apply bg-orange-600; 73 | } 74 | 75 | .poison { 76 | @apply bg-purple-400; 77 | } 78 | 79 | .fairy { 80 | @apply bg-pink-400; 81 | } 82 | 83 | .bug { 84 | @apply bg-emerald-400; 85 | } 86 | 87 | .normal { 88 | @apply bg-yellow-600; 89 | } 90 | 91 | .fighting { 92 | @apply bg-yellow-700; 93 | } 94 | 95 | .psychic { 96 | @apply bg-pink-500; 97 | } 98 | 99 | .ghost { 100 | @apply bg-gray-500; 101 | } 102 | 103 | .dark { 104 | @apply bg-slate-800; 105 | } 106 | 107 | .dragon { 108 | @apply bg-red-600; 109 | } 110 | 111 | .steel { 112 | @apply bg-slate-400; 113 | } 114 | 115 | .unknown { 116 | @apply bg-zinc-700; 117 | } 118 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { Disclosure } from "@headlessui/react"; 2 | import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; 3 | import { useRouter } from "next/router"; 4 | 5 | function classNames(...classes) { 6 | return classes.filter(Boolean).join(" "); 7 | } 8 | 9 | export default function Navbar() { 10 | const router = useRouter(); 11 | 12 | const navigation = [ 13 | { name: "Home", href: "/", current: router.pathname === "/" }, 14 | { name: "About", href: "/about", current: router.pathname === "/about" }, 15 | ]; 16 | 17 | return ( 18 | 19 | {({ open }) => ( 20 | <> 21 |
22 |
23 |
24 | {/* Mobile menu button*/} 25 | 26 | Open main menu 27 | {open ? ( 28 | 33 |
34 |
35 |
36 | Your Company 41 | Your Company 46 |
47 |
48 |
49 | {navigation.map((item) => ( 50 | 61 | {item.name} 62 | 63 | ))} 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 |
72 | {navigation.map((item) => ( 73 | 85 | {item.name} 86 | 87 | ))} 88 |
89 |
90 | 91 | )} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/pages/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import getType from "../hooks/getType"; 3 | import capitalize from "../hooks/capitalize"; 4 | import PokemonDetails from "../components/PokemonDetails"; 5 | import { ThreeDots } from "react-loader-spinner"; 6 | import Link from "next/link"; 7 | 8 | function App() { 9 | const [pokemons, setPokemons] = useState([]); 10 | const [pokemonAmount, setPokemonAmount] = useState(16); 11 | const [isLoading, setIsLoading] = useState(false); 12 | const [pokemonName, setPokemonName] = useState(""); 13 | const [isSearch, setIsSearch] = useState(false); 14 | 15 | const searchHandle = (e) => { 16 | e.preventDefault(); 17 | setIsSearch(true); 18 | }; 19 | 20 | useEffect(() => { 21 | const fetchPokemons = async () => { 22 | setIsLoading(true); 23 | const response = await fetch( 24 | `https://pokeapi.co/api/v2/pokemon?limit=${pokemonAmount}` 25 | ); 26 | const data = await response.json(); 27 | const results = data.results; 28 | 29 | const pokemonData = await Promise.all( 30 | results.map(async (pokemon) => { 31 | const response = await fetch(pokemon.url); 32 | const data = await response.json(); 33 | const types = data.types.map((type) => type.type.name); 34 | return { 35 | id: data.id, 36 | name: data.name, 37 | types: types, 38 | }; 39 | }) 40 | ); 41 | 42 | setPokemons(pokemonData); 43 | setIsLoading(false); 44 | }; 45 | 46 | fetchPokemons(); 47 | }, [pokemonAmount]); 48 | 49 | 50 | return ( 51 |
52 |
53 | 58 |
59 | 62 |
63 |
64 | 74 |
75 | setPokemonName(e.target.value)} 79 | placeholder="Pokemon name or id" 80 | className="input-search" 81 | required 82 | /> 83 | 86 |
87 |
88 |
89 | 90 |
91 |
92 | {pokemons.map((pokemon) => ( 93 | 98 |
99 | 103 | (e.target.src = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`) 104 | } 105 | alt={pokemon.name} 106 | /> 107 |
108 |

109 | {capitalize(pokemon.name)} 110 |

111 |
112 |
113 |

{pokemon.id}

114 |
115 | {pokemon.types.map((type, index) => ( 116 |

122 | {type} 123 |

124 | ))} 125 |
126 |
127 |
128 | 129 | ))} 130 |
131 |
132 | 133 |
134 | 154 |
155 |
156 | ); 157 | } 158 | 159 | export default App; 160 | -------------------------------------------------------------------------------- /src/pages/pokemon/[pokemonId].jsx: -------------------------------------------------------------------------------- 1 | import capitalize from "@/hooks/capitalize"; 2 | import getType from "@/hooks/getType"; 3 | 4 | export async function getStaticPaths() { 5 | const maxPokemons = 1008; 6 | 7 | const res = await fetch( 8 | `https://pokeapi.co/api/v2/pokemon/?limit=${maxPokemons}` 9 | ); 10 | const data = await res.json(); 11 | 12 | const paths = data.results.map((pokemon, index) => { 13 | return { 14 | params: { pokemonId: (index + 1).toString() }, 15 | }; 16 | }); 17 | 18 | return { 19 | paths, 20 | fallback: false, 21 | }; 22 | } 23 | 24 | export async function getStaticProps(context) { 25 | const id = context.params.pokemonId; 26 | 27 | const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`); 28 | const data = await res.json(); 29 | 30 | const abilities = await Promise.all( 31 | data.abilities.map(async (ability) => { 32 | const response = await fetch( 33 | `https://pokeapi.co/api/v2/ability/${ability.ability.name}` 34 | ); 35 | const info = await response.json(); 36 | return { name: ability.ability.name, info }; 37 | }) 38 | ); 39 | 40 | return { 41 | props: { pokemon: data, abilities }, 42 | }; 43 | } 44 | 45 | export default function PokemonId({ pokemon, abilities }) { 46 | 47 | return ( 48 |
49 |
50 |
51 | 55 | (e.target.src = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`) 56 | } 57 | alt={pokemon.name} 58 | /> 59 |
60 |

61 | {capitalize(pokemon.name)} 62 |

63 |
64 |
65 |

{pokemon.id}

66 |
67 |
68 |

73 | {pokemon.types[0].type.name} 74 |

75 | 76 | {pokemon.types[1]?.type.name && ( 77 |

82 | {pokemon.types[1].type.name} 83 |

84 | )} 85 |
86 |
87 |
88 |
89 | 90 |
91 |

Status

92 | 93 |
94 |
95 |
96 |

HP

97 | 98 | {pokemon.stats[0].base_stat} 99 | 100 |
101 | 102 |
103 |
112 |
113 |
114 | 115 |
116 |
117 |

118 | Attack 119 |

120 | 121 | {pokemon.stats[1].base_stat} 122 | 123 |
124 | 125 |
126 |
135 |
136 |
137 | 138 |
139 |
140 |

141 | Defense 142 |

143 | 144 | {pokemon.stats[2].base_stat} 145 | 146 |
147 | 148 |
149 |
158 |
159 |
160 | 161 |
162 |
163 |

164 | Speed 165 |

166 | 167 | {pokemon.stats[5].base_stat} 168 | 169 |
170 | 171 |
172 |
181 |
182 |
183 | 184 |
185 |
186 |

187 | Special Attack 188 |

189 | 190 | {pokemon.stats[3].base_stat} 191 | 192 |
193 | 194 |
195 |
204 |
205 |
206 | 207 |
208 |
209 |

210 | Special Defense 211 |

212 | 213 | {pokemon.stats[4].base_stat} 214 | 215 |
216 | 217 |
218 |
227 |
228 |
229 |
230 |
231 | 232 |
233 |

Abilities

234 | 235 |
236 | {abilities.map((ability) => ( 237 |
241 |
242 |

{capitalize(ability.name)}

243 |
244 | 245 | {ability.info.effect_entries 246 | .filter((effect) => effect.language.name.includes("en")) 247 | .map((effect, index) => ( 248 |

249 | {effect.effect} 250 |

251 | ))} 252 |
253 | ))} 254 |
255 |
256 |
257 |
258 | ); 259 | } 260 | --------------------------------------------------------------------------------