Skip to content

Commit

Permalink
Feat/user profile page (#453)
Browse files Browse the repository at this point in the history
* 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
CuriousCoder00 authored Oct 8, 2024
1 parent 4cd2b3d commit 06258fc
Show file tree
Hide file tree
Showing 19 changed files with 1,047 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/actions/upload-to-cdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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 };
Expand Down
127 changes: 127 additions & 0 deletions src/actions/user.profile.actions.ts
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 };
}
};
27 changes: 27 additions & 0 deletions src/app/profile/edit/page.tsx
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;
25 changes: 25 additions & 0 deletions src/app/profile/layout.tsx
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;
35 changes: 35 additions & 0 deletions src/app/profile/page.tsx
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;
25 changes: 25 additions & 0 deletions src/app/profile/settings/page.tsx
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;
13 changes: 13 additions & 0 deletions src/components/profile/AccountSettings.tsx
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>
);
};
Loading

0 comments on commit 06258fc

Please sign in to comment.