Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
storybook-static
dist-ssr
*.local

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

*storybook.log
storybook-static
8 changes: 8 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
17 changes: 17 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-mcp"
],
"framework": "@storybook/react-vite"
};
export default config;
21 changes: 21 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Preview } from '@storybook/react-vite'

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo'
}
},
};

export default preview;
159 changes: 129 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,129 @@
## 🎯 Objetivo
Nesse desafio para Frontend, você deve construir uma aplicação web para listar e visualizar detalhes dos usuários, buscando os dados de uma API.


## 🎨 Design
[Acesse aqui](https://www.figma.com/design/d7tffwO8l1hXxjH8fxdaZH/Desafio-Frontend---Find-People?node-id=0-1&p=f&t=8y4o21BIsLtoqOLg-0) o design do Figma desse desafio.

## ✅ Requisitos
- [ ] Deve listar usuários, buscando da API [randomuser.me](randomuser.me )
- [ ] Deve ser possível **visualizar o perfil de um usuário** específico
- [ ] Deve **paginar** a lista de usuários
- [ ] Deve ser apresentado **10 usuários por página**
- [ ] Deve ser possível pesquisar usuários pelo **primeiro nome**
- [ ] Deve ser possível pesquisar usuários pelo **último nome**
- [ ] Deve ser possível pesquisar usuários pela **idade**
- [ ] Deve conter **teste de todos os componentes**

## ⚙️ Stack obrigatória
- [React com Vite](https://vite.dev/guide/)
- [TypeScript](https://www.typescriptlang.org/)
- [Sass](https://sass-lang.com/)
- [React Router](https://reactrouter.com/)
- [Storybook](https://storybook.js.org/)
- [Motion](https://motion.dev/)
- [React Query](https://tanstack.com/query/latest)
- [Jest](https://jestjs.io/)
- [React Testing Library](https://testing-library.com/)

## 📅 Prazo para finalizar
Você tem 5 dias para finalizar e entregar o desafio.
# Find People

Uma aplicação web para listar e visualizar detalhes de usuários, consumindo dados da [Random User API](https://randomuser.me/). Projeto desenvolvido como desafio técnico de Front-End.

---

## Stack

| Tecnologia | Versão |
|---|---|
| React + Vite | 19.x / 8.x |
| TypeScript | 6.x |
| React Router | 7.x |
| TanStack React Query | 5.x |
| Motion | 12.x |
| Sass | 1.x |
| Jest | 30.x |
| React Testing Library | 16.x |
| Storybook | 10.x |

---

## Funcionalidades

### Home
- **Tabela de usuários** com colunas: ID, Avatar, First Name, Last Name, Title, Date, Age, Actions
- **Busca em tempo real** por primeiro nome, último nome ou idade
- **Paginação** com 10 usuários por página e navegação por círculos numerados
- **Estado vazio** — mensagem "Nenhum resultado encontrado" quando a busca não retorna dados
- **Loading e erro** — feedback visual para carregamento e falhas de rede

### Perfil do Usuário (`/user/:id`)
- Avatar, nome completo e título
- Sistema de **abas** com três seções:
- **Info** — First Name, Last Name, Title, Date, Age
- **Location** — Street, City, State, Country, Postcode + mapa interativo
- **Login** — Username, UUID, Email, Phone, Cell, Gender, Nationality
- **Mapa com geolocalização** via Google Maps Embed, buscando pelo endereço real do usuário

### Design System

| Elemento | Valor |
|---|---|
| Fundo da página | `#171717` (dark) / `#f0f0f0` (light) |
| Fundo dos cards | `#171717` (dark) / `#ffffff` (light) |
| Cor de destaque | `#811ac0` |
| Texto principal | `#f1f1f1` (dark) / `#1a1a1a` (light) |
| Texto secundário | `#a0a0a0` (dark) / `#666` (light) |
| Borda | `#333` (dark) / `#ddd` (light) |
| Max-width do conteúdo | 900px |
| Border-radius dos cards | 8px |

---

## Melhorias Implementadas

### 🌗 Modo Claro / Escuro
Toggle no canto superior direito que alterna entre os dois temas. A preferência é persistida no `localStorage` e todas as cores do sistema são gerenciadas via **CSS Custom Properties**, garantindo transição suave entre os temas sem necessidade de reload.

### 🗺️ Mapa Interativo na Aba Location
Ao acessar a aba **Location** no perfil do usuário, um mapa é renderizado dinamicamente utilizando o **Google Maps Embed**. O mapa busca automaticamente pelo endereço completo (rua, cidade, país) e exibe um **pino de localização** preciso, permitindo visualizar onde o usuário está localizado.

### 🖼️ Foto na Tabela de Usuários
Cada linha da tabela exibe o **thumbnail do avatar** do usuário ao lado do nome, facilitando a identificação visual. A coluna de avatar é mantida mesmo na versão **responsiva para mobile**.

### 📱 Responsividade
Em dispositivos móveis, a tabela reduz para apenas **ID, Avatar e First Name**, e a paginação oculta os números intermediários, mantendo apenas as setas de navegação.

---

## Como Rodar

```bash
# Instalar dependências
npm install

# Servidor de desenvolvimento
npm run dev

# Build de produção
npm run build

# Testes
npm test

# Storybook
npm run storybook
npm run build-storybook
```

---

## Estrutura do Projeto

```
src/
├── api/ # Serviço da API randomuser.me
├── components/
│ ├── Layout/ # Header + ThemeToggle + Outlet
│ ├── UserCard/ # Linha da tabela
│ ├── UserList/ # Tabela completa com estados
│ ├── SearchBar/ # Input de busca
│ ├── Pagination/ # Navegação por páginas
│ └── UserProfile/ # Perfil com abas e mapa
├── contexts/ # ThemeContext
├── hooks/ # React Query hooks
├── pages/
│ ├── HomePage/ # Lista + busca + paginação
│ └── ProfilePage/ # Perfil do usuário
├── styles/ # Variáveis e estilos globais
└── types/ # Tipos TypeScript
```

---

## Testes

**35 testes unitários** cobrindo todos os componentes e páginas, incluindo:

- Renderização com diferentes estados (loading, erro, vazio, sucesso)
- Interações do usuário (cliques, digitação, mudança de abas)
- Navegação entre rotas
- Alternância de tema
- Acessibilidade (roles, labels, aria-*)

```bash
npm test # Executa todos os testes
npm test:watch # Modo watch
```
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Find People</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
13 changes: 13 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.test.json',
}],
},
moduleNameMapper: {
'\\.(css|scss)$': 'identity-obj-proxy',
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
testMatch: ['**/*.test.tsx', '**/*.test.ts'],
};
Loading