forked from code100x/job-board
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/user profile page (code100x#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
- Loading branch information
1 parent
4cd2b3d
commit 06258fc
Showing
19 changed files
with
1,047 additions
and
7 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }; | ||
} | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="md:container flex flex-col w-full"> | ||
<div className="flex justify-between items-center"> | ||
<span>Edit Profile</span> | ||
</div> | ||
<EditProfile name={user?.name || ''} email={user?.email || ''} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default EditProfilePage; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="container flex max-md:flex-col md:gap-5 w-full relative"> | ||
<Sidebar /> | ||
<div className="flex px-2 w-full overflow-y-auto md:max-h-[73vh] lg:h-full md:border md:rounded-xl md:pt-6 "> | ||
{children} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ProfileLayout; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="md:container flex flex-col w-full"> | ||
<div className="flex justify-between items-start mb-4 w-full"> | ||
<span className="w-full">My Account</span> | ||
<Link | ||
href={'/profile/edit'} | ||
className="sm:w-40 w-full items-center justify-center gap-4 md:flex hidden" | ||
> | ||
<Edit size={18} /> | ||
Edit Profile | ||
</Link> | ||
</div> | ||
<ProfileInfo /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ProfilePage; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="md:container flex flex-col w-full"> | ||
<div className="flex justify-between items-center mb-4"> | ||
<span>Account Settings</span> | ||
</div> | ||
<AccountSettings /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AccountSettingsPage; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ChangePassword } from './ChangePassword'; | ||
|
||
type Props = {}; | ||
export const AccountSettings = ({}: Props) => { | ||
return ( | ||
<div className="flex flex-col gap-6 w-full h-full"> | ||
<div className="flex flex-col gap-4 rounded-xl border p-3"> | ||
<p className="text-lg">Password and Authentication</p> | ||
<ChangePassword /> | ||
</div> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.