feat: pagina de Perfil do usuario com preferencias (idioma, tema, senha) - #18
Merged
Conversation
Nao existia tela de perfil: o link do usuario na sidebar levava para
Configuracoes (que e configuracao da organizacao, nao da pessoa), e o
backend so tinha rotas de gestao de usuarios restritas a admin/owner -
um membro comum nao conseguia nem trocar a propria senha.
Backend - rotas de auto-servico em /api/users/me:
- GET /me perfil do proprio usuario
- PATCH /me nome + preferencias (idioma, tema), com validacao
- POST /me/password troca de senha exigindo a senha atual (diferente
do PATCH /:userId de admin, que redefine direto)
Login, magic link e /auth/me agora devolvem `preferences`, para o
frontend aplicar idioma/tema ja na autenticacao.
Frontend - nova pagina /profile:
- Cartao de identidade (avatar, papel, membro desde, ultimo acesso)
- Dados pessoais (nome editavel; e-mail somente leitura)
- Preferencias: idioma PT/EN e tema claro/escuro/sistema, salvos na
conta - antes o idioma so vivia no localStorage e nao acompanhava o
usuario em outro navegador. Aplica na hora e reverte se o save falhar.
- Seguranca: troca de senha
Sidebar passa a apontar para /profile. Textos em pt.json/en.json.
Contributor
There was a problem hiding this comment.
Pull request overview
Este PR adiciona uma página de Perfil do usuário no frontend e cria rotas de auto-serviço no backend para que usuários não-admin consigam visualizar/atualizar preferências (idioma/tema) e trocar a própria senha, além de propagar preferences no payload de autenticação para aplicar idioma/tema já no login.
Changes:
- Backend: novas rotas
/api/users/me,/api/users/me(PATCH) e/api/users/me/password, com validação básica de preferências e troca de senha exigindo senha atual. - Backend:
login,magic linke/auth/mepassam a retornarpreferences(language/theme). - Frontend: nova página
/profile, atualização deAuthContextpara aplicar idioma do perfil e ajuste da sidebar/rotas/i18n para suportar o novo fluxo.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/services/api.js | Adiciona métodos getMe/updateMe/changePassword para rotas de auto-serviço. |
| frontend/src/pages/Profile.jsx | Implementa UI de perfil (identidade, dados pessoais, preferências e segurança). |
| frontend/src/i18n/locales/pt.json | Adiciona traduções PT para a página de Perfil. |
| frontend/src/i18n/locales/en.json | Adiciona traduções EN para a página de Perfil. |
| frontend/src/contexts/AuthContext.jsx | Aplica idioma salvo em user.preferences durante login/checkAuth e expõe setUser. |
| frontend/src/components/Sidebar.jsx | Atualiza link do usuário para apontar para /profile. |
| frontend/src/components/Layout.jsx | Mapeia /profile para título traduzido no layout. |
| frontend/src/App.jsx | Registra a rota protegida profile. |
| backend/src/modules/users/users.routes.js | Implementa rotas /me (GET/PATCH) e /me/password com validação e auditoria. |
| backend/src/modules/auth/auth.routes.js | Inclui preferences no payload do usuário em login/magic link e /auth/me. |
Suppressed comments (2)
backend/src/modules/users/users.routes.js:105
- Em
POST /me/password, o usuário é carregado por_idsemtenant_id. Como o tenant já está disponível viatenantMiddlewaree o restante das rotas usa esse filtro, vale manter o escopo por tenant também aqui para garantir isolamento e consistência.
const user = await db.collection('users').findOne({ _id: request.currentUser._id });
backend/src/modules/users/users.routes.js:120
- Ainda em
POST /me/password, oupdateOneque grava o novo hash de senha não filtra portenant_id. Mesmo que_idnormalmente seja único, manter o filtro por tenant reduz risco em cenários de dados inconsistentes e segue o padrão do resto do módulo.
await db.collection('users').updateOne(
{ _id: user._id },
{ $set: { password: await bcrypt.hash(newPassword, 10), updated_at: new Date() } }
);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+52
to
+58
| if (name !== undefined) { | ||
| const trimmed = String(name).trim(); | ||
| if (!trimmed) { | ||
| return reply.status(400).send({ error: 'Nome não pode ficar vazio' }); | ||
| } | ||
| updates.name = trimmed; | ||
| } |
Comment on lines
+84
to
+85
| const updated = await db.collection('users').findOne({ _id: request.currentUser._id }); | ||
| return { success: true, user: publicProfile(updated) }; |
Comment on lines
+88
to
+102
| const handleLanguageChange = async (code) => { | ||
| if (code === profile?.preferences?.language) return; | ||
| // Aplica na hora; se o salvamento falhar, o idioma volta ao anterior. | ||
| const previous = i18n.language; | ||
| i18n.changeLanguage(code); | ||
| try { | ||
| const { data } = await userAPI.updateMe({ preferences: { language: code } }); | ||
| setProfile(data.user); | ||
| syncAuthUser(data.user); | ||
| toast.success(t('profile.saved')); | ||
| } catch (err) { | ||
| i18n.changeLanguage(previous); | ||
| toast.error(err.response?.data?.error || t('profile.saveError')); | ||
| } | ||
| }; |
Comment on lines
+272
to
+275
| className={currentLanguage === lang.code ? 'border-primary border-2' : ''} | ||
| style={{ width: '150px', cursor: 'pointer' }} | ||
| onClick={() => handleLanguageChange(lang.code)} | ||
| > |
Comment on lines
+298
to
+301
| className={theme === item.value ? 'border-primary border-2' : ''} | ||
| style={{ width: '150px', cursor: 'pointer' }} | ||
| onClick={() => handleThemeChange(item.value)} | ||
| > |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que resolve
Não existia tela de perfil no app. O link do usuário no rodapé da sidebar levava para Configurações, que é configuração da organização (tags, categorias, permissões), não da pessoa. E no backend só havia rotas de gestão de usuários restritas a admin/owner — ou seja, um membro comum não conseguia nem trocar a própria senha.
Também dá um lugar definitivo para a escolha de idioma: antes ela vivia só no
localStoragedo navegador e não acompanhava a conta.Backend — rotas de auto-serviço
GET /api/users/mePATCH /api/users/mePOST /api/users/me/passwordPATCH /:userIdde admin, que redefine diretologin,magic linke/auth/meagora devolvempreferences, para o frontend aplicar idioma/tema já na autenticação.Frontend — nova página
/profileA sidebar passa a apontar para
/profile. Todos os textos empt.json/en.json.Sobre "a opção de idioma não está aparecendo"
Investiguei: não é bug. O seletor PT/EN está no navbar desde o PR #17, que foi mesclado às 16:53. Sua aplicação estava rodando código anterior a esse merge.
Confirmei renderizando o navbar isoladamente no navegador (tema claro e escuro) — o botão aparece normalmente, em azul, entre o sino de notificações e o "Captura Rápida". Para vê-lo:
O
npm installimporta aqui porque o PR #17 adicionou dependências novas (react-i18nexte afins).Test plan
npm run buildsem erros/profile, trocar idioma, recarregar e confirmar que persistiumember(não-admin) para confirmar o auto-serviço