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 (
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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 (
+
+
+ );
+};
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 (
+
+
+
+
+ Delete Account
+
+
+
+
+
+ Are you sure you want to delete your account?
+
+
+ This action cannot be undone. This will permanently delete your
+ account and all associated data.
+
+
+
+
+ {isPending ? : 'Delete Account'}
+
+
+
+ Cancel
+
+
+
+
+
+ );
+};
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.
+
+
+
+
+
+
+
+
+
+ );
+};
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 (
+
+
+
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
+
+
+ Name
+
+
+
+ Email
+
+
+
+
+ );
+};
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 ? (
-
+
) : (
-
+
;
+
+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;