diff --git a/frontend/public/logout_icon.svg b/frontend/public/logout_icon.svg new file mode 100644 index 0000000..2365674 --- /dev/null +++ b/frontend/public/logout_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/navBar/navbar.tsx b/frontend/src/components/navBar/navbar.tsx index c85d417..d510f9b 100644 --- a/frontend/src/components/navBar/navbar.tsx +++ b/frontend/src/components/navBar/navbar.tsx @@ -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"; @@ -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; @@ -294,21 +294,9 @@ const Navbar: React.FC = ({ navHeader, previousRoute }) => { tooltipOptions={{ position: 'bottom' }} style={{ color: '#6c757d' }} /> - setProfileDetail(true)}> - {(userData?.user_image && userData?.user_image.length > 0) ? - - : - userData?.user_name.charAt(0).toUpperCase() - } - + - {profileDetail && ( - - )} > ); }; diff --git a/frontend/src/components/navBar/profile-menu.tsx b/frontend/src/components/navBar/profile-menu.tsx new file mode 100644 index 0000000..6105e93 --- /dev/null +++ b/frontend/src/components/navBar/profile-menu.tsx @@ -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(null); + const menuTrigger = useRef(null); + const toast = useRef(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 ( + + + + {userData?.company_name} + + + {!userData?.user_image ? ( + + {userData?.user_name.charAt(0)} + + ) : ( + + + + + )} + + + {userData?.user_name} + {userData?.user_role.replace(/_/g, ' ')} + + {userData?.user_email} + + Products + + {userData?.products.map(product => ( + {product.product_name} + ))} + + + + + + Logout + + + + ); + } + } + ]; + + + return ( + <> + menuTrigger.current.toggle(event)}> + {!userData?.user_image ? ( + + {userData?.user_name.charAt(0)} + + ): ( + + )} + + + > + ) +} \ No newline at end of file diff --git a/frontend/src/styles/profile-menu.css b/frontend/src/styles/profile-menu.css new file mode 100644 index 0000000..a9d4e2d --- /dev/null +++ b/frontend/src/styles/profile-menu.css @@ -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; +} \ No newline at end of file diff --git a/frontend/src/utility/auth.ts b/frontend/src/utility/auth.ts index 68bdcbe..e8bb0ff 100644 --- a/frontend/src/utility/auth.ts +++ b/frontend/src/utility/auth.ts @@ -295,4 +295,22 @@ export const getUserDetails = async (dataToSend: Record) => { throw error; } } +}; + +export const fetchCompanyProduct = async (dataCompanyIfricId: string) => { + try { + return await api.get(`${REGISTRY_API_URL}/auth/get-company-products/${dataCompanyIfricId}`,{ + headers: { + "Content-Type": "application/json", + }, + }); + + } catch (error: any) { + console.log('err from fetch company product ',error); + if (error?.response && error?.response?.status === 401) { + updatePopupVisible(true); + } else { + throw error; + } + } }; \ No newline at end of file