Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5851e73
chore: initial commit
Sep 9, 2025
8810f3c
chore: setup vite + react + ts and dependencies
Sep 9, 2025
6604f25
chore: setup eslint, prettier and envs
Sep 9, 2025
133c0aa
chore: husky, setup pré commit, lint/prettier and tests
Sep 9, 2025
8c6d4f5
feat: refactor ESLint configuration, remove unused CSS, and implement…
Sep 9, 2025
f203904
feat:implement Redux Toolkit and display Pokémon list with pagination
Sep 10, 2025
fa35c95
feat:implement Redux Toolkit and display Pokémon list with pagination
Sep 10, 2025
0f4057a
feat: refactor project structure, implement Redux for state managemen…
Sep 11, 2025
c767575
feat: implement API functions for fetching Pokémon by ID and name, an…
Sep 11, 2025
5fc197d
feat: add new Pokémon icons (rock, steel, water) and implement search…
Sep 13, 2025
ec97793
feature: Refactor Statistics page styles and add new icons/ Created s…
Sep 13, 2025
bf7c9fd
feat: implement layout structure with Header and Footer components; r…
Sep 13, 2025
0d7f472
feat: add GitHub and LinkedIn icons to Footer; update styles and cont…
Sep 13, 2025
a12853e
feat: add Jest configuration and testing setup
Sep 14, 2025
6751f91
feat: add tests for HeaderCarousel component and update theme structure
Sep 14, 2025
aef1702
feat: add unit tests for various components including Layout, Pokemon…
Sep 14, 2025
feb34cf
feat:add ErrorMessage and Loading components to Home and Statistics p…
Sep 14, 2025
037c833
feat: update theme colors and refactor components to use theme-based …
Sep 14, 2025
bcbaf69
feat: rename Wrapper to Container in Carousel, PokemonCard, SearchBar…
Sep 14, 2025
bcb2983
feat: Added Cypress version 15.2.0 for testing.
Sep 14, 2025
40a4e7f
feat: add left and right SVG icons; enhance SimpleButton with new pro…
Sep 14, 2025
9222f35
feat: update README with project details and setup instructions; clea…
Sep 14, 2025
20d5270
Update README.md
Landim013 Sep 14, 2025
5c1fbeb
Update main.tsx
Landim013 Sep 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

VITE_POKEAPI_BASE_URL=https://pokeapi.co/api/v2/
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# envs locais
.env
.env.*.local
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules

6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"printWidth": 100
}
203 changes: 151 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,151 @@
### Would you like to work with us? Apply [here](https://looqbox.gupy.io/)!

# Looqbox FrontEnd Challenge
![Looqbox](https://github.com/looqbox/looqbox-frontend-challenge/blob/master/logo.png)

## Challenge
In this challenge you will need to build a **S**ingle **P**age **A**pplication using ReactJS and a provided api

We will not use anything from your project other than evaluate your skills and you are free to use it in your portfolio

## Stack
We use:
- ReactJS
- Redux
- TypeScript
- AntDesign

## Submitting
- Make a fork of this repository
- Create your branch
- ⚠️ Do a initial Commit when you start
- ⚠️ Do a final commit when you finish
- When you're done send us a pull request

# Guidelines
You need to create a Single Page Application (SPA) that displays a list of Pokémon and allows users to search for them, using the [Pokeapi](https://pokeapi.co/docs/v2). Your app must be dynamic, meaning you **must not** reload the page to show new content.

The PokeAPI was chosen for its simplicity in making requests. Since it is an open API, please **be mindful of how many requests** you make.

## Requirements:

- On the main page, include a search bar and a preloaded list of Pokémon.
- Clicking on any Pokémon should display a card, modal, or page with that Pokémon’s information.
- Typing in the search bar and pressing Enter should display the search result instead of the list.
- Your app must include at least two different routes (e.g., /home, /details — be creative!).
- Add a README file to document your project.

You may use any libraries or dependencies you like (e.g., Axios, Bootstrap, Material UI...).

## Bonus points!
- Pagination
- Error handling
- Documentation
- Linting
- Charts
- Unit Testing
- Ant Design

## Useful links
- [React docs](https://react.dev/)
- [PokeApi docs](https://pokeapi.co/docs/v2)
- [Redux](https://redux.js.org/)
# 📦 Looqbox Frontend Challenge

Aplicação desenvolvida como desafio técnico para exibir uma Pokédex interativa, com listagem, busca e detalhes de Pokémon utilizando a **PokeAPI**.

---

## 🚀 Como executar

Clone o repositório:

```bash
git clone https://github.com/Landim013/looqbox-frontend-challenge.git
cd looqbox-frontend-challenge
```

Instale as dependências:

```bash
npm install
```

Execute o projeto:

```bash
npm run dev
```

Acesse no navegador:
👉 [http://localhost:5173](http://localhost:5173)

---

## 🧪 Testes

### Testes unitários

Rodar todos os testes unitários:

```bash
npm run test
```

Rodar em modo "watch":

```bash
npm run test:watch
```

Rodar em CI (com reporter junit):

```bash
npm run test:ci
```

### Testes end-to-end (Cypress)

Suba a aplicação:

```bash
npm run dev
```

Em outro terminal, execute o Cypress:

```bash
npx cypress open
```

Ou para rodar em modo headless:

```bash
npx cypress run
```

---

## ✅ Funcionalidades

- Listagem inicial de Pokémon paginados
- Busca por nome com resultado imediato
- Exibição de detalhes do Pokémon (peso, altura, tipos e estatísticas)
- Gráficos interativos (barras e radar) das estatísticas
- Navegação entre **próximo/anterior Pokémon**
- Responsividade para mobile e desktop
- Componentes reutilizáveis com Styled Components
- Tratamento de **loading** e mensagens de erro personalizadas
- Testes unitários (Jest + Testing Library)
- Testes end-to-end (Cypress)

---

## 📊 Dados consumidos da API

A aplicação consome dados da **PokeAPI**:

- `/pokemon` → lista de Pokémon
- `/pokemon/:id` → detalhes de um Pokémon
- `/pokemon-species/:id` → descrição em diferentes idiomas

---

## 📷 Visualização da aplicação

💻 **Página inicial (listagem + busca)**
<img width="1686" height="1156" alt="image" src="https://github.com/user-attachments/assets/50e6a74d-2284-42ef-b798-5c2a60e64d83" />


📄 **Página de detalhes do Pokémon**
<img width="1666" height="1282" alt="image" src="https://github.com/user-attachments/assets/cb02997f-aa96-46da-9adb-4d11df5c1bac" />


📊 **Gráfico de estatísticas**
<img width="1608" height="1244" alt="image" src="https://github.com/user-attachments/assets/cd21adcf-cadf-4388-ab38-e0a6b4d98066" />


📱 **Versão mobile**
<img width="426" height="1205" alt="image" src="https://github.com/user-attachments/assets/3cfa68ea-2906-45d0-806a-03b11e38ccdf" />

<img width="418" height="1165" alt="image" src="https://github.com/user-attachments/assets/1646e449-8c09-4687-b97c-a44473b3c31e" />


---

## 🛠️ Tecnologias utilizadas

- [React.js](https://react.dev/)
- [Vite](https://vitejs.dev/)
- [Redux Toolkit](https://redux-toolkit.js.org/)
- [React Router DOM](https://reactrouter.com/)
- [Ant Design](https://ant.design/)
- [Styled Components](https://styled-components.com/)
- [Axios](https://axios-http.com/)
- [Jest](https://jestjs.io/) + [Testing Library](https://testing-library.com/)
- [Cypress](https://www.cypress.io/)

---

## 💡 Arquitetura

- **Modularidade**: componentes e hooks reutilizáveis
- **Escalabilidade**: Redux para gerenciar estado global
- **Responsividade**: design adaptado para telas pequenas e grandes
- **Testabilidade**: cobertura com testes unitários e E2E
- **Boas práticas**: ESLint, Prettier, Husky e lint-staged

---

## ✨ Autor

**Douglas Landim**
[LinkedIn](https://www.linkedin.com/in/douglas-landim/) | [GitHub](https://github.com/Landim013)
7 changes: 7 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
},
});
26 changes: 26 additions & 0 deletions cypress/e2e/home-and-details.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe('Home -> Details', () => {
it('renderiza a lista e navega para o detalhe do Bulbasaur', () => {
cy.intercept('GET', '**/api/v2/pokemon?*', { fixture: 'pokemon-list.json' }).as('getList');

cy.intercept('GET', '**/api/v2/pokemon/1', { fixture: 'pokemon-1.json' }).as('getBulbasaur');
cy.intercept('GET', '**/api/v2/pokemon-species/1', { fixture: 'species-1.json' }).as(
'getSpecies1',
);

cy.intercept('GET', '**/api/v2/pokemon?*', { fixture: 'pokemon-list.json' }).as('getList');

cy.intercept('GET', '**/api/v2/pokemon/bulbasaur*', { fixture: 'pokemon-1.json' }).as(
'getBulbasaurByName',
);
cy.intercept('GET', '**/api/v2/pokemon/ivysaur*', { fixture: 'pokemon-2.json' }).as(
'getIvysaurByName',
);

cy.intercept('GET', /\/api\/v2\/pokemon\/1\/?$/, { fixture: 'pokemon-1.json' }).as(
'getBulbasaurById',
);
cy.intercept('GET', /\/api\/v2\/pokemon-species\/1\/?$/, { fixture: 'species-1.json' }).as(
'getSpecies1',
);
});
});
25 changes: 25 additions & 0 deletions cypress/e2e/search-error.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// cypress/e2e/search-error.cy.ts
describe('busca com erro mostra ErrorMessage', () => {
it('mostra mensagem quando o Pokémon não existe', () => {
// lista inicial
cy.intercept('GET', '**/api/v2/pokemon?*', { fixture: 'pokemon-list.json' }).as('getList');

cy.visit('/');
cy.wait('@getList');

cy.intercept('GET', '**/api/v2/pokemon/naoexiste*', {
statusCode: 404,
body: { detail: 'Not found' },
}).as('getNotFound');

cy.get('[data-testid="search-input"] input', { timeout: 10000 })
.should('be.visible')
.type('naoexiste{enter}');

cy.wait('@getNotFound');

cy.get('[data-testid="error-message"]', { timeout: 10000 })
.should('be.visible')
.contains(/pokémon não encontrado/i);
});
});
18 changes: 18 additions & 0 deletions cypress/fixtures/bulbasaur.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": 1,
"name": "bulbasaur",
"height": 7,
"weight": 69,
"types": [
{ "slot": 1, "type": { "name": "grass", "url": "" } },
{ "slot": 2, "type": { "name": "poison", "url": "" } }
],
"stats": [
{ "base_stat": 45, "effort": 0, "stat": { "name": "hp", "url": "" } },
{ "base_stat": 49, "effort": 0, "stat": { "name": "attack", "url": "" } },
{ "base_stat": 49, "effort": 0, "stat": { "name": "defense", "url": "" } },
{ "base_stat": 45, "effort": 0, "stat": { "name": "speed", "url": "" } },
{ "base_stat": 65, "effort": 0, "stat": { "name": "special-defense", "url": "" } },
{ "base_stat": 65, "effort": 0, "stat": { "name": "special-attack", "url": "" } }
]
}
6 changes: 6 additions & 0 deletions cypress/fixtures/list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"count": 1,
"next": null,
"previous": null,
"results": [{ "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }]
}
18 changes: 18 additions & 0 deletions cypress/fixtures/pokemon-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": 1,
"name": "bulbasaur",
"height": 7,
"weight": 69,
"types": [
{ "slot": 1, "type": { "name": "grass", "url": "" } },
{ "slot": 2, "type": { "name": "poison", "url": "" } }
],
"stats": [
{ "base_stat": 45, "effort": 0, "stat": { "name": "hp", "url": "" } },
{ "base_stat": 49, "effort": 0, "stat": { "name": "attack", "url": "" } },
{ "base_stat": 49, "effort": 0, "stat": { "name": "defense", "url": "" } },
{ "base_stat": 45, "effort": 0, "stat": { "name": "speed", "url": "" } },
{ "base_stat": 65, "effort": 0, "stat": { "name": "special-defense", "url": "" } },
{ "base_stat": 65, "effort": 0, "stat": { "name": "special-attack", "url": "" } }
]
}
8 changes: 8 additions & 0 deletions cypress/fixtures/pokemon-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": 2,
"name": "ivysaur",
"height": 10,
"weight": 130,
"types": [{ "slot": 1, "type": { "name": "grass", "url": "" } }],
"stats": [{ "base_stat": 60, "effort": 0, "stat": { "name": "hp", "url": "" } }]
}
9 changes: 9 additions & 0 deletions cypress/fixtures/pokemon-list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"count": 2,
"next": null,
"previous": null,
"results": [
{ "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" },
{ "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }
]
}
14 changes: 14 additions & 0 deletions cypress/fixtures/species-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"flavor_text_entries": [
{
"flavor_text": "Uma semente estranha foi plantada em suas costas.",
"language": { "name": "pt" },
"version": { "name": "qualquer" }
},
{
"flavor_text": "A strange seed was planted on its back at birth.",
"language": { "name": "en" },
"version": { "name": "red" }
}
]
}
Loading