Clone didático da plataforma Steam desenvolvido com React 19 + Vite, aplicando os conceitos ensinados nas aulas 07 a 16.
Feed com hero banner, seções Recentes, Mais Avaliados e Populares, sidebar de gêneros e atalhos.
Busca por título e filtro por gênero em tempo real. Listagem de todos os jogos disponíveis.
Galeria de imagens, conquistas, reviews e controles de biblioteca e wishlist.
Criar, editar e excluir avaliações com nota. Média calculada dinamicamente.
Desenvolvedor cria conquistas por jogo, exibidas na página de detalhes.
Criação de jogos com gêneros e upload de imagens, edição e gerenciamento completo.
import axios from 'axios'; const api = axios.create({ baseURL: 'https://alunos-ads-api...', }); // Token injetado automaticamente api.interceptors.request.use(config => { const token = localStorage .getItem('token'); if (token) config.headers.token = token; return config; });
// Home — 2 chamadas paralelas const [destaquesRes, generosRes] = await Promise.all([ api.get('/jogos/destaques'), api.get('/generos'), ]); // GameDetails — 3 chamadas paralelas const [gameRes, statusRes, userRes] = await Promise.all([ api.get(`/jogos/${id}`), api.get(`/jogos/${id}/status`), api.get('/auth/me'), ]);
const [matricula, setMatricula] = useState(""); const [senha, setSenha] = useState(""); <input value={matricula} onChange={e => setMatricula( e.target.value )} />
function handleGenero(id) { setGeneroIds(prev => prev.includes(id) ? prev.filter( g => g !== id) : [...prev, id] ); } // Validação antes do POST if (generoIds.length === 0) { swal.fire({ icon: 'warning' }); return; }
async function handleSubmit(e) { e.preventDefault(); await api.patch( '/usuarios/me', { nome } ); // Atualiza Context global login(token, novoUser); navigate('/'); }
<Routes> <Route path="/login" element={<Login />} /> <Route path="/" element={ <ProtectedRoute> <Home /> </ProtectedRoute> } /> <Route path="/jogos/:id" element={<GameDetails />} /> <Route path="/perfil/:matricula" element={<PublicProfile />} /> </Routes>
:id e :matriculafunction ProtectedRoute({ children }) { const { token, loading } = useAuth(); if (loading) return <Spinner />; if (!token) return <Navigate to="/login" />; return children; }
token JWT + dados do usuáriolocalStorageasync function handleLogin(e) { e.preventDefault(); const res = await api.post( '/auth/login', { matricula, senha } ); const { token, usuario } = res.data; // Salva no AuthContext global login(token, usuario); if (!usuario.nome) { navigate('/first-access'); } else { navigate('/'); } }
const AuthContext = createContext(); export function AuthProvider({ children }) { const [token, setToken] = useState(null); const [user, setUser] = useState(null); useEffect(() => { async function init() { const stored = localStorage.getItem('token'); if (stored) { const res = await api.get('/auth/me'); setToken(stored); setUser(res.data); } } init(); }, []); return ( <AuthContext.Provider value={{ token, user, login, logout }}> {children} </AuthContext.Provider> ); }
Sem Context, cada página teria que ler o localStorage manualmente e não reagiria a mudanças. Com o AuthProvider no topo da árvore, qualquer componente acessa o usuário logado instantaneamente.
tokenJWT da sessão ativauserDados do usuário logadoloadingAguardando restauraçãologin()Persiste token e atualiza estadologout()Limpa token e redirecionaexport function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error( "useAuth must be used" + " within AuthProvider" ); } return context; // { token, user, loading, // login, logout } }
// ProtectedRoute.jsx const { token, loading } = useAuth(); // Navbar.jsx const { user, logout } = useAuth(); // Login.jsx const { login } = useAuth(); // GameDetails.jsx const { user } = useAuth();
Encapsula o useContext(AuthContext) + validação de escopo em um único lugar. Qualquer componente obtém o estado global com uma linha — sem repetição de código.
Clique em qualquer tela para ver como ela funciona.
Hero, recentes, populares
clique →Busca + filtro por gênero
clique →Info, galeria, conquistas
clique →Jogos adquiridos
clique →Lista de desejos
clique →Criar, editar, excluir
clique →Form + gêneros + imagens
clique →Biblioteca de outro user
clique →Cada aula do semestre foi aplicada diretamente no Vaporzão — da integração básica com APIs até o estado global com Context API.
useAuth