From fa667e3adf6970c38cd8ecd36c66ee6160e499f6 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Tue, 20 Jan 2026 16:01:24 -0300 Subject: [PATCH] feat(profile): create profile page --- src/app/(dashboard)/perfil/page.tsx | 156 ++++++++++++++++++ .../profile/change-password-button.tsx | 23 +++ src/modules/profile/change-password-modal.tsx | 120 ++++++++++++++ src/modules/profile/profile-form.tsx | 42 +++++ 4 files changed, 341 insertions(+) create mode 100644 src/app/(dashboard)/perfil/page.tsx create mode 100644 src/modules/profile/change-password-button.tsx create mode 100644 src/modules/profile/change-password-modal.tsx create mode 100644 src/modules/profile/profile-form.tsx diff --git a/src/app/(dashboard)/perfil/page.tsx b/src/app/(dashboard)/perfil/page.tsx new file mode 100644 index 00000000..5d9d0513 --- /dev/null +++ b/src/app/(dashboard)/perfil/page.tsx @@ -0,0 +1,156 @@ +'use client' + +import { ArrowLeftIcon, Loader2 } from 'lucide-react' +import { useEffect, useState } from 'react' + +import { getCurrentUser } from '@/actions/users' +import { DashboardContainer } from '@/components/dashboard/container' +import { Avatar } from '@/components/ui/avatar' +import { Button } from '@/components/ui/button' +import { DatePicker } from '@/components/ui/date-picker' +import { Divider } from '@/components/ui/divider' +import { Input } from '@/components/ui/input' +import { NavButton } from '@/components/ui/nav-button' +import { ChangePasswordButton } from '@/modules/profile/change-password-button' +import { ProfileForm } from '@/modules/profile/profile-form' + +interface UserProfile { + category?: string + register?: string + avatar_url?: string | null | undefined + entryDate?: string | null | undefined + role: 'specialist' +} + +export default function Page() { + const [user, setUser] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + async function fetchUser() { + try { + const data = await getCurrentUser() + + if (!data) { + setUser(null) + return + } + + const formattedUser: UserProfile = { + ...data, + role: 'specialist', + entryDate: data.created_at + ? new Date(data.created_at).toLocaleDateString('pt-BR') + : undefined, + category: (data as UserProfile).category ?? '', + register: (data as UserProfile).register ?? '', + avatar_url: (data as UserProfile).avatar_url ?? '', + } + + setUser(formattedUser) + } catch (error) { + console.error('Erro ao carregar dados do usuário:', error) + } finally { + setLoading(false) + } + } + + fetchUser() + }, []) + + if (loading) { + return ( +
+ +
+ ) + } + + if (!user) { + return ( +
+ Não foi possível carregar suas informações. +
+ ) + } + + const isEspecialist = user.role?.toLowerCase() === 'especialist' + + return ( +
+ + + + Voltar + +
+
+ + +
+
+ Imagem + + Min 400x400px, PNG ou JPEG + +
+
+
+ + +
+ +
+ + +
+ + {isEspecialist && ( + <> +
+ + +
+ +
+ + +
+ + )} + +
+ +
+
+
+ + + +
+
+ ) +} diff --git a/src/modules/profile/change-password-button.tsx b/src/modules/profile/change-password-button.tsx new file mode 100644 index 00000000..859e179d --- /dev/null +++ b/src/modules/profile/change-password-button.tsx @@ -0,0 +1,23 @@ +'use client' + +import { RectangleEllipsisIcon } from 'lucide-react' +import { useState } from 'react' + +import { Dialog, DialogTrigger } from '@/components/ui/dialog' + +import { ChangePasswordModal } from './change-password-modal' + +export function ChangePasswordButton() { + const [dialogOpen, setDialogOpen] = useState(false) + + return ( + + + + Alterar senha + + + {dialogOpen && } + + ) +} diff --git a/src/modules/profile/change-password-modal.tsx b/src/modules/profile/change-password-modal.tsx new file mode 100644 index 00000000..0fff025b --- /dev/null +++ b/src/modules/profile/change-password-modal.tsx @@ -0,0 +1,120 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { FormProvider, useForm } from 'react-hook-form' +import { z } from 'zod' + +import { FormContainer } from '@/components/form/form-container' +import { FormField } from '@/components/form/form-field' +import { PasswordInput } from '@/components/form/password-input' +import { Alert } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { + DialogClose, + DialogContainer, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' + +// TODO: create a shared schema for password change and new password forms +const changePasswordSchema = z + .object({ + password: z + .string() + .min(1, 'Insira sua senha') + .min(8, 'Sua senha precisa conter 8 ou mais caracteres') + .regex(/^(?=.*[A-Z])(?=.*\d)/, 'Senha inválida'), + confirmPassword: z.string(), + currentPassword: z.string().min(1, 'Digite sua senha atual'), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Suas senhas não coincidem', + path: ['confirmPassword'], + }) + +type ChangePasswordSchema = z.infer + +interface PasswordModalProps { + onOpenChange: (open: boolean) => void +} + +export function ChangePasswordModal({ onOpenChange }: PasswordModalProps) { + const methods = useForm({ + resolver: zodResolver(changePasswordSchema), + defaultValues: { password: '', confirmPassword: '', currentPassword: '' }, + mode: 'onBlur', + }) + + const isSubmitting = methods.formState.isSubmitting + const errorMessage = methods.formState.errors.root?.message + const success = false + + async function onSubmit(data: ChangePasswordSchema) { + console.log('Dados enviados para troca de senha:', data) + onOpenChange(false) + } + + return ( + + + Alterar senha + + + + + + + + + + + + + + + + + Cancelar + + + + {success && ( + + Senha atualizada com sucesso. + + )} + + {errorMessage && ( + + {errorMessage} + + )} + + + + + ) +} diff --git a/src/modules/profile/profile-form.tsx b/src/modules/profile/profile-form.tsx new file mode 100644 index 00000000..e5191e75 --- /dev/null +++ b/src/modules/profile/profile-form.tsx @@ -0,0 +1,42 @@ +'use client' + +import { FormProvider, useForm } from 'react-hook-form' + +import { DateInput } from '@/components/form/date-input' +import { FormContainer } from '@/components/form/form-container' +import { FormField } from '@/components/form/form-field' +import { TextInput } from '@/components/form/text-input' + +export function ProfileForm() { + const formMethods = useForm({ + defaultValues: { + name: 'Claudio Luiz Oliveira', + entry_date: String(new Date()), + professional: 'Enfermagem', + specialty: 'Enfermagem', + professional_registration: 'COREN-SP 112233', + }, + mode: 'onBlur', + }) + + return ( + + + + + + + + + + + + + + + ) +}