Skip to content

Commit

Permalink
Merge pull request #278 from IndustryFusion/profile-menu
Browse files Browse the repository at this point in the history
Profile Menu
  • Loading branch information
LahariMIBS authored Nov 25, 2024
2 parents a84211c + 9735b90 commit 23b3b21
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 14 deletions.
4 changes: 4 additions & 0 deletions frontend/public/logout_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 2 additions & 14 deletions frontend/src/components/navBar/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useTranslation } from "next-i18next";
import { BreadCrumb } from "primereact/breadcrumb";
import "../../styles/navbar.css";
import ProfileDialog from "./profile-dialog";
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import { getAccessGroup } from "@/utility/indexed-db";
Expand All @@ -16,6 +15,7 @@ import { RootState } from "@/redux/store";
import { getUserDetails } from "@/utility/auth";
import Alerts from "@/components/alert/alerts"
import Language from "./language";
import ProfileMenu from "./profile-menu";

type NavbarProps = {
navHeader?: string;
Expand Down Expand Up @@ -294,21 +294,9 @@ const Navbar: React.FC<NavbarProps> = ({ navHeader, previousRoute }) => {
tooltipOptions={{ position: 'bottom' }}
style={{ color: '#6c757d' }}
/>
<div className="nav_avatar" onClick={() => setProfileDetail(true)}>
{(userData?.user_image && userData?.user_image.length > 0) ?
<img src={userData.user_image} alt="Image" style={{borderRadius: "100px"}} width="45" height="45" />
:
userData?.user_name.charAt(0).toUpperCase()
}
</div>
<ProfileMenu />
</div>
</div>
{profileDetail && (
<ProfileDialog
profileDetailProp={profileDetail}
setProfileDetailProp={setProfileDetail}
/>
)}
</>
);
};
Expand Down
174 changes: 174 additions & 0 deletions frontend/src/components/navBar/profile-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 
// Copyright (c) 2024 IB Systems GmbH 
// 
// Licensed under the Apache License, Version 2.0 (the "License"); 
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
// 
//  http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
// See the License for the specific language governing permissions and 
// limitations under the License. 
// 

import { clearIndexedDbOnLogout, getAccessGroup } from '@/utility/indexed-db'
import '../../styles/profile-menu.css'
import { useEffect, useRef, useState } from 'react';
import { getCompanyDetailsById } from '@/utility/auth';
import { fetchCompanyProduct, getUserDetails } from '@/utility/auth';
import Image from 'next/image';
import { Menu } from 'primereact/menu';
import { Button } from 'primereact/button';
import { showToast } from '@/utility/toast';
import { Toast } from 'primereact/toast';

interface Product {
_id: string;
product_id: string;
product_name: string;
company_id: string;
__v?: number;
}

interface UserData {
user_name: string;
user_email: string;
company_ifric_id: string;
company_name: string;
user_image: string;
user_role: string;
products: Product[];
}

export default function ProfileMenu() {
const [userData, setUserData] = useState<UserData | null>(null);
const menuTrigger = useRef<any>(null);
const toast = useRef<Toast>(null);
const ifxSuiteUrl = process.env.NEXT_PUBLIC_IFX_SUITE_FRONTEND_URL;
useEffect(() => {
fetchUserData();
}, []);

const fetchUserData = async () => {
try {
const accessGroupData = await getAccessGroup();
const companyDetails = await getCompanyDetailsById(accessGroupData.company_ifric_id);
const userDetails = await getUserDetails({
user_email: accessGroupData.user_email,
company_ifric_id: accessGroupData.company_ifric_id
})

const companyProducts = await fetchCompanyProduct(accessGroupData.company_ifric_id);

setUserData({
user_name: accessGroupData.user_name,
user_email: accessGroupData.user_email,
company_ifric_id: accessGroupData.company_ifric_id,
company_name: companyDetails?.data[0].company_name,
user_image: userDetails?.data[0].user_image,
user_role: accessGroupData.user_role,
products: companyProducts?.data
});
} catch (error) {
console.error('Error fetching user data:', error);
showToast(toast, 'error', 'Error', 'Error fetching user data');
}
};
const handleLogout = async () => {
try {
if (userData?.user_email) {
await clearIndexedDbOnLogout();
showToast(toast, 'success', 'Logout Successful', 'You have been logged out');
setTimeout(() => {
window.location.href = `${ifxSuiteUrl}/home`;
},500);
} else {
showToast(toast, 'error', 'Logout Failed', 'User email not found');
}
} catch (error) {
console.error("Logout failed:", error);
showToast(toast, 'error', 'Logout Failed', 'An error occurred during logout');
}
};
let items = [
{
template: () => {
return (
<div>
<Toast ref={toast} />
<div className="profile_bg">
<div className="title_company_name">{userData?.company_name}</div>
</div>
<div className="profile_modal_avatar">
{!userData?.user_image ? (
<div
className="profile_avatar_circle"
>
{userData?.user_name.charAt(0)}
</div>
) : (
<div
className="profile_avatar_circle"
>
<img
alt={userData?.user_name}
src={userData?.user_image}
draggable="false"
/>
</div>

)}
</div>
<div className="menu_modal_text_wrapper">
<div className='menu_modal_name'>{userData?.user_name}</div>
<div className='menu_modal_role'>{userData?.user_role.replace(/_/g, ' ')}</div>
</div>
<div className='menu_modal_email'>{userData?.user_email}</div>
<div className="profile_menu_divider"></div>
<div className='menu_title'>Products</div>
<div className="profile_menu_products">
{userData?.products.map(product => (
<div className='profile_menu_product_chip' key={product.product_id}>{product.product_name}</div>
))}
</div>
<div className="profile_menu_divider"></div>
<div className="profile_menu_link_wrapper">
<Button onClick={handleLogout} className='profile_menu_link logout'>
<Image src="/logout_icon.svg" width={22} height={22} alt=''></Image>
<div>Logout</div>
</Button>
</div>
</div>
);
}
}
];


return (
<>
<Button className='profile_menu_wrapper' onClick={(event) => menuTrigger.current.toggle(event)}>
{!userData?.user_image ? (
<div
className="user_avatar_circle"
>
{userData?.user_name.charAt(0)}
</div>
): (
<img
className="user_avatar_image_circle"
alt={userData?.user_name}
src={userData?.user_image}
width={45}
height={45}
draggable="false"
/>
)}
</Button>
<Menu model={items} popup ref={menuTrigger} className="profile_menu_modal" />
</>
)
}
176 changes: 176 additions & 0 deletions frontend/src/styles/profile-menu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
.profile_menu_wrapper {
width: 45px;
height: 45px;
border-radius: 50% !important;
background-color: #2b2b2b;
padding: 0px;
margin: 0px;
box-shadow: none;
border: none;
transition: filter 100ms ease-in-out;
font-family: "League Spartan";
}

.profile_menu_wrapper:focus,
.profile_menu_wrapper:hover {
filter: brightness(80%)
}

.user_avatar_circle {
display: grid;
place-items: center;
width: 45px;
height: 45px;
font-size: 28px;
font-weight: 500;
color: white;
border-radius: 50%;
}

.user_avatar_image_circle {
border-radius: 50%;
}

.profile_menu_modal {
padding: 0px 0px 10px 0px;
box-shadow: 0px 0px 0px 1px #00000010, 0px 4px 16px 0px #00000015;
border-radius: 5px;
overflow: hidden;
width: 360px;
font-family: "League Spartan";
margin-top: 4px;
border: none;
}

.menu_modal_title {
font-size: 0.75rem;
font-weight: 500;
color: #2b2b2b;
text-transform: uppercase;
}

.profile_bg {
height: 55px;
background-color: #efefef;
padding: 16px 84px 16px 84px;
display: flex;
align-items: center;
justify-content: center;
}

.title_company_name {
font-size: 15px;
color: #000;
font-weight: 500;
text-transform: capitalize;
}

.profile_avatar_circle {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
background-color: #2b2b2b;
color: white;
display: grid;
place-items: center;
font-size: 34px;
font-weight: 400;
padding-bottom: 3px;
margin: -30px 16px 0px 16px;
border: 2px solid #fff;
}

.profile_avatar_circle img {
width: 100%;
height: 100%;
object-fit: cover;
}

.menu_modal_text_wrapper {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
padding: 16px 16px 6px 16px;
}

.menu_modal_name {
color: #2b2b2b;
font-weight: 600;
font-size: 20px;
text-transform: capitalize;
}

.menu_modal_role {
color: #2b2b2b;
font-size: 12px;
text-transform: capitalize;
}

.menu_modal_email {
color: #888888;
font-size: 14px;
padding: 0px 16px;
}

.profile_menu_divider {
height: 1px;
width: calc(100% - 32px);
margin: 14px auto 10px auto;
background-color: #e7e7e7;
}

.profile_menu_link_wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
}

.profile_menu_link {
padding: 10px 16px;
color: #2b2b2b;
text-decoration: none;
background-color: white;
display: inline-block;
outline: none;
box-shadow: none;
display: flex;
gap: 8px;
align-items: center;
border: none;
font-family: inherit;
border-radius: 0px !important;
}

.profile_menu_link.logout {
color: #DE350B
}

.profile_menu_link:focus,
.profile_menu_link:hover {
background-color: #f3f3f5;
}

.menu_title {
color: #4e4e4e;
font-weight: 500;
font-size: 14px;
margin: 16px 16px 0px
}

.profile_menu_products {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 10px 16px 6px;
}

.profile_menu_product_chip {
padding: 5px 8px;
color: #2b2b2b;
border: 1px solid #e0e0e0;
background-color: white;
border-radius: 6px;
font-size: 13px;
}
Loading

0 comments on commit 23b3b21

Please sign in to comment.