From 06258fc319508f583b8d1097a20d4f00a9ad1308 Mon Sep 17 00:00:00 2001 From: Kapil jangid <103230903+CuriousCoder00@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:42:36 +0530 Subject: [PATCH] Feat/user profile page (#453) * Add profile related paths to path.config.ts * Refactor user and admin navigation menus * Refactor authOptions.ts to include user name and email in the session * Refactor user.profile.actions.ts to include functions for updating user profile, changing password, updating avatar, and deleting user * Refactor profile layout and page components - Refactor the profile layout component to include a sidebar and handle session authentication. - Refactor the profile page component to display profile information and handle session authentication. - Add the profile info component to display user profile details. - Create a sidebar component for the profile page with navigation options. * Refactor profile edit page and components * Refactor profile settings page and components * Refactor profile layout styles * Refactor profile layout styles and fix session redirect bug * Refactor profile sidebar layout styles and update imports * Refactor profile picture upload functionality * Refactor ChangePassword component and improve password change functionality * Refactor AccountSettings component and improve delete account functionality * Refactor EditProfile component and improve form submission handling * Refactor mobile nav component and add profile link * Refactor: Added loader when performing account deletion action * Refactor: Update profile layout and sidebar styling * Refactor: Update file upload URLs to include file extension * Refactor: Update authOptions to include user image in session * Refactor: Update EditProfile component to remove unnecessary props and use session data for profile picture * Refactor: Update AccountSettings, ChangePassword, and EditProfile components * Refactor: Update user.profile.actions.ts and EditProfilePicture.tsx components - Add removeAvatar function to user.profile.actions.ts to remove user's avatar - Update EditProfilePicture.tsx component to include a removeImage function for removing the profile picture * Refactor: Update authOptions to include user image in session --- src/actions/upload-to-cdn.ts | 4 +- src/actions/user.profile.actions.ts | 127 ++++++++++++ src/app/profile/edit/page.tsx | 27 +++ src/app/profile/layout.tsx | 25 +++ src/app/profile/page.tsx | 35 ++++ src/app/profile/settings/page.tsx | 25 +++ src/components/profile/AccountSettings.tsx | 13 ++ src/components/profile/ChangePassword.tsx | 182 +++++++++++++++++ .../profile/DeleteAccountDialog.tsx | 85 ++++++++ src/components/profile/EditProfile.tsx | 185 ++++++++++++++++++ src/components/profile/EditProfilePicture.tsx | 148 ++++++++++++++ src/components/profile/ProfileInfo.tsx | 59 ++++++ src/components/profile/sidebar.tsx | 91 +++++++++ src/config/path.config.ts | 3 + src/layouts/header.tsx | 4 +- src/layouts/mobile-nav.tsx | 11 +- src/lib/authOptions.ts | 4 + src/lib/constant/app.constant.ts | 6 +- src/lib/validators/user.profile.validator.ts | 20 ++ 19 files changed, 1047 insertions(+), 7 deletions(-) create mode 100644 src/actions/user.profile.actions.ts create mode 100644 src/app/profile/edit/page.tsx create mode 100644 src/app/profile/layout.tsx create mode 100644 src/app/profile/page.tsx create mode 100644 src/app/profile/settings/page.tsx create mode 100644 src/components/profile/AccountSettings.tsx create mode 100644 src/components/profile/ChangePassword.tsx create mode 100644 src/components/profile/DeleteAccountDialog.tsx create mode 100644 src/components/profile/EditProfile.tsx create mode 100644 src/components/profile/EditProfilePicture.tsx create mode 100644 src/components/profile/ProfileInfo.tsx create mode 100644 src/components/profile/sidebar.tsx create mode 100644 src/lib/validators/user.profile.validator.ts diff --git a/src/actions/upload-to-cdn.ts b/src/actions/upload-to-cdn.ts index d57ea3c2..8eb3c2dc 100644 --- a/src/actions/upload-to-cdn.ts +++ b/src/actions/upload-to-cdn.ts @@ -14,7 +14,7 @@ export async function uploadFileAction(formData: FormData) { if (!file) { return { error: 'File is required', status: 400 }; } - const uploadUrl = `${CDN_BASE_UPLOAD_URL}/${uniqueFileName}`; + const uploadUrl = `${CDN_BASE_UPLOAD_URL}/${uniqueFileName}.webp`; const fileBuffer = Buffer.from(await file.arrayBuffer()); const response = await fetch(uploadUrl, { @@ -28,7 +28,7 @@ export async function uploadFileAction(formData: FormData) { if (response.ok) { return { message: 'File uploaded successfully', - url: `${CDN_BASE_ACCESS_URL}/${uniqueFileName}`, + url: `${CDN_BASE_ACCESS_URL}/${uniqueFileName}.webp`, }; } else { return { error: 'Failed to upload file', status: response.status }; diff --git a/src/actions/user.profile.actions.ts b/src/actions/user.profile.actions.ts new file mode 100644 index 00000000..9ba48b9f --- /dev/null +++ b/src/actions/user.profile.actions.ts @@ -0,0 +1,127 @@ +'use server'; +import prisma from '@/config/prisma.config'; +import { UserProfileSchemaType } from '@/lib/validators/user.profile.validator'; +import bcryptjs from 'bcryptjs'; + +export const updateUser = async ( + email: string, + data: UserProfileSchemaType +) => { + try { + const existingUser = await prisma.user.findFirst({ + where: { email: email }, + }); + if (!existingUser) return { error: 'User not found!' }; + + // TO-DO verifyEmail + + await prisma.user.update({ + where: { id: existingUser.id }, + data: { + name: data.name, + email: data.email, + }, + }); + return { success: 'Your profile has been successfully updated.' }; + } catch (error) { + return { error: error }; + } +}; + +export const changePassword = async ( + email: string, + data: { + currentPassword: string; + newPassword: string; + confirmNewPassword: string; + } +) => { + try { + const existingUser = await prisma.user.findFirst({ + where: { email: email }, + }); + const { currentPassword, newPassword, confirmNewPassword } = data; + if (!currentPassword || !newPassword || !confirmNewPassword) { + return { error: 'Password is required' }; + } + if (newPassword !== confirmNewPassword) { + return { error: 'Passwords do not match' }; + } + + if (!existingUser) return { error: 'User not found!' }; + if (!existingUser.password) { + return { error: 'User password not found!' }; + } + const matchPassword = await bcryptjs.compare( + currentPassword, + existingUser.password + ); + if (!matchPassword) { + return { error: 'Invalid credentials' }; + } + + const hashedPassword = await bcryptjs.hash(newPassword, 10); + + await prisma.user.update({ + where: { id: existingUser.id }, + data: { + password: hashedPassword, + }, + }); + + return { success: 'Your password has been successfully updated.' }; + } catch (error) { + return { error: error }; + } +}; + +export const updateAvatar = async (email: string, avatar: string) => { + try { + const existingUser = await prisma.user.findFirst({ + where: { email: email }, + }); + if (!existingUser) return { error: 'User not found!' }; + await prisma.user.update({ + where: { id: existingUser.id }, + data: { + avatar: avatar, + }, + }); + return { success: 'Your avatar has been successfully updated.' }; + } catch (error) { + return { error: error }; + } +}; + +export const removeAvatar = async (email: string) => { + try { + const existingUser = await prisma.user.findFirst({ + where: { email: email }, + }); + if (!existingUser) return { error: 'User not found!' }; + await prisma.user.update({ + where: { id: existingUser.id }, + data: { + avatar: null, + }, + }); + return { success: 'Your avatar has been successfully removed.' }; + } catch (error) { + return { error: error }; + } +}; + +export const deleteUser = async (email: string) => { + try { + const existingUser = await prisma.user.findFirst({ + where: { email: email }, + }); + if (!existingUser) return { error: 'User not found!' }; + await prisma.user.delete({ + where: { id: existingUser.id }, + }); + return { success: 'Your account has been successfully deleted.' }; + } catch (error) { + return { error: error }; + } +}; diff --git a/src/app/profile/edit/page.tsx b/src/app/profile/edit/page.tsx new file mode 100644 index 00000000..a5e50f8c --- /dev/null +++ b/src/app/profile/edit/page.tsx @@ -0,0 +1,27 @@ +'use client'; +import { EditProfile } from '@/components/profile/EditProfile'; +import APP_PATHS from '@/config/path.config'; +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; + +const EditProfilePage = () => { + const router = useRouter(); + const session = useSession(); + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile`); + }, [session.status, router]); + const user = session.data?.user; + + return ( +
+
+ Edit Profile +
+ +
+ ); +}; + +export default EditProfilePage; diff --git a/src/app/profile/layout.tsx b/src/app/profile/layout.tsx new file mode 100644 index 00000000..f69e44ca --- /dev/null +++ b/src/app/profile/layout.tsx @@ -0,0 +1,25 @@ +'use client'; +import Sidebar from '@/components/profile/sidebar'; +import APP_PATHS from '@/config/path.config'; +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; + +const ProfileLayout = ({ children }: { children: React.ReactNode }) => { + const router = useRouter(); + const session = useSession(); + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile`); + }, [session.status]); + return ( +
+ +
+ {children} +
+
+ ); +}; + +export default ProfileLayout; diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx new file mode 100644 index 00000000..d6213f14 --- /dev/null +++ b/src/app/profile/page.tsx @@ -0,0 +1,35 @@ +'use client'; +import { ProfileInfo } from '@/components/profile/ProfileInfo'; +import APP_PATHS from '@/config/path.config'; +import { Edit } from 'lucide-react'; +import { useSession } from 'next-auth/react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; + +const ProfilePage = () => { + const session = useSession(); + const router = useRouter(); + + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/create`); + }, [session.status]); + return ( +
+
+ My Account + + + Edit Profile + +
+ +
+ ); +}; + +export default ProfilePage; diff --git a/src/app/profile/settings/page.tsx b/src/app/profile/settings/page.tsx new file mode 100644 index 00000000..8ad13135 --- /dev/null +++ b/src/app/profile/settings/page.tsx @@ -0,0 +1,25 @@ +'use client'; +import { AccountSettings } from '@/components/profile/AccountSettings'; +import APP_PATHS from '@/config/path.config'; +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; + +const AccountSettingsPage = () => { + const router = useRouter(); + const session = useSession(); + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile`); + }, [session.status]); + return ( +
+
+ Account Settings +
+ +
+ ); +}; + +export default AccountSettingsPage; diff --git a/src/components/profile/AccountSettings.tsx b/src/components/profile/AccountSettings.tsx new file mode 100644 index 00000000..69da97c3 --- /dev/null +++ b/src/components/profile/AccountSettings.tsx @@ -0,0 +1,13 @@ +import { ChangePassword } from './ChangePassword'; + +type Props = {}; +export const AccountSettings = ({}: Props) => { + return ( +
+
+

Password and Authentication

+ +
+
+ ); +}; diff --git a/src/components/profile/ChangePassword.tsx b/src/components/profile/ChangePassword.tsx new file mode 100644 index 00000000..21edafd1 --- /dev/null +++ b/src/components/profile/ChangePassword.tsx @@ -0,0 +1,182 @@ +'use client'; + +import { useState, useTransition } from 'react'; + +import { useForm } from 'react-hook-form'; +import { useSession } from 'next-auth/react'; +import { useToast } from '../ui/use-toast'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { + UserPasswordSchema, + UserPasswordSchemaType, +} from '@/lib/validators/user.profile.validator'; + +import { Input } from '../ui/input'; +import { EyeIcon, EyeOffIcon } from 'lucide-react'; +import { Button } from '../ui/button'; +import { changePassword } from '@/actions/user.profile.actions'; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../ui/form'; +import Loader from '../loader'; + +export const ChangePassword = () => { + const session = useSession(); + const { toast } = useToast(); + + const { register, watch } = useForm(); + + const [showPassword, setShowPassword] = useState(false); + + const [isPending, startTransition] = useTransition(); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const form = useForm({ + resolver: zodResolver(UserPasswordSchema), + defaultValues: { + currentPassword: '', + newPassword: '', + confirmNewPassword: '', + }, + }); + + const handleInputChange = (e: React.ChangeEvent) => { + form.setValue( + e.target.name as keyof UserPasswordSchemaType, + e.target.value + ); + }; + + const handleFormSubmit = async (data: UserPasswordSchemaType) => { + try { + startTransition(() => { + changePassword(session.data?.user.email as string, data) + .then((res) => { + res?.error + ? toast({ + title: (res.error as string) || 'something went wrong', + variant: 'destructive', + }) + : toast({ title: res.success as string, variant: 'success' }); + }) + .then(() => { + form.reset(); + }); + }); + } catch (error: any) { + toast({ + title: error?.message || 'Internal server error', + variant: 'destructive', + }); + } + }; + + return ( +
+ +

Change your password

+ ( + + Current Password + +
+ +
+
+ +
+ )} + /> + ( + + New Password + +
+ +
+
+ +
+ )} + /> + ( + + Confirm New Password + +
+ { + return val !== watch('newPassword') + ? 'Passwords do not match' + : true; + }, + })} + onChange={handleInputChange} + className="rounded focus-visible:ring-0 focus:outline-none focus:border-slate-500" + /> + +
+
+ +
+ )} + /> + +
+ +
+ + + ); +}; diff --git a/src/components/profile/DeleteAccountDialog.tsx b/src/components/profile/DeleteAccountDialog.tsx new file mode 100644 index 00000000..617cc3c7 --- /dev/null +++ b/src/components/profile/DeleteAccountDialog.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useTransition } from 'react'; + +import { signOut, useSession } from 'next-auth/react'; +import { useToast } from '../ui/use-toast'; + +import { Button } from '../ui/button'; +import Loader from '../loader'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, + DialogClose, +} from '@/components/ui/dialog'; + +import { deleteUser } from '@/actions/user.profile.actions'; +import { Trash } from 'lucide-react'; + +export const DeleteAccountDialog = () => { + const { toast } = useToast(); + const session = useSession(); + + const [isPending, startTransition] = useTransition(); + + const handleDeleteAccount = async () => { + startTransition(() => { + deleteUser(session.data?.user.email as string) + .then((res) => { + res.error + ? toast({ + title: res.error as string, + variant: 'destructive', + }) + : toast({ + title: res.success as string, + variant: 'success', + }); + }) + .then(() => { + signOut(); + }); + }); + }; + return ( + + + + + + + + Are you sure you want to delete your account? + + + This action cannot be undone. This will permanently delete your + account and all associated data. + + +
+ + + + +
+
+
+ ); +}; diff --git a/src/components/profile/EditProfile.tsx b/src/components/profile/EditProfile.tsx new file mode 100644 index 00000000..bdaffb90 --- /dev/null +++ b/src/components/profile/EditProfile.tsx @@ -0,0 +1,185 @@ +'use client'; + +import { useEffect, useTransition } from 'react'; + +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; + +import APP_PATHS from '@/config/path.config'; +import { getNameInitials } from '@/lib/utils'; +import { EditProfilePicture } from './EditProfilePicture'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Input } from '@/components/ui/input'; +import { Button } from '../ui/button'; + +import { + UserProfileSchema, + UserProfileSchemaType, +} from '@/lib/validators/user.profile.validator'; + +import { updateUser } from '@/actions/user.profile.actions'; +import { useToast } from '../ui/use-toast'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../ui/form'; + +import Loader from '../loader'; +import { DeleteAccountDialog } from './DeleteAccountDialog'; + +type Props = { + name: string; + email: string; +}; + +export const EditProfile = ({ name, email }: Props) => { + const router = useRouter(); + const session = useSession(); + const { toast } = useToast(); + + const [isPending, startTransition] = useTransition(); + + const user = session.data?.user; + const form = useForm({ + resolver: zodResolver(UserProfileSchema), + defaultValues: { + name: session.data?.user.name, + email: session.data?.user.email, + }, + }); + + useEffect(() => { + form.setValue('name', name); + form.setValue('email', email); + }, [user]); + + const handleInputChange = (e: React.ChangeEvent) => { + form.setValue(e.target.name as keyof UserProfileSchemaType, e.target.value); + }; + + const handleFormSubmit = async (data: UserProfileSchemaType) => { + try { + startTransition(() => { + updateUser(user?.email || email, data) + .then((res) => { + res.error + ? toast({ + title: (res.error as string) || 'something went wrong', + variant: 'destructive', + }) + : toast({ title: res.success as string, variant: 'success' }); + }) + .then(() => { + session.update({ ...session, user: { ...user, ...data } }); + }); + }); + } catch (error: any) { + toast({ + title: error?.message || 'Internal server error', + variant: 'destructive', + }); + } + }; + + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile/edit`); + }); + + return ( +
+
+ + + + + + {getNameInitials(user?.name ?? '')} + + + + + + Profile Picture + + A profile picture helps others recognize you. + + + + + +
+
+ +
+ Profile Info + +
+ ( + + Name + + + + + + )} + /> + ( + + Email + + + + + + )} + /> +
+ +
+ + +
+ ); +}; diff --git a/src/components/profile/EditProfilePicture.tsx b/src/components/profile/EditProfilePicture.tsx new file mode 100644 index 00000000..4ad6fb48 --- /dev/null +++ b/src/components/profile/EditProfilePicture.tsx @@ -0,0 +1,148 @@ +'use client'; + +import React, { useEffect, useTransition } from 'react'; + +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import { useToast } from '../ui/use-toast'; + +import { uploadFileAction } from '@/actions/upload-to-cdn'; +import { updateAvatar } from '@/actions/user.profile.actions'; + +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; + +import { PencilIcon, Trash } from 'lucide-react'; +import APP_PATHS from '@/config/path.config'; +import { getNameInitials } from '@/lib/utils'; +import Loader from '../loader'; + +export const EditProfilePicture = () => { + const router = useRouter(); + const session = useSession(); + const { toast } = useToast(); + + const name = getNameInitials(session.data?.user.name as string); + const [isPending, startTransition] = useTransition(); + + const uploadImage = async (e: React.ChangeEvent) => { + startTransition(() => { + const file = e.target.files?.[0]; + if (!file) return; + + const formData = new FormData(); + + formData.append('file', file); + // const fileName = file.name; + // formData.append('uniqueFileName', fileName); + uploadFileAction(formData).then((response) => { + if (response.error) { + toast({ + title: response.error, + variant: 'destructive', + }); + } else { + updateAvatar( + session.data?.user.email as string, + response?.url as string + ) + .then((res) => { + res.error + ? toast({ + title: res.error as string, + variant: 'destructive', + }) + : toast({ + title: res.success, + variant: 'success', + }); + }) + .then(() => { + session.update({ + ...session, + user: { ...session.data?.user, image: response.url }, + }); + }); + } + }); + }); + }; + + const removeImage = () => { + startTransition(() => { + updateAvatar(session.data?.user.email as string, '') + .then((res) => { + res.error + ? toast({ + title: res.error as string, + variant: 'destructive', + }) + : toast({ + title: res.success, + variant: 'success', + }); + }) + .then(() => { + session.update({ + ...session, + user: { ...session.data?.user, image: '' }, + }); + }); + }); + }; + + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile/edit`); + }, [session.status, router]); + + return ( +
+
+ + + {name} + +
+ Accepts .PNG, .JPEG, .JPG +
+ + + +
+
+ ); +}; diff --git a/src/components/profile/ProfileInfo.tsx b/src/components/profile/ProfileInfo.tsx new file mode 100644 index 00000000..181c3c49 --- /dev/null +++ b/src/components/profile/ProfileInfo.tsx @@ -0,0 +1,59 @@ +'use client'; + +import React, { useEffect } from 'react'; + +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; + +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; + +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; + +import APP_PATHS from '@/config/path.config'; + +import { getNameInitials } from '@/lib/utils'; + +export const ProfileInfo = () => { + const router = useRouter(); + const session = useSession(); + + useEffect(() => { + if (session.status !== 'loading' && session.status === 'unauthenticated') + router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile`); + }, [session.status]); + + return ( +
+
+ + + + {getNameInitials(session.data?.user.name ?? '')} + + +
+
+
+ Profile Info +
+
+ + +
+
+ + +
+
+
+ ); +}; diff --git a/src/components/profile/sidebar.tsx b/src/components/profile/sidebar.tsx new file mode 100644 index 00000000..04e40189 --- /dev/null +++ b/src/components/profile/sidebar.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { useSession } from 'next-auth/react'; +import { usePathname } from 'next/navigation'; + +import Link from 'next/link'; + +import { + Sheet, + SheetClose, + SheetContent, + SheetTrigger, +} from '@/components/ui/sheet'; +import { Separator } from '@/components/ui/separator'; + +import { userProfileNavbar } from '@/lib/constant/app.constant'; +import { MenuIcon, XIcon } from 'lucide-react'; + +const Sidebar = () => { + return ( +
+ + +
+ ); +}; + +const DesktopSidebar = () => { + return ( +
+
+ +
+
+ ); +}; + +const MobileSidebar = () => { + return ( +
+ + + + + + + + +
+ +
+
+
+
+ ); +}; + +const SidebarNavs = () => { + return ( +
+
+ + User Settings + + +
+ {userProfileNavbar.map((nav) => ( + + ))} +
+ ); +}; + +const NavItem = ({ path, label }: { path: string; label: string }) => { + const session = useSession(); + const pathname = usePathname(); + if (session.status === 'loading') return; + if (!session.data?.user) return; + if (!session) return; + return ( + + {label} + + ); +}; + +export default Sidebar; diff --git a/src/config/path.config.ts b/src/config/path.config.ts index d1afa84f..9809adc6 100644 --- a/src/config/path.config.ts +++ b/src/config/path.config.ts @@ -12,5 +12,8 @@ const APP_PATHS = { VERIFY_EMAIL: '/verify-email', FORGOT_PASSWORD: '/forgot-password', WELCOME: '/welcome', + PROFILE: '/profile', + EDIT_PROFILE: '/profile/edit', + ACCOUNT_SETTINGS: '/profile/settings', }; export default APP_PATHS; diff --git a/src/layouts/header.tsx b/src/layouts/header.tsx index a3c19f54..2f345a32 100644 --- a/src/layouts/header.tsx +++ b/src/layouts/header.tsx @@ -142,7 +142,9 @@ const Header = () => { > - Profile + + Profile +
    {session.status !== 'loading' && session.data?.user && ( -
    +
    { + router.push(APP_PATHS.PROFILE); + }} + > {session.data?.user.role === ADMIN_ROLE || session.data?.user.role === HR_ROLE ? ( -
    +

    HS

    ) : ( -
    +
    ; + +export const UserPasswordSchema = z.object({ + currentPassword: z + .string() + .min(6, 'Password must be at least of 6 characters.'), + newPassword: z.string().min(6, 'Password must be at least of 6 characters.'), + confirmNewPassword: z + .string() + .min(6, 'Password must be at least of 6 characters.'), +}); + +export type UserPasswordSchemaType = z.infer;