diff --git a/.gitignore b/.gitignore index c9a988c..5850423 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .env node_modules /dist -.vscode \ No newline at end of file +.vscode + +package-lock.json + +*log \ No newline at end of file diff --git a/DEPLOY_CHECKLIST.md b/DEPLOY_CHECKLIST.md new file mode 100644 index 0000000..272258f --- /dev/null +++ b/DEPLOY_CHECKLIST.md @@ -0,0 +1,59 @@ +# ✅ Checklist de Deploy - Time for Code Frontend + +## 🔒 Segurança + +- [x] **Arquivo .env no .gitignore** - Variáveis sensíveis não serão commitadas +- [x] **Zero URLs hardcoded** - Todas as URLs da API usam variáveis de ambiente +- [x] **Configuração limpa** - Apenas exemplos genéricos nos arquivos de documentação + +## 🚀 Configuração Vercel + +- [x] **vercel.json** - Configurado para SPA (Single Page Application) +- [x] **package.json** - Scripts de build configurados corretamente +- [x] **Build testado** - `npm run build` funciona sem erros + +## 🔧 Variáveis de Ambiente + +### Para Deploy na Vercel: +1. Acesse o dashboard da Vercel +2. Vá em **Settings > Environment Variables** +3. Adicione: + - **Name**: `VITE_API_BASE_URL` + - **Value**: `https://sua-api-producao.com` + - **Environment**: Production (e Preview se desejar) + +## 📁 Arquivos Importantes + +- [x] `src/config/api.js` - Configuração centralizada da API +- [x] `vite.config.js` - Proxy configurado para desenvolvimento +- [x] `vercel.json` - Configuração para SPA +- [x] `package.json` - Scripts de build +- [x] `.gitignore` - Protege arquivos sensíveis +- [x] `README.md` - Documentação atualizada +- [x] `env.example` - Exemplo de configuração + +## 🎯 Próximos Passos + +1. **Commit das alterações:** + ```bash + git add . + git commit -m "feat: configure environment variables and deploy setup" + ``` + +2. **Push para GitHub:** + ```bash + git push origin main + ``` + +3. **Configurar Vercel:** + - Conectar repositório GitHub + - Configurar variável de ambiente + - Deploy automático ativado + +## ✅ Status: PRONTO PARA DEPLOY + +O projeto está configurado corretamente para: +- ✅ Deploy automático na Vercel +- ✅ Uso seguro de variáveis de ambiente +- ✅ Funcionamento em desenvolvimento e produção +- ✅ Zero informações sensíveis no repositório \ No newline at end of file diff --git a/README.md b/README.md index 071fb1b..f61fa95 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,63 @@ O Time for Code está sendo desenvolvido para crianças e adolescentes que estã - **Lucas [(zlucasftw)](https://github.com/zlucasftw)**: Desenvolvedor Backend - **Layla [(laycsz)](https://github.com/laycsz)**: Desenvolvedora Frontend e Designer UI/UX - **Arthur [(ArthurVenturi)](https://github.com/ArthurVenturi)**: Desenvolvedor Frontend + +# Time for Code - Frontend + +## Configuração de Variáveis de Ambiente + +### Desenvolvimento Local + +1. Copie o arquivo `env.example` para `.env`: +```bash +cp env.example .env +``` + +2. Configure a URL da API no arquivo `.env`: + +**Para testar API de produção:** +```bash +VITE_API_BASE_URL=https://sua-api-producao.com +``` + +**Para API local:** +```bash +VITE_API_BASE_URL=http://localhost:3000 +``` + +**Nota:** Em desenvolvimento, o sistema usa proxy do Vite automaticamente para APIs externas. + +### Deploy na Vercel + +1. Acesse o dashboard da Vercel +2. Vá para **Settings > Environment Variables** +3. Adicione a variável: + - **Name**: `VITE_API_BASE_URL` + - **Value**: `https://sua-api-producao.com` + - **Environment**: Production (e Preview se desejar) + +### Vantagens da Configuração + +✅ **Flexível**: Pode usar qualquer URL da API através de variáveis de ambiente +✅ **Seguro**: URLs não ficam hardcoded no código +✅ **Portável**: Funciona em qualquer ambiente (dev, staging, prod) +✅ **Manutenível**: Fácil de mudar URLs sem alterar código + +### Estrutura de Arquivos + +- `src/config/api.js` - Configuração centralizada da API +- `src/api/` - Endpoints da API +- `src/contexts/` - Contextos do React + +## Scripts Disponíveis + +- `npm run dev` - Inicia o servidor de desenvolvimento +- `npm run build` - Gera build de produção +- `npm run vercel-build` - Build específico para Vercel + +## Tecnologias + +- React 19 +- Vite +- TanStack Router +- TanStack Query diff --git a/SETUP_PRODUCTION_TEST.md b/SETUP_PRODUCTION_TEST.md new file mode 100644 index 0000000..93e8f2a --- /dev/null +++ b/SETUP_PRODUCTION_TEST.md @@ -0,0 +1,55 @@ +# Configuração para Testar API de Produção + +## Passo 1: Configurar o arquivo .env + +Edite o arquivo `.env` e configure a URL da API: + +```bash +# Para testar API de produção +VITE_API_BASE_URL=https://sua-api-producao.com + +# Para API local (alternativa) +# VITE_API_BASE_URL=http://localhost:3000 +``` + +## Passo 2: Testar localmente + +Execute o projeto: + +```bash +npm run dev +``` + +O sistema vai automaticamente: +- Usar a URL configurada em `VITE_API_BASE_URL` +- Se for uma URL externa, usar o proxy do Vite para evitar CORS +- Redirecionar `/api/*` para a URL configurada + +## Passo 3: Testar funcionalidades + +1. Acesse: http://localhost:5175 +2. Teste o cadastro de usuário +3. Teste o login +4. Verifique se não há erros de CORS + +## Passo 4: Preparar para Deploy + +Quando estiver funcionando, você pode: + +1. Fazer commit das alterações +2. Fazer push para o GitHub +3. Na Vercel, configurar a variável de ambiente: + - **Name**: `VITE_API_BASE_URL` + - **Value**: `https://sua-api-producao.com` + +## Como funciona: + +- **Desenvolvimento**: Usa proxy do Vite (`/api/*` → `VITE_API_BASE_URL/*`) +- **Produção**: Usa URL direta (`VITE_API_BASE_URL/*`) + +## Vantagens desta configuração: + +✅ **Flexível**: Pode usar qualquer URL da API +✅ **Seguro**: URLs não ficam hardcoded no código +✅ **Portável**: Funciona em qualquer ambiente +✅ **Manutenível**: Fácil de mudar URLs sem alterar código \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..98daf42 --- /dev/null +++ b/env.example @@ -0,0 +1,16 @@ +# Exemplo de variáveis de ambiente +# Copie este arquivo para .env e configure os valores + +# URL base da API +# Para desenvolvimento local com API local: +VITE_API_BASE_URL=http://localhost:3000 + +# Para desenvolvimento local com API remota: +# VITE_API_BASE_URL=https://sua-api-producao.com + +# Para produção, use a URL da sua API: +# VITE_API_BASE_URL=https://sua-api-producao.com + +# NOTA: O sistema usa variáveis de ambiente para máxima flexibilidade +# Em desenvolvimento, usa proxy do Vite para APIs externas +# Em produção, usa a URL direta configurada na Vercel diff --git a/index.html b/index.html index 1342602..8f2ab37 100644 --- a/index.html +++ b/index.html @@ -10,21 +10,19 @@ - - - - + +
Não Renderizado
- - + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 651c5da..715a738 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@tanstack/react-router": "^1.97.1", "jquery": "^3.7.1", "jquery-touchswipe": "^1.6.19", + "primeicons": "^7.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -20,8 +21,8 @@ "@tanstack/eslint-plugin-query": "^5.62.16", "@tanstack/router-plugin": "^1.97.1", "@testing-library/react": "^16.2.0", + "@types/react": "^19.1.8", "@vitejs/plugin-react": "^4.3.4", - "babel-plugin-react-compiler": "^19.0.0-beta-e552027-20250112", "eslint": "^9.18.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.14.0", @@ -841,9 +842,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -902,9 +903,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1756,6 +1757,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.26.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", @@ -2187,16 +2198,6 @@ "@babel/types": "^7.23.6" } }, - "node_modules/babel-plugin-react-compiler": { - "version": "19.0.0-beta-e552027-20250112", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-e552027-20250112.tgz", - "integrity": "sha512-pUTT0mAZ4XLewC6bvqVeX015nVRLVultcSQlkzGdC10G6YV6K2h4E7cwGlLAuLKWTj3Z08mTO9uTnPP/opUBsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.19.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2218,9 +2219,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2445,6 +2446,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -2956,9 +2964,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3010,9 +3018,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -4039,6 +4047,18 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -4690,6 +4710,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/primeicons": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5333,6 +5359,51 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5569,15 +5640,18 @@ } }, "node_modules/vite": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", - "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -5640,6 +5714,34 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index a07e075..c7c0b36 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "scripts": { "test": "", "lint": "eslint", - "dev": "vite" + "dev": "vite", + "build": "vite build", + "vercel-build": "vite build" }, "type": "module", "repository": { @@ -27,6 +29,7 @@ "@tanstack/react-router": "^1.97.1", "jquery": "^3.7.1", "jquery-touchswipe": "^1.6.19", + "primeicons": "^7.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -34,8 +37,8 @@ "@tanstack/eslint-plugin-query": "^5.62.16", "@tanstack/router-plugin": "^1.97.1", "@testing-library/react": "^16.2.0", + "@types/react": "^19.1.8", "@vitejs/plugin-react": "^4.3.4", - "babel-plugin-react-compiler": "^19.0.0-beta-e552027-20250112", "eslint": "^9.18.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.14.0", diff --git a/public/assets/css/cadastro.css b/public/assets/css/cadastro.css index ec54634..c095a13 100644 --- a/public/assets/css/cadastro.css +++ b/public/assets/css/cadastro.css @@ -9,7 +9,6 @@ body { bottom: 0; left: 0; height: 100%; - z-index: -1; } .container { @@ -19,6 +18,9 @@ body { grid-template-columns: repeat(2, 1fr); grid-gap: 7rem; padding: 0 2rem; + bottom: 0; + left: 8rem; + position: fixed; } .img { @@ -211,6 +213,7 @@ a:hover { justify-content: center; } } + .border-first-button a { display: inline-block !important; padding: 10px 20px !important; @@ -262,4 +265,4 @@ a:hover { .login-message a:hover { color: #c197f5; /* Efeito hover para o link */ -} +} \ No newline at end of file diff --git a/public/assets/css/exercicio1.css b/public/assets/css/exercicio1.css index d7b1cc6..9a52090 100644 --- a/public/assets/css/exercicio1.css +++ b/public/assets/css/exercicio1.css @@ -126,13 +126,25 @@ p { } .personagem { + position: fixed; + left: 40px; + bottom: 0; + z-index: 10; display: flex; - justify-content: center; - align-items: start; - position: relative; + align-items: flex-end; + box-sizing: border-box; + gap: 30px; + justify-content: flex-start; + align-items: center; padding-top: 110px; - padding-right: 60%; - z-index: 2; + padding-right: 55%; + max-width: 95vw; +} + +.personagem img { + height: auto; + width: 100%; + max-width: 300px; } .text-content { @@ -142,8 +154,14 @@ p { z-index: inherit; border-radius: 1rem; padding: 10px; - margin-top: 20px; - margin-left: 20px; + margin-bottom: 40px; +} + +.text-content p { + font-size: 1.2rem; + color: #4c4c4c; + margin: 0; + padding: 0 10px; } /* Estilos responsivos */ diff --git a/public/assets/css/exercicio2.css b/public/assets/css/exercicio2.css index b2d309d..b7fe5cb 100644 --- a/public/assets/css/exercicio2.css +++ b/public/assets/css/exercicio2.css @@ -95,6 +95,7 @@ button:focus { margin-top: -150px; /* background-color: #ede6ace7; */ border-radius: 1rem; + position: absolute; } #message h1 { @@ -147,23 +148,20 @@ button:focus { height: auto; } -.personagem { - display: flex; - justify-content: center; - align-items: start; - position: relative; - padding-top: 450px; - padding-right: 30%; - z-index: 2; -} - -.text-content { - display: flex; - align-items: center; - background-color: white; - z-index: inherit; - border-radius: 1rem; - padding: 10px; - margin-top: 20px; - margin-left: 20px; +@media (max-width: 768px) { + .personagem { + flex-direction: column; + align-items: flex-start; + left: 10px; + bottom: 10px; + width: auto; + max-width: 95vw; + padding: 0; + } + .text-content { + margin-left: 0; + margin-top: 10px; + width: 100%; + box-sizing: border-box; + } } \ No newline at end of file diff --git a/public/assets/css/exercicio3.css b/public/assets/css/exercicio3.css index 02004c7..36d59de 100644 --- a/public/assets/css/exercicio3.css +++ b/public/assets/css/exercicio3.css @@ -120,6 +120,7 @@ button:focus { .wave, .waveInverted{ color: #D6F6DE; } + .wave { position: fixed; bottom: 0; @@ -161,24 +162,3 @@ button:focus { width: 100%; height: auto; } - -.personagem{ - display: flex; - justify-content: center; - align-items: start; - position: relative; - padding-top: 400px; - padding-right: 60%; - z-index: 2; -} - -.text-content{ - display: flex; - align-items: center; - background-color: white; - z-index: inherit; - border-radius: 1rem; - padding: 10px; - margin-top: 20px; - margin-left: -30px; -} diff --git a/public/assets/css/exercicio4.css b/public/assets/css/exercicio4.css index 05934e3..2107e6c 100644 --- a/public/assets/css/exercicio4.css +++ b/public/assets/css/exercicio4.css @@ -224,25 +224,4 @@ h1 { font-size: 3rem; color: rgba(51, 51, 51, 0.365); text-transform: uppercase; -} - -.personagem{ - display: flex; - justify-content: center; - align-items: start; - position: absolute; - padding-top: 450px; - padding-right: 50%; - padding-left: 50px; - z-index: 2; -} - -.text-content{ - display: flex; - align-items: center; - background-color: white; - z-index: inherit; - border-radius: 1rem; - padding: 10px; - margin-top: 20px; } \ No newline at end of file diff --git a/public/assets/css/header.css b/public/assets/css/header.css new file mode 100644 index 0000000..5295c33 --- /dev/null +++ b/public/assets/css/header.css @@ -0,0 +1,77 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 32px; + background-color: #000000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); + box-sizing: border-box; +} + +.header-nav { + display: flex; + align-items: center; +} + +.main-nav { + display: flex; + align-items: center; +} + +.logo { + width: 64px; + height: 64px; + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +.flex-container { + display: flex; + gap: 18px; + list-style: none; + margin: 0; + padding: 0; +} + +.scroll-to-section { + display: flex; + align-items: center; +} + +.scroll-to-section a:hover { + color: #c197f5; + /* Laranja Claro */ +} + +.border-first-button a { + display: inline-block !important; + padding: 10px 20px !important; + background-color: #dbc4f8; + /* Rosa Claro */ + color: #fff; + /* Branco */ + border: 1px solid #dbc4f8; + /* Rosa Claro */ + border-radius: 23px; + font-weight: 500 !important; + letter-spacing: 0.3px !important; + transition: all 0.5s; +} + +.border-first-button a:hover { + background-color: #c197f5; /* Laranja Claro */ + color: #fff !important; + border: 1px solid #c197f5; /* Rosa Claro */ +} + +.login-button { + color: #fff; + border-radius: 6px; + padding: 6px 18px; + transition: background 0.2s; +} + +.login-button a:hover { + color: #c197f5 !important; +} \ No newline at end of file diff --git a/public/assets/css/home.css b/public/assets/css/home.css index 89ebf84..f90b400 100644 --- a/public/assets/css/home.css +++ b/public/assets/css/home.css @@ -50,7 +50,6 @@ header { } .main-banner { - background-color: #FDF3D6; padding: 60px 20px; display: flex; align-items: center; @@ -139,7 +138,6 @@ header { background-color: #D3EDC5; padding: 40px 20px; text-align: center; - } .about-section h2 { @@ -154,7 +152,6 @@ header { } .content-section { - background-color: #FDF3D6; padding: 60px 20px; text-align: center; } @@ -207,17 +204,6 @@ footer { footer p { color: #fff; } -.content-section { - background-color: #FDF3D6; - padding: 60px 20px; - text-align: center; -} - -.content-section h2 { - font-size: 2.5rem; - color: #DBC4F8; - margin-bottom: 40px; -} .content-grid { display: grid; diff --git a/public/assets/css/index.css b/public/assets/css/index.css index adc9027..bfd53d6 100644 --- a/public/assets/css/index.css +++ b/public/assets/css/index.css @@ -51,11 +51,11 @@ body { a { text-decoration: none !important; - color: #ffbe98; /* Laranja Claro */ + color: white; /* Laranja Claro */ } a:hover { - color: #f9b2bc; /* Rosa Claro */ + color: black; /* Rosa Claro */ } h1, @@ -154,23 +154,6 @@ body { background-color: #726ae3; } -.border-first-button a { - display: inline-block !important; - padding: 10px 20px !important; - background-color: #dbc4f8; /* Rosa Claro */ - color: #fff; /* Branco */ - border: 1px solid #dbc4f8; /* Rosa Claro */ - border-radius: 23px; - font-weight: 500 !important; - letter-spacing: 0.3px !important; - transition: all 0.5s; -} - -.border-first-button a:hover { - background-color: #c197f5; /* Laranja Claro */ - color: #fff !important; -} - .intro-banner { text-align: center; padding-top: 200px; @@ -1879,6 +1862,30 @@ footer p a { transition: all 0.5s; } +/*return button*/ +.return-button { + position: absolute; + top: 24px; + left: 32px; + z-index: 1000; + background: #dbc4f8; + color: #fff; + padding: 8px 18px; + border-radius: 23px; + font-weight: 500; + text-decoration: none; + font-size: 1rem; + transition: background 0.3s, color 0.3s; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); +} + +.return-button:hover { + background: #c197f5; + color: #fff; +} + +/* Responsive Styles */ + @media (max-width: 1200px) { .header-area .main-nav .logo h4 { font-size: 24px; diff --git a/public/assets/css/login.css b/public/assets/css/login.css index ebe883f..51fd977 100644 --- a/public/assets/css/login.css +++ b/public/assets/css/login.css @@ -9,7 +9,6 @@ body{ bottom: 0; left: 0; height: 100%; - z-index: -1; } .container{ @@ -19,6 +18,9 @@ body{ grid-template-columns: repeat(2, 1fr); grid-gap :7rem; padding: 0 2rem; + bottom: 0; + left: 8rem; + position: fixed; } .img{ @@ -214,6 +216,7 @@ a:hover{ justify-content: center; } } + .border-first-button a { display: inline-block !important; padding: 10px 20px !important; diff --git a/public/assets/css/performance.css b/public/assets/css/performance.css new file mode 100644 index 0000000..9123fdf --- /dev/null +++ b/public/assets/css/performance.css @@ -0,0 +1,436 @@ +/* Layout principal da página de performance */ +.performance-layout { + display: grid; + grid-template-columns: 75% 1fr; + gap: 10px; + width: 100vw; + box-sizing: border-box; +} + +.content-performance{ + padding: 1rem; +} + +.content-performance.right { + margin: 1rem; + padding: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 1rem; +} + +/* Seção dos pilares */ +.pilares-section { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; + width: 100%; + justify-content: stretch; + align-items: stretch; + box-sizing: border-box; +} + +/* Card de cada pilar */ +.card-pilars { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + background-color: #f9f9f9; + border-radius: 1rem; + padding: 1rem; + border-left: 8px solid; + text-align: center; + min-width: fit-content; + height: 280px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: relative; + box-sizing: border-box; +} + +/* Informações do pilar */ +.card-info { + display: flex; + flex-direction: column; + justify-content: start; + padding: 10px; + gap: 30px; +} + +.card-info img { + display: block; + size: 1rem; + height: 150px; + width: fit-content; + background-size: cover; +} + +.card-info h2 { + font-size: 1.5rem; + color: #333; +} + +/* Gráfico circular do pilar */ +.card-graphic { + position: absolute; + top: 1rem; + right: 1rem; + width: 140px; + height: 140px; + display: flex; + justify-content: center; + align-items: center; + pointer-events: none; +} + +.card-graphic svg { + position: relative; + width: 100%; + height: 100%; + transform: rotate(-90deg); + display: block; +} + +.card-graphic .background-circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 10; +} + +.card-graphic .progress-circle { + fill: none; + stroke: #dbc4f8; + stroke-width: 10; + stroke-linecap: round; + stroke-dasharray: 0, 440; + transition: stroke-dasharray 0.3s ease; +} + +.card-graphic .percentage { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 1.5rem; + font-weight: bold; + color: #333; + pointer-events: none; +} + +/* Seção da tabela de exercícios */ +.exercises-section { + display: flex; + flex-direction: column; + width: 100%; + height: auto; + padding: 1rem; + margin-top: 4rem; + background-color: #f9f9f9; + border-radius: 1rem; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.exercises-section h2 { + font-size: 2rem; + color: #333; + text-align: center; + margin-bottom: 1rem; +} + +/* Tabela de exercícios */ +.exercises-table { + width: 100%; + border-collapse: collapse; + text-align: center; + font-size: 1rem; +} + +.exercises-table th, +.exercises-table td { + padding: 10px; + border: 1px solid #ddd; +} + +.exercises-table th { + background-color: #fdf3d6; + color: #333; + font-weight: bold; +} + +.exercises-table td .status-button { + display: inline-block; + padding: 5px 10px; + border-radius: 1rem; + font-size: 0.9rem; + font-weight: bold; + color: #fff; + text-align: center; +} + +.exercises-table td .status-button.completed { + background-color: #4caf50; +} + +.exercises-table td .status-button.incomplete { + background-color: #f44336; +} + +/* ================= Ranking Table ================= */ +.ranking-table { + background: #f9f9f9; + padding: 1rem; + display: flex; + flex-direction: column; + align-items: center; + max-height: 350px; +} + +.ranking-table-scroll { + max-height: 100%; + overflow-y: auto; + width: 100%; +} + +.ranking-header { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + gap: 30px; + position: relative; + margin-bottom: 1rem; +} + +.ranking-header h2 { + font-size: 2rem; + color: #333; + flex: 1; + text-align: center; +} + +.ranking-table-inner { + width: 100%; + border-collapse: collapse; + font-size: 1rem; + background: #fff; + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); +} + +.ranking-table-inner th, +.ranking-table-inner td { + padding: 10px 16px; + border-bottom: 1px solid #eee; + text-align: center; +} + +.ranking-table-inner th { + background: #e9e3f7; + color: #333; + font-weight: bold; +} + +.ranking-table-inner tr:last-child td { + border-bottom: none; +} + +.ranking-table-inner tr.highlight-you td { + background: #f8dd56; + color: #333; + font-weight: bold; +} + +.ranking-table-inner td:nth-child(2) { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +/* ===== Botão e Modal de Pontuação ===== */ +.pontuation-info-btn { + position: absolute; + right: 10px; + width: 48px; + height: 48px; + min-width: 48px; + min-height: 48px; + max-width: 48px; + max-height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: #e9e3f7; + color: #333; + border: none; + border-radius: 50%; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: background 0.2s; + margin-left: auto; +} + +.pontuation-info-btn i { + font-size: 1.5rem; +} + +.pontuation-info-btn:hover { + background: #d5c2e0; +} + +.pontuation-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.25); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} + +.pontuation-modal { + background: #fff; + border-radius: 1rem; + padding: 2rem 1.5rem 1.5rem 1.5rem; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.18); + max-width: 350px; + width: 90vw; + position: relative; + text-align: left; + animation: pontuationModalIn 0.2s; +} + +@keyframes pontuationModalIn { + from { + transform: scale(0.95); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +.pontuation-modal h3 { + margin-top: 0; + font-size: 1.3rem; + color: #6c4fa1; + margin-left: 1rem; +} + +.pontuation-modal ul { + display: flex; + flex-direction: column; + gap: 8px; + margin: 1rem; + padding: 0; + color: #444; + font-size: 1rem; +} + +.pontuation-modal-close { + position: absolute; + top: 0.7rem; + right: 1rem; + background: none; + border: none; + font-size: 1.5rem; + color: #888; + cursor: pointer; + transition: color 0.2s; +} + +.pontuation-modal-close:hover { + color: #333; +} + +/* ================= Estilo do Perfil ================= */ +.performance-profile { + display: flex; + flex-direction: column; + align-items: center; + border-radius: 1rem; + padding: 2.8rem 0; + background-color: #f9f9f9; + position: relative; + box-sizing: border-box; + border-top: 8px solid; +} + +.profile-circle { + width: 160px; + height: 160px; + border-radius: 50%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border: 6px solid; +} + +.profile-rank { + font-size: 2.5rem; + font-weight: bold; + color: #6c4fa1; +} + +.profile-stars { + display: flex; + gap: 12px; + margin-top: 8px; +} + +.profile-note { + margin-top: 12px; + font-size: 1.2rem; + color: #333; + text-align: center; + font-weight: bold; +} + +/* ================= Responsividade ================= */ +@media (max-width: 900px) { + .performance-layout { + grid-template-columns: 1fr; + } + .pilares-section { + grid-template-columns: 1fr 1fr; + } + .info { + grid-template-columns: 1fr; + } + .ranking-table { + margin-top: 0; + margin-bottom: 1rem; + } +} + +@media (max-width: 600px) { + .layout { + padding: 1rem; + gap: 1rem; + } + .performance-layout { + grid-template-columns: 1fr; + } + .pilares-section { + grid-template-columns: 1fr; + gap: 10px; + } + .card-pilars { + height: auto; + grid-template-columns: 1fr; + } + .info { + grid-template-columns: 1fr; + gap: 10px; + } + .ranking-table-inner th, + .ranking-table-inner td { + padding: 8px 4px; + font-size: 0.95rem; + } +} \ No newline at end of file diff --git a/public/assets/css/statistics.css b/public/assets/css/statistics.css new file mode 100644 index 0000000..e82ab66 --- /dev/null +++ b/public/assets/css/statistics.css @@ -0,0 +1,101 @@ +.statistics-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + min-height: 100vh; +} + +.statistics-content { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + margin: 0 25px 32px 25px; +} + +.points-container { + position: absolute; + top: 20px; + right: -25px; +} + +.points { + display: flex; + gap: 8px; + background-color: white; + padding: 6px 12px; + border-radius: 1rem; + align-items: center; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); +} + +.title-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.statistics-grid { + display: flex; + flex-wrap: wrap; + gap: 20px; + width: 100%; +} + +.exercise-card { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + background-color: white; + padding: 40px; + border-radius: 1rem; + position: relative; +} + +.exercise-details { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; +} + +.data { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + padding: 10px; + border-radius: 5px; +} + +.data li { + font-size: 1.5rem; +} + +progress{ + height: 10px; + appearance: none; +} + +progress[value]::-webkit-progress-bar { + background-color: #f0f0f0; + border-radius: 1rem; +} + +progress[value]::-webkit-progress-value { + background-color: #dbc4f8; + border-radius: 1rem; +} + +.finish-btn{ + background-color: #dbc4f8; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; +} diff --git a/public/assets/css/successPopUp.css b/public/assets/css/successPopUp.css new file mode 100644 index 0000000..51d107e --- /dev/null +++ b/public/assets/css/successPopUp.css @@ -0,0 +1,37 @@ +.popup-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.popup-content { + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + text-align: center; + max-width: 400px; + width: 90%; +} + +.close-button { + margin-top: 10px; + padding: 10px 20px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +.close-button:hover { + background-color: #45a049; +} diff --git a/public/assets/css/utilidades.css b/public/assets/css/utilidades.css new file mode 100644 index 0000000..f56370f --- /dev/null +++ b/public/assets/css/utilidades.css @@ -0,0 +1,77 @@ +.error-boundary-fallback { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100vw; + height: 100vh; + color: #C197F5; +} + +.error-boundary-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 1rem; + max-width: 600px; + padding: 20px; +} + +.exception-button{ + background-color: white; + color: var(--primary-color); + font-size: large; + font-weight: bolder; + padding: 10px 20px; + border-radius: 2rem; + border: 1px solid #C197F5; +} + +.exception-button:hover { + background-color: #C197F5; + color: white; +} + +.error-boundary-content h1 { + font-size: 8rem; +} + +.error-boundary-content h2 { + font-size: 1.5rem; +} + +/*End of error-boundary-fallback styles*/ + +.container-404 { + text-align: center; + margin-top: 10%; +} + +.container-404 h1 { + font-size: 6rem; + margin: 20px 0; +} + +.container-404 p { + font-size: 1.5rem; + margin: 25px 0; +} + +.container-404 .home-button { + display: inline-block; + padding: 15px 25px; + font-size: 1rem; + color: #C197F5; + text-decoration: none; + border: 1px solid #C197F5; + border-radius: 1rem; + transition: background-color 0.3s ease; +} + +.container-404 .home-button:hover { + background-color: #C197F5; + color: #fff; +} + +/*End of 404 page styles*/ \ No newline at end of file diff --git a/public/assets/js/custom.js b/public/assets/js/custom.js index 523628a..55bed6d 100644 --- a/public/assets/js/custom.js +++ b/public/assets/js/custom.js @@ -1,9 +1,9 @@ (function ($) { - - "use strict"; - // Header Type = Fixed - $(window).scroll(function() { + "use strict"; + + // Header Type = Fixed + $(window).scroll(function () { var scroll = $(window).scrollTop(); var box = $('.header-text').height(); var header = $('header').height(); @@ -16,30 +16,30 @@ }); - $('.loop').owlCarousel({ - center: true, - items:1, - loop:true, - autoplay: true, - nav: true, - margin:0, - responsive:{ - 1200:{ - items:5 - }, - 992:{ - items:3 - }, - 760:{ - items:2 - } + $('.loop').owlCarousel({ + center: true, + items: 1, + loop: true, + autoplay: true, + nav: true, + margin: 0, + responsive: { + 1200: { + items: 5 + }, + 992: { + items: 3 + }, + 760: { + items: 2 } + } }); - - // Menu Dropdown Toggle - if($('.menu-trigger').length){ - $(".menu-trigger").on('click', function() { + + // Menu Dropdown Toggle + if ($('.menu-trigger').length) { + $(".menu-trigger").on('click', function () { $(this).toggleClass('active'); $('.header-area .nav').slideToggle(200); }); @@ -47,16 +47,16 @@ // Menu elevator animation - $('.scroll-to-section a[href*=\\#]:not([href=\\#])').on('click', function() { - if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) { + $('.scroll-to-section a[href*=\\#]:not([href=\\#])').on('click', function () { + if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { var target = $(this.hash); - target = target.length ? target : $('[name=' + this.hash.slice(1) +']'); + target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); if (target.length) { var width = $(window).width(); - if(width < 991) { + if (width < 991) { $('.menu-trigger').removeClass('active'); - $('.header-area .nav').slideUp(200); - } + $('.header-area .nav').slideUp(200); + } $('html,body').animate({ scrollTop: (target.offset().top) + 1 }, 700); @@ -66,79 +66,79 @@ }); $(document).ready(function () { - $(document).on("scroll", onScroll); - - //smoothscroll - $('.scroll-to-section a[href^="#"]').on('click', function (e) { - e.preventDefault(); - $(document).off("scroll"); - - $('.scroll-to-section a').each(function () { - $(this).removeClass('active'); - }) - $(this).addClass('active'); - - var target = this.hash, - menu = target; - var target = $(this.hash); - $('html, body').stop().animate({ - scrollTop: (target.offset().top) + 1 - }, 500, 'swing', function () { - window.location.hash = target; - $(document).on("scroll", onScroll); - }); + $(document).on("scroll", onScroll); + + //smoothscroll + $('.scroll-to-section a[href^="#"]').on('click', function (e) { + e.preventDefault(); + $(document).off("scroll"); + + $('.scroll-to-section a').each(function () { + $(this).removeClass('active'); + }) + $(this).addClass('active'); + + var target = this.hash, + menu = target; + var target = $(this.hash); + $('html, body').stop().animate({ + scrollTop: (target.offset().top) + 1 + }, 500, 'swing', function () { + window.location.hash = target; + $(document).on("scroll", onScroll); }); + }); }); - function onScroll(event){ - var scrollPos = $(document).scrollTop(); - $('.nav a').each(function () { - var currLink = $(this); - var refElement = $(currLink.attr("href")); - if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) { - $('.nav ul li a').removeClass("active"); - currLink.addClass("active"); - } - else{ - currLink.removeClass("active"); - } - }); + function onScroll(event) { + var scrollPos = $(document).scrollTop(); + $('.nav a').each(function () { + var currLink = $(this); + var refElement = $(currLink.attr("href")); + if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) { + $('.nav ul li a').removeClass("active"); + currLink.addClass("active"); + } + else { + currLink.removeClass("active"); + } + }); } // Acc - $(document).on("click", ".naccs .menu div", function() { + $(document).on("click", ".naccs .menu div", function () { var numberIndex = $(this).index(); if (!$(this).is("active")) { - $(".naccs .menu div").removeClass("active"); - $(".naccs ul li").removeClass("active"); + $(".naccs .menu div").removeClass("active"); + $(".naccs ul li").removeClass("active"); - $(this).addClass("active"); - $(".naccs ul").find("li:eq(" + numberIndex + ")").addClass("active"); + $(this).addClass("active"); + $(".naccs ul").find("li:eq(" + numberIndex + ")").addClass("active"); - var listItemHeight = $(".naccs ul") - .find("li:eq(" + numberIndex + ")") - .innerHeight(); - $(".naccs ul").height(listItemHeight + "px"); - } + var listItemHeight = $(".naccs ul") + .find("li:eq(" + numberIndex + ")") + .innerHeight(); + $(".naccs ul").height(listItemHeight + "px"); + } }); - // Page loading animation - $(window).on('load', function() { + // Page loading animation + $(window).on('load', function () { - $('#js-preloader').addClass('loaded'); + $('#js-preloader').addClass('loaded'); + + }); - }); - - // Window Resize Mobile Menu Fix + // Window Resize Mobile Menu Fix function mobileNav() { var width = $(window).width(); - $('.submenu').on('click', function() { - if(width < 767) { + $('.submenu').on('click', function () { + if (width < 767) { $('.submenu ul').removeClass('active'); $(this).find('ul').toggleClass('active'); } diff --git a/public/assets/js/exercicio2.js b/public/assets/js/exercicio2.js index 117d66d..2f9bc8c 100644 --- a/public/assets/js/exercicio2.js +++ b/public/assets/js/exercicio2.js @@ -1,4 +1,4 @@ -import { mazeCanvas, virtCanvas, context, imgData, ctx } from '../../../src/pages/exercicio2'; +import { mazeCanvas, virtCanvas, context, imgData, ctx } from '../../../src/pages/Exercicio2'; import $ from 'jquery'; import 'jquery-touchswipe/jquery.touchSwipe.min.js'; import { toggleVisablity } from '../../../src/utils/utilidades'; diff --git a/public/assets/js/exercicio4.js b/public/assets/js/exercicio4.js index daf487c..b02c9c9 100644 --- a/public/assets/js/exercicio4.js +++ b/public/assets/js/exercicio4.js @@ -43,7 +43,7 @@ export function addDraggableDivs(draggableDivs, cells) { let shufflePosition = shufflePositions(); draggableDivs.forEach((div, i) => { - div.style.backgroundImage = 'url("../public/img/grupoMascotes.png")'; + div.style.backgroundImage = 'url("/img/grupoMascotes.png")'; cells.append(div); div.style.backgroundPosition = `-${Positions[i][1]}vw -${Positions[i][0]}vw`; div.style.left = `${shufflePosition[i][0]}vw`; diff --git a/public/img/istockphoto-1393685037-612x612.jpg b/public/img/istockphoto-1393685037-612x612.jpg deleted file mode 100644 index f085bb0..0000000 Binary files a/public/img/istockphoto-1393685037-612x612.jpg and /dev/null differ diff --git a/public/img/logo.png b/public/img/logo.png index f8ce912..bfccbfb 100644 Binary files a/public/img/logo.png and b/public/img/logo.png differ diff --git a/src/api/Statistics.jsx b/src/api/Statistics.jsx new file mode 100644 index 0000000..4df9761 --- /dev/null +++ b/src/api/Statistics.jsx @@ -0,0 +1,34 @@ +import { getApiUrl, API_CONFIG } from '../config/api.js'; + +export const getAllUsers = async () => { + try { + const response = await fetch(getApiUrl(API_CONFIG.ENDPOINTS.USERS)); + if (!response.ok) { + throw new Error("Failed to fetch users"); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching users:", error); + return []; + } +} + +export const postStatisticsData = async (data) => { + try { + const response = await fetch(getApiUrl(API_CONFIG.ENDPOINTS.STATISTICS + '/:id'), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + if (!response.ok) { + throw new Error("Failed to post statistics data"); + } + return await response.json(); + } catch (error) { + console.error("Error posting statistics data:", error); + return null; + } +} \ No newline at end of file diff --git a/src/api/User.jsx b/src/api/User.jsx new file mode 100644 index 0000000..09d7198 --- /dev/null +++ b/src/api/User.jsx @@ -0,0 +1,71 @@ +import { getApiUrl, API_CONFIG } from '../config/api.js'; + +export async function login(email, password) { + try { + const url = getApiUrl(API_CONFIG.ENDPOINTS.LOGIN); + console.log('Attempting login to:', url); + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ "email": email, "password": password }), + }); + + console.log('Login response status:', response.status); + console.log('Login response headers:', response.headers); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Login failed with status:', response.status, 'Error:', errorText); + throw new Error(`Login falhou! Status: ${response.status}`); + } + + const data = await response.json(); + console.log('Login successful:', data); + return data; + + } catch (error) { + console.error("Erro ao fazer login:", error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error("Erro de conexão! Verifique se o servidor está rodando."); + } + throw error; + } +}; + +export async function cadastro(nome, anoNascimento, email, senha) { + try { + const url = getApiUrl(API_CONFIG.ENDPOINTS.REGISTER); + console.log('Attempting registration to:', url); + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ nome, anoNascimento, email, senha }), + }); + + console.log('Registration response status:', response.status); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Registration failed with status:', response.status, 'Error:', errorText); + throw new Error(`Cadastro falhou! Status: ${response.status}`); + } + + const data = await response.json(); + console.log('Registration successful:', data); + return data; + + } catch (error) { + console.error("Erro ao fazer o cadastro:", error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error("Erro de conexão! Verifique se o servidor está rodando."); + } + throw error; + } +}; + diff --git a/src/components/App.jsx b/src/components/App.jsx index 74d5481..bab77cc 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -3,17 +3,21 @@ import { createRoot } from "react-dom/client"; import { routeTree } from "../routeTree.gen"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createRouter, RouterProvider } from "@tanstack/react-router"; +import { UserProvider } from "../contexts/UserContext"; +import "primeicons/primeicons.css"; const router = createRouter({ routeTree }) const queryClient = new QueryClient(); const App = () => { return ( - - - - - + + + + + + + ); }; diff --git a/src/components/BannerPrincipal.jsx b/src/components/BannerPrincipal.jsx index dd5707b..e27521b 100644 --- a/src/components/BannerPrincipal.jsx +++ b/src/components/BannerPrincipal.jsx @@ -25,7 +25,7 @@ const BannerPrincipal = () => {
- Ilustração + Ilustração
diff --git a/src/components/Conteudos.jsx b/src/components/Conteudos.jsx index ca6091d..1d0abe7 100644 --- a/src/components/Conteudos.jsx +++ b/src/components/Conteudos.jsx @@ -9,22 +9,22 @@ const Conteudos = () => {

Pintando o Arco-Íris

Arraste e organize as cores na ordem correta do arco-íris.

- Começar Jogo + Começar Jogo

Robô no Labirinto

Ajude o robô a sair do labirinto, organizando as instruções corretas.

- Começar Jogo + Começar Jogo

Caça ao Tesouro

Encontre o tesouro seguindo padrões de pistas escondidas.

- Começar Jogo + Começar Jogo

Quebra-Cabeça Gigante

Encontre o tesouro seguindo padrões de pistas escondidas.

- Começar Jogo + Começar Jogo
diff --git a/src/components/ErrorBoundaries.jsx b/src/components/ErrorBoundaries.jsx new file mode 100644 index 0000000..1a3cf64 --- /dev/null +++ b/src/components/ErrorBoundaries.jsx @@ -0,0 +1,79 @@ +import * as React from "react"; +import "../../public/assets/css/utilidades.css"; +import { useNavigate } from "@tanstack/react-router"; + +// ErrorBoundary catches JavaScript errors in its child component tree, logs them, and displays a fallback UI. +// Use it to prevent the entire app from crashing due to errors in part of the UI. +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + this.handleGoHome = this.handleGoHome.bind(this); + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error, info) { + this.setState({ hasError: true }); + console.log(error, info); + // You can also log the error to an error reporting service here. + } + + handleGoHome() { + if (this.props.navigate) { + this.props.navigate({ to: "/" }); + setTimeout(() => window.location.reload(), 0); + } + } + + render() { + if (this.state.hasError) { + // Render custom fallback UI if provided, otherwise show a default message. + return ( + this.props.fallback || ( +
+
+

WHOPPS!

+

Alguma coisa deu errado. Que Vergonha

+ +
+
+ ) + ); + } + + return this.props.children; + } +} + +// Wrapper to inject navigate prop from useNavigate into ErrorBoundary +export function ErrorBoundaryWithNavigate(props) { + const navigate = useNavigate(); + return ; +} + +export const Pagina404 = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate({ to: "/" }); + setTimeout(() => window.location.reload(), 0); + }; + + return ( +
+

404 - Página não encontrada

+

A página que você está procurando não existe.

+ +
+ ); +}; \ No newline at end of file diff --git a/src/components/Header.jsx b/src/components/Header.jsx index cecc937..fc14fba 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,36 +1,75 @@ import { Link } from "@tanstack/react-router"; -import '../../public/assets/css/index.css' +import "../../public/assets/css/header.css"; +import { useUser } from "../contexts/UserContext"; +import "../../public/assets/css/index.css"; const Header = () => { - return ( -
-
-
-
- -
+ const { user } = useUser(); + + return ( +
+
+ + Logo + +
+
+
-
- ); + + ) : ( + <> +
  • +
    + + Login + +
    +
  • +
  • +
    + + Criar conta + +
    +
  • + + )} + + +
    +
    + ); }; export default Header; \ No newline at end of file diff --git a/src/components/Loading.jsx b/src/components/Loading.jsx index df5cce3..b95f2d2 100644 --- a/src/components/Loading.jsx +++ b/src/components/Loading.jsx @@ -1,16 +1,24 @@ -const Loading = () => { - return ( -
    -
    - -
    - - - -
    -
    -
    - ); -}; +const Loading = (props) => ( +
    + + +
    +); -export default Loading \ No newline at end of file + +export default Loading; \ No newline at end of file diff --git a/src/components/Servicos.jsx b/src/components/Servicos.jsx index 885b0d1..0b1c69b 100644 --- a/src/components/Servicos.jsx +++ b/src/components/Servicos.jsx @@ -57,7 +57,7 @@ const Servicos = () => {
    - lilu + lilu
    @@ -80,7 +80,7 @@ const Servicos = () => {
    - cadu + cadu
    @@ -104,7 +104,7 @@ const Servicos = () => {
    - ana + ana
    @@ -128,7 +128,7 @@ const Servicos = () => {
    - soso + soso
    diff --git a/src/config/api.js b/src/config/api.js new file mode 100644 index 0000000..08dc2d2 --- /dev/null +++ b/src/config/api.js @@ -0,0 +1,31 @@ +// src/config/api.js +export const API_CONFIG = { + BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000', + ENDPOINTS: { + LOGIN: '/login', + REGISTER: '/register', + SESSION: '/sesion', + USERS: '/users', + STATISTICS: '/statistics' + } +}; + +export const getApiUrl = (endpoint) => { + const baseUrl = API_CONFIG.BASE_URL; + + // Se a URL é externa e estamos em desenvolvimento, usa proxy + if (import.meta.env.DEV && baseUrl && baseUrl.includes('https://')) { + console.log('Using proxy for external API in development'); + return `/api${endpoint}`; + } + + // Se estamos em desenvolvimento e a URL é localhost, usa diretamente + if (import.meta.env.DEV && baseUrl && baseUrl.includes('localhost')) { + console.log('Using direct localhost URL in development'); + return `${baseUrl}${endpoint}`; + } + + // Para produção ou outras configurações + console.log('Using configured API URL:', `${baseUrl}${endpoint}`); + return `${baseUrl}${endpoint}`; +}; \ No newline at end of file diff --git a/src/contexts/UserContext.jsx b/src/contexts/UserContext.jsx new file mode 100644 index 0000000..385170e --- /dev/null +++ b/src/contexts/UserContext.jsx @@ -0,0 +1,62 @@ +import { createContext, useContext, useState, useEffect } from "react" +import { getApiUrl, API_CONFIG } from '../config/api.js'; + +//creating a context to store the user data with a default value as null and no function +const UserContext = createContext({ + user: null, + armazenarLogin: (data) => { UserProvider.armazenarLogin(data) }, + armazenarCadastro: (data) => { UserProvider.armazenarCadastro(data) }, + sair: () => { UserProvider.sair() }, +}) + +export const UserProvider = ({ children }) => { + const [user, setUser] = useState(null); + + // Fetch user info from backend on mount (after reload) + useEffect(() => { + async function fetchUser() { + try { + const res = await fetch(getApiUrl(API_CONFIG.ENDPOINTS.SESSION), { + credentials: "include", // send cookies + headers: { + "Content-Type": "application/json" + } + }); + if (res.ok) { + const data = await res.json(); + setUser(data.user); + } + } catch (e) { + setUser(null); + } + } + fetchUser(); + }, []); + + const armazenarLogin = (data) => { + if (data !== null) { + setUser(data); + } + } + + const armazenarCadastro = (data) => { + if (data !== null) { + setUser(data) + console.log(user); + } + } + + const sair = () => { + if (user !== null) { + setUser(null) + } + } + + return ( + + {children} + + ) +} + +export const useUser = () => useContext(UserContext) \ No newline at end of file diff --git a/src/pages/Cadastro.jsx b/src/pages/Cadastro.jsx index 9f84467..24161de 100644 --- a/src/pages/Cadastro.jsx +++ b/src/pages/Cadastro.jsx @@ -1,87 +1,153 @@ -import { Link } from '@tanstack/react-router'; +import { Link, useNavigate } from '@tanstack/react-router'; import '../../public/assets/css/cadastro.css' +import { useMutation } from '@tanstack/react-query'; +import { cadastro } from '../api/User'; +import { useState } from 'react'; +import { useUser } from '../contexts/UserContext'; +import Loading from '../components/Loading'; const Cadastro = () => { + const [nome, setNome] = useState(''); + const [anoNascimento, setAnoNascimento] = useState(''); + const [email, setEmail] = useState(''); + const [senha, setSenha] = useState(''); + const { armazenarCadastro } = useUser() + const navigate = useNavigate(); + + const mutation = useMutation({ + mutationKey: ['cadastro'], + mutationFn: async ({ nome, anoNascimento, email, senha }) => { + return cadastro(nome, parseInt(anoNascimento), email, senha) + }, + onError: (e) => console.log(e), + onSuccess: (data) => { + armazenarCadastro(data.user) + console.log('Cadastro realizado com sucesso!', data); + armazenarLogin(data.name); + setTimeout(() => { + navigate({ to: "/home", reloadDocument: true }); + }, 800); + }, + }) + return ( <> - wave + + ← Voltar + + wave
    - grupoMascotes + grupoMascotes
    -
    -

    Cadastro

    -
    -
    - -
    -
    -
    Nome Completo
    - -
    -
    -
    -
    - -
    -
    -
    Ano de Nascimento
    - + {mutation.isPending ? ( + + ) : ( + mutation.mutate({ nome, anoNascimento, email, senha })}> +

    Cadastro

    +
    +
    + +
    +
    +
    Nome Completo
    + setNome(e.target.value)} + required + minLength="3" + maxLength="50" + title="O nome deve conter apenas letras e espaços" + /> +
    -
    -
    -
    - +
    +
    + +
    +
    +
    Ano de Nascimento
    + setAnoNascimento(e.target.value)} + required + title="Digite uma data válida no formato DD/MM/AAAA" + /> +
    -
    -
    E-mail dos Pais
    - +
    +
    + +
    +
    +
    E-mail dos Pais
    + setEmail(e.target.value)} + required + minLength="5" + maxLength="50" + title="Digite um e-mail válido" + regex="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$" + /> +
    -
    -
    -
    - +
    +
    + +
    +
    +
    Senha
    + +
    -
    -
    Senha
    - +
    +
    + +
    +
    +
    Confirmar Senha
    + setSenha(e.target.value)} + required + minLength="8" + maxLength="20" + title="A senha deve ter pelo menos 8 caracteres, incluindo letras e números" + /> +
    -
    -
    -
    - +
    +
    -
    -
    Confirmar Senha
    - -
    -
    -
    - -
    -

    - Já tem uma conta?Faça login! -

    - +

    + Já tem uma conta?Faça login! +

    + + )}
    - ); }; -export default Cadastro; - - -{/* */} - +export default Cadastro; \ No newline at end of file diff --git a/src/pages/Exercicio1.jsx b/src/pages/Exercicio1.jsx index 336abad..b849c4a 100644 --- a/src/pages/Exercicio1.jsx +++ b/src/pages/Exercicio1.jsx @@ -2,6 +2,7 @@ import { verificarResultado, reiniciarExercicio, embaralharCores, drop, dragOver import '../../public/assets/css/exercicio1.css'; import { useEffect, useRef } from 'react'; import { toggleVisablity } from '../utils/utilidades.js' +import { Link } from "@tanstack/react-router"; const Exercicio1 = () => { const resultadoRef = useRef(null); @@ -46,6 +47,9 @@ const Exercicio1 = () => { return ( <> + + ← Continuar em outra hora ? +

    Organize as Cores do Arco-Íris

    @@ -83,7 +87,7 @@ const Exercicio1 = () => {
    - + ) } diff --git a/src/pages/Exercicio2.jsx b/src/pages/Exercicio2.jsx index 4286f4a..71d70fd 100644 --- a/src/pages/Exercicio2.jsx +++ b/src/pages/Exercicio2.jsx @@ -2,12 +2,13 @@ import { loadEvents } from '../../public/assets/js/exercicio2'; import '../../public/assets/css/exercicio2.css'; import { useEffect, useRef } from 'react'; import { toggleVisablity } from '../utils/utilidades.js' -import { Link } from '@tanstack/react-router'; +import { useNavigate, Link } from '@tanstack/react-router'; export var mazeCanvas, virtCanvas, context, imgData, ctx; const Exercicio2 = () => { const elementsCreated = useRef(false); + const navigate = useNavigate(); useEffect(() => { if (elementsCreated.current) return; @@ -25,6 +26,9 @@ const Exercicio2 = () => { return ( <> + + ← Continuar em outra hora ? +

    Navegar o labirinto

    @@ -32,9 +36,7 @@ const Exercicio2 = () => {

    Parabéns!

    Você finalizou a tarefa.

    - - - +
    @@ -56,4 +58,4 @@ const Exercicio2 = () => { ) } -export default Exercicio2; +export default Exercicio2; \ No newline at end of file diff --git a/src/pages/Exercicio3.jsx b/src/pages/Exercicio3.jsx index 2ae3307..98fe9ee 100644 --- a/src/pages/Exercicio3.jsx +++ b/src/pages/Exercicio3.jsx @@ -1,12 +1,13 @@ -import { loadEvents } from '../../public/assets/js/exercicio3'; -import '../../public/assets/css/exercicio3.css'; -import { useEffect, useRef } from 'react'; -import { Link } from '@tanstack/react-router'; +import { loadEvents } from "../../public/assets/js/exercicio3"; +import "../../public/assets/css/exercicio3.css"; +import { useEffect, useRef } from "react"; +import { Link, useNavigate } from "@tanstack/react-router"; export var mazeCanvas, virtCanvas, context, imgData, ctx; const Exercicio3 = () => { const elementsCreated = useRef(false); + const navigate = useNavigate(); useEffect(() => { if (elementsCreated.current) return; @@ -23,6 +24,13 @@ const Exercicio3 = () => { return ( <> + + ← Continuar em outra hora ? +

    Caça ao Tesouro

    @@ -30,32 +38,42 @@ const Exercicio3 = () => {

    Parabéns!

    Você finalizou a tarefa.

    - - - +
    -
    wave wave
    -
    - +
    +
    - -
    +
    Cadu
    -

    Nevege o labirinto com as flechas do teclado e segue minhas instruções para encontrar o tesouro.

    +

    + Nevege o labirinto com as flechas do teclado e segue minhas + instruções para encontrar o tesouro. +

    -
    +
    - ) -} + ); +}; export default Exercicio3; \ No newline at end of file diff --git a/src/pages/Exercicio4.jsx b/src/pages/Exercicio4.jsx index 24081ac..511cf26 100644 --- a/src/pages/Exercicio4.jsx +++ b/src/pages/Exercicio4.jsx @@ -1,11 +1,12 @@ -import { toggleVisablity } from '../utils/utilidades.js' +import { toggleVisablity } from "../utils/utilidades.js"; import "../../public/assets/css/exercicio4.css"; import { useEffect, useRef } from "react"; import { addDraggableDivs, createElments, dragDropEvents } from "../../public/assets/js/exercicio4"; -import { Link } from '@tanstack/react-router'; +import { useNavigate, Link } from "@tanstack/react-router"; const Exercicio4 = () => { const elementsCreated = useRef(false); + const navigate = useNavigate(); useEffect(() => { if (elementsCreated.current) return; @@ -21,21 +22,36 @@ const Exercicio4 = () => { createElments(puzzle, puzzleDivs, draggableDivs, cellsAmount); addDraggableDivs(draggableDivs, cells); - dragDropEvents(draggableDivs, puzzleDivs, modal, cellsAmount, attempt, modalBtn); + dragDropEvents( + draggableDivs, + puzzleDivs, + modal, + cellsAmount, + attempt, + modalBtn + ); elementsCreated.current = true; }, []); return ( <> + + ← Continuar em outra hora ? +

    Monte o Quebra-Cabeça

    -
    toggleVisablity("personagem")} style={{ visibility: "visible" }}> - Soso +
    toggleVisablity("personagem")} + style={{ visibility: "visible" }} + > + Soso

    Ajude-me a montar o quebra-cabeça com a foto da nossa turma @@ -49,9 +65,12 @@ const Exercicio4 = () => {

    Parabéns!

    Você finalizou a tarefa.

    - - - +
    diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 8fd2fa6..7540732 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -12,7 +12,7 @@ const Home = () => {

    Aprenda sobre tecnologia de forma divertida e interativa!

    - Ilustração + Ilustração
    diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 6724370..68f1a72 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,46 +1,99 @@ -import { Link } from '@tanstack/react-router'; -import '../../public/assets/css/login.css' +import { Link, useNavigate } from "@tanstack/react-router"; +import "../../public/assets/css/login.css"; +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { login } from "../api/User"; +import { useUser } from "../contexts/UserContext"; +import Loading from "../components/Loading"; const Login = () => { - return ( - <> - wave -
    -
    - grupoMascotes -
    -
    -
    -

    Login

    -
    -
    - -
    -
    -
    E-mail
    - -
    -
    -
    -
    - -
    -
    -
    Senha
    - -
    -
    - Esqueceu a sua senha? -
    - -
    -

    Não tem uma conta? Crie uma!

    -
    -
    -
    - - ); -}; + const [email, setEmail] = useState(""); + const [senha, setSenha] = useState(""); + const { armazenarLogin } = useUser(); + const navigate = useNavigate(); + + const mutation = useMutation({ + mutationKey: ["login"], + mutationFn: async ({ email, senha }) => { + return login(email, senha); + }, + onError: (e) => console.log(e), + onSuccess: (data) => { + armazenarLogin(data.user); + setTimeout(() => { + navigate({ to: "/home", reloadDocument: true }); + }, 800); + console.log("Cadastro realizado com sucesso!", data); + }, + }); -export default Login; + return ( + <> + + ← Voltar + + wave +
    +
    + grupoMascotes +
    + {mutation.isPending ? ( + + ) : ( +
    +
    mutation.mutate({ email, senha })}> +

    Login

    +
    +
    + +
    +
    +
    E-mail
    + setEmail(e.target.value)} + required + minLength="5" + maxLength="50" + pattern="[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$" + title="Digite um e-mail válido" + /> +
    +
    +
    +
    + +
    +
    +
    Senha
    + setSenha(e.target.value)} + required + minLength="8" + maxLength="20" + pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$" + title="A senha deve ter pelo menos 8 caracteres, incluindo letras e números" + /> +
    +
    + Esqueceu a sua senha? +
    + +
    +

    + Não tem uma conta? Crie uma! +

    +
    +
    + )} +
    + + ); +}; +export default Login; \ No newline at end of file diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 26d8940..cbcb938 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -18,6 +18,7 @@ import { Route as rootRoute } from './routes/__root' const StatisticsLazyImport = createFileRoute('/statistics')() const RegisterLazyImport = createFileRoute('/register')() +const PerformanceLazyImport = createFileRoute('/performance')() const LoginLazyImport = createFileRoute('/login')() const HomeLazyImport = createFileRoute('/home')() const IndexLazyImport = createFileRoute('/')() @@ -37,6 +38,12 @@ const RegisterLazyRoute = RegisterLazyImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/register.lazy').then((d) => d.Route)) +const PerformanceLazyRoute = PerformanceLazyImport.update({ + id: '/performance', + path: '/performance', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/performance.lazy').then((d) => d.Route)) + const LoginLazyRoute = LoginLazyImport.update({ id: '/login', path: '/login', @@ -86,6 +93,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginLazyImport parentRoute: typeof rootRoute } + '/performance': { + id: '/performance' + path: '/performance' + fullPath: '/performance' + preLoaderRoute: typeof PerformanceLazyImport + parentRoute: typeof rootRoute + } '/register': { id: '/register' path: '/register' @@ -116,6 +130,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexLazyRoute '/home': typeof HomeLazyRoute '/login': typeof LoginLazyRoute + '/performance': typeof PerformanceLazyRoute '/register': typeof RegisterLazyRoute '/statistics': typeof StatisticsLazyRoute '/exercise/$id': typeof ExerciseIdLazyRoute @@ -125,6 +140,7 @@ export interface FileRoutesByTo { '/': typeof IndexLazyRoute '/home': typeof HomeLazyRoute '/login': typeof LoginLazyRoute + '/performance': typeof PerformanceLazyRoute '/register': typeof RegisterLazyRoute '/statistics': typeof StatisticsLazyRoute '/exercise/$id': typeof ExerciseIdLazyRoute @@ -135,6 +151,7 @@ export interface FileRoutesById { '/': typeof IndexLazyRoute '/home': typeof HomeLazyRoute '/login': typeof LoginLazyRoute + '/performance': typeof PerformanceLazyRoute '/register': typeof RegisterLazyRoute '/statistics': typeof StatisticsLazyRoute '/exercise/$id': typeof ExerciseIdLazyRoute @@ -146,16 +163,25 @@ export interface FileRouteTypes { | '/' | '/home' | '/login' + | '/performance' | '/register' | '/statistics' | '/exercise/$id' fileRoutesByTo: FileRoutesByTo - to: '/' | '/home' | '/login' | '/register' | '/statistics' | '/exercise/$id' + to: + | '/' + | '/home' + | '/login' + | '/performance' + | '/register' + | '/statistics' + | '/exercise/$id' id: | '__root__' | '/' | '/home' | '/login' + | '/performance' | '/register' | '/statistics' | '/exercise/$id' @@ -166,6 +192,7 @@ export interface RootRouteChildren { IndexLazyRoute: typeof IndexLazyRoute HomeLazyRoute: typeof HomeLazyRoute LoginLazyRoute: typeof LoginLazyRoute + PerformanceLazyRoute: typeof PerformanceLazyRoute RegisterLazyRoute: typeof RegisterLazyRoute StatisticsLazyRoute: typeof StatisticsLazyRoute ExerciseIdLazyRoute: typeof ExerciseIdLazyRoute @@ -175,6 +202,7 @@ const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, HomeLazyRoute: HomeLazyRoute, LoginLazyRoute: LoginLazyRoute, + PerformanceLazyRoute: PerformanceLazyRoute, RegisterLazyRoute: RegisterLazyRoute, StatisticsLazyRoute: StatisticsLazyRoute, ExerciseIdLazyRoute: ExerciseIdLazyRoute, @@ -193,6 +221,7 @@ export const routeTree = rootRoute "/", "/home", "/login", + "/performance", "/register", "/statistics", "/exercise/$id" @@ -207,6 +236,9 @@ export const routeTree = rootRoute "/login": { "filePath": "login.lazy.jsx" }, + "/performance": { + "filePath": "performance.lazy.jsx" + }, "/register": { "filePath": "register.lazy.jsx" }, diff --git a/src/routes/__root.jsx b/src/routes/__root.jsx index 7328b44..2a7434b 100644 --- a/src/routes/__root.jsx +++ b/src/routes/__root.jsx @@ -1,14 +1,18 @@ -import * as React from 'react' -import { createRootRoute, Outlet } from '@tanstack/react-router' +import * as React from "react"; +import { createRootRoute, Outlet, useRouterState } from "@tanstack/react-router"; +import { ErrorBoundaryWithNavigate, Pagina404 }from "../components/ErrorBoundaries"; export const Route = createRootRoute({ - component: RootComponent, -}) + component: RootComponent, +}); function RootComponent() { - return ( - - - - ) + const { statusCode } = useRouterState(); + return ( + + + {statusCode === 404 ? : } + + + ); } diff --git a/src/routes/exercise/$id.lazy.jsx b/src/routes/exercise/$id.lazy.jsx index e5b7319..7e19132 100644 --- a/src/routes/exercise/$id.lazy.jsx +++ b/src/routes/exercise/$id.lazy.jsx @@ -1,6 +1,6 @@ import { createLazyFileRoute } from '@tanstack/react-router' import Exercicio1 from '../../pages/Exercicio1' -import Exercicio2 from '../../pages/exercicio2' +import Exercicio2 from '../../pages/Exercicio2' import Exercicio3 from '../../pages/Exercicio3' import Exercicio4 from '../../pages/Exercicio4' diff --git a/src/routes/login.lazy.jsx b/src/routes/login.lazy.jsx index 95ec8ac..4d036ae 100644 --- a/src/routes/login.lazy.jsx +++ b/src/routes/login.lazy.jsx @@ -1,5 +1,5 @@ import { createLazyFileRoute } from '@tanstack/react-router' -import Login from '../pages/login' +import Login from '../pages/Login' export const Route = createLazyFileRoute('/login')({ component: LoginComponent, diff --git a/src/routes/performance.lazy.jsx b/src/routes/performance.lazy.jsx new file mode 100644 index 0000000..b868d51 --- /dev/null +++ b/src/routes/performance.lazy.jsx @@ -0,0 +1,252 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import { useState } from "react"; +import "../../public/assets/css/performance.css"; +import Header from "../components/Header"; +import { Star } from "./statistics.lazy"; + +export const Route = createLazyFileRoute("/performance")({ + component: PerformanceComponent, +}); + +function PerformanceComponent() { + const keys = Object.keys(localStorage).filter((key) => + key.startsWith("Exer") + ); + + const defaultTodosExer = [ + [0, 2, "00:01:30"], + [0, 1, "00:02:10"], + [0, 3, "00:01:50"], + [0, 2, "00:03:00"], + ]; + + const todosExer = + keys.length === 4 + ? keys.map((key) => JSON.parse(localStorage.getItem(key))) + : defaultTodosExer; + + const pillars = [ + { name: "Decomposição", img: "/img/ana.png", lessons: 0, total: 1, color: "#D5C2E0", }, + { + name: "Reconhecimento de Padrões", + img: "/img/lilu.png", + lessons: 0, + total: 1, + color: "#F8DD56", + }, + { name: "Abstração", img: "/img/soso.png", lessons: 0, total: 1, color: "#78BD77" }, + { name: "Algoritmos", img: "/img/cadu.png", lessons: 0, total: 1, color: "#C2DAD6" }, + ]; + + const exercises = [ + { + name: "Organize as Cores do Arco-Íris", + time: todosExer[0][2], + tries: todosExer[0][1], + completed: true, + }, + { + name: "Navegar o labirinto", + time: todosExer[1][2], + tries: 0, + completed: true, + }, + { + name: "Caça ao Tesouro", + time: todosExer[2][2], + tries: 0, + completed: true, + }, + { + name: "Monte o Quebra-Cabeça", + time: todosExer[3][2], + tries: 0, + completed: true, + }, + ]; + + const [showModal, setShowModal] = useState(false); + + const ranking = [ + { pos: 1, user: "Ana Silva", score: 950 }, + { pos: 2, user: "João Souza", score: 900 }, + { pos: 3, user: "Maria Oliveira", score: 850 }, + { pos: 4, user: "Você", score: 800 }, + { pos: 5, user: "Lucas Ferreira", score: 750 }, + { pos: 6, user: "Pedro Almeida", score: 700 }, + { pos: 7, user: "Clara Costa", score: 650 }, + { pos: 8, user: "Rafaela Lima", score: 600 }, + { pos: 9, user: "Bruno Martins", score: 550 }, + { pos: 10, user: "Gabriel Rocha", score: 500 }, + ]; + const userRow = ranking.find(r => r.user === "Você"); + const userPos = userRow ? userRow.pos : 0; + + let filledStars = 0; + if (userPos === 1) filledStars = 3; + else if (userPos === 2) filledStars = 2; + else if (userPos === 3) filledStars = 1; + else filledStars = 0; + + return ( + <> +
    +
    +
    +
    + {pillars.map((pillar, index) => ( +
    +
    + {pillar.name} +

    {pillar.name}

    +
    +
    + + + + +
    + {`${((pillar.lessons / pillar.total) * 100)}%`} +
    +
    +
    + ))} +
    +
    +
    +

    Exercícios

    + + + + + + + + + + + {exercises.map((exercise, index) => ( + + + + + + + ))} + +
    NomeTempoTentativasStatus
    {exercise.name}{exercise.time}{exercise.tries} + + {exercise.completed ? "Concluído" : "Não Concluído"} + +
    +
    +
    +
    +
    +
    +
    +
    + {userPos ? `${userPos}º` : "--"} +
    +
    +
    + {Array(filledStars).fill().map((index) => ( + + ))} +
    +
    + {userPos && userPos <= 5 ? `Você está no Top ${userPos}!` : "Continue para subir no ranking!"} +
    +
    +
    +
    +

    Ranking

    + + {showModal && ( +
    setShowModal(false)}> +
    e.stopPropagation()} + > + +

    Sistema de Pontuação

    +
      +
    • +1000 pontos : Concluir todos os exercícios sem erros
    • +
    • +900 pontos : Concluir com até 1 erro
    • +
    • +800 pontos : Concluir com até 2 erros
    • +
    • +1 ponto : Para cada segundo a menos no tempo total
    • +
    • Estrelas : Top 1 (3⭐), Top 2-3 (2⭐), Top 4-5 (1⭐)
    • +
    +
    +
    + )} +
    +
    + + + + + + + + + + {ranking.map((row) => ( + + + + + + ))} + +
    PosiçãoUsuárioPontuação
    {row.pos}{row.user}{row.score}
    +
    +
    +
    +
    + + ); +} \ No newline at end of file diff --git a/src/routes/statistics.lazy.jsx b/src/routes/statistics.lazy.jsx index 07a8df1..5a52a9b 100644 --- a/src/routes/statistics.lazy.jsx +++ b/src/routes/statistics.lazy.jsx @@ -1,83 +1,169 @@ import { createLazyFileRoute, Link } from "@tanstack/react-router"; -import "../../public/assets/css/exercicio1.css"; +import "../../public/assets/css/statistics.css"; export const Route = createLazyFileRoute("/statistics")({ component: statisticsComponent, }); function statisticsComponent() { - const keys = Object.keys(localStorage).filter(key => key.startsWith('Exer')); - const todosExer = keys.map(key => JSON.parse(localStorage.getItem(key))); - + const keys = Object.keys(localStorage).filter((key) => + key.startsWith("Exer") + ); + const todosExer = keys.map((key) => JSON.parse(localStorage.getItem(key))); + console.log(todosExer) + + + return ( - <> -
    - {console.log(localStorage)} - {console.log(todosExer[3][3])} -
    -
    -

    - Estatísticas: -

    -

    +

    +
    +

    Parabéns

    +

    Você concluiu todas os exercícios

    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • + {todosExer[0][3]} 100 XP +
    +
    +

    Exercicio 1

    -

    - Exercicio Concluido: {todosExer[0][0]} -

    -

    - Tentativas: {todosExer[0][1]} -

    -

    - Tempo de Conclusão: {todosExer[0][2]} segundos -

    - -

    +

    +
    +
  • + {todosExer[0][2]} s +
    +
    +
  • + {todosExer[0][1]} +
    +
    + + Concluido +
    +
    +
    + +
    +
    +
    +
  • + {todosExer[0][3]} 100 XP +
    +
    +

    Exercicio 2

    -

    - Exercicio Concluido: {todosExer[1][0]} -

    -

    - Tentativas: {todosExer[1][1]} -

    -

    - Tempo de Conclusão: {todosExer[1][2]} segundos -

    - -

    +

    +
    +
  • + {todosExer[1][2]} s +
    +
    +
  • + {todosExer[1][1]} +
    +
    + + Concluido +
    +
    +
    + +
    +
    +
    +
  • + {todosExer[2][3]} 100 XP +
    +
    +

    Exercicio 3

    -

    - Tempo de Conclusão: {todosExer[2][0]} -

    -

    - Tentativas: {todosExer[2][1]} -

    -

    - Tempo Total: {todosExer[2][2]} segundos -

    - -

    +

    +
    +
  • + {todosExer[2][2]} s +
    +
    +
  • + {todosExer[2][1]} +
    +
    + + Concluido +
    +
    +
    + +
    +
    +
    +
  • + {todosExer[3][3]} 50 XP +
    +
    +

    Exercicio 4

    -

    - Exercicio Concluido: {todosExer[3][0]} -

    -

    - Tentativas: {todosExer[3][1]} -

    -

    - Tempo de Conclusão: {todosExer[3][2]} segundos -

    - - - +
    +
    +
  • + {todosExer[3][2]} s +
    +
    +
  • + {todosExer[3][1]} +
    +
    + + Concluido
    - + + + +
    ); } + + /* + gold:#C9B037 + silver:#B4B4B4 + bronze:#CD7F32 + */ + +export const Star = ({ width, height, color }) => ( + + + +); \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..41be2e9 --- /dev/null +++ b/vercel.json @@ -0,0 +1 @@ +{ "rewrites": [{ "source": "/(.*)", "destination": "/" }] } \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index f6d9d5b..860e305 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,19 +3,15 @@ import react from "@vitejs/plugin-react"; import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; export default defineConfig({ - plugins: [ - TanStackRouterVite(), - react({ - babel: { - plugins: [ - [ - "babel-plugin-react-compiler", - { - target: "19", - }, - ], - ], + plugins: [TanStackRouterVite(), react()], + server: { + proxy: { + '/': { + target: process.env.VITE_API_BASE_URL || 'http://localhost:3000', + changeOrigin: true, + secure: true, + rewrite: (path) => path.replace(/^\/api/, ''), }, - }), - ], + }, + }, });