Skip to content

Commit

Permalink
feat: company subsidiaries and permission (#184)
Browse files Browse the repository at this point in the history
* fix: optimize permissions and add custom roles

* feat: add company Hierarchy

* feat: add company hierarchy extranal button

* feat: get the CompanySubsidiaries interface

* feat: display the"Switch Company" Panel on Buyer Portal

* fix: add constans z-index

* fix: add a method to determine the Level Permission

* fix: b2b-1567 bugs

* fix: modifying superAdmin role definition

* fix: b2b-1660 bugs

* fix: remove superadmin company hierarchy and add subsidiariesCompany permission

* fix: component Optimization

* feat: invocie aggregated view b2b-1566

* feat: order and detail aggregated view b2b-1566-1564

* fix: optimizing the code

* fix: order and invocie fix bugs

* fix: order detail SL btn

* fix: superadmin remove Company hierarchy

* fix: log in again to terminate the company hierarchy agent

* fix: super admin invocie action b2b-1659

* feat: add Settings in Django Admin to Control Account Hierarchy

* fix: company Hierarchy permission optimization

* fix: no address permission,Contact information is not displayed

* fix: translation for account hierarchy b2b-1758

* fix: ui change orderDetail change tips b2b-1808

* fix: company Hierarchy bugs

* feat: add address level permission and modify the files related to cspell

* feat: add quote and purchasability level permission and modify the files related to cspell

* fix: quotes level permission bugs

* fix: purchasability level permission bugs

* feat: user management permission

* feat: 1873 shoppingList permission

* fix: user and SL permission and component optimization

* fix: modify the files related to cspell

* fix: resolve eslint no-cycle issue in permissions utils

* fix: fix shoppingList permission bugs

* fix: remove unused file

* fix: resolve hierarchyDialog bug

* fix: resole permission bugs

* fix: optimize company subsidiaries and permission code

* fix: mobile version company hierarchy

* feat: companyHierarchy permission code separation and code optimization (#208)

* fix: optimize b3CheckPermissions utils

* fix: b3RolePermissions and b3CheckPermissions are merged into one utils

* fix: optimize the b3CheckPermissions utils and remove the use of the getB2BPermission method

* fix: use b2bPermissionsMap and import it separately

* feat: add permission hooks and merge shoppingList permission methods

* fix: add separate address permissions

* fix: add separate userManagement permissions

* fix: change style

* fix: order list get new Data when switch agency
  • Loading branch information
BrianJiang2021 authored Jan 20, 2025
1 parent cedada6 commit 8f24c17
Show file tree
Hide file tree
Showing 101 changed files with 3,915 additions and 718 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"msword",
"purchasability",
"registeredbctob2b",
"skus",
"clickaway",
"Turborepo"
],
// flagWords - list of words to be always considered incorrect
Expand Down
62 changes: 32 additions & 30 deletions apps/storefront/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { lazy, useContext, useEffect, useState } from 'react';
import { lazy, useContext, useEffect, useMemo, useState } from 'react';
import { HashRouter } from 'react-router-dom';

import { usePageMask } from '@/components';
Expand All @@ -10,8 +10,8 @@ import { CustomStyleContext } from '@/shared/customStyleButton';
import { GlobalContext } from '@/shared/global';
import { gotoAllowedAppPage } from '@/shared/routes';
import { setChannelStoreType } from '@/shared/service/b2b';
import { CustomerRole } from '@/types';
import {
b2bJumpPath,
getQuoteEnabled,
handleHideRegisterPage,
hideStorefrontElement,
Expand All @@ -28,7 +28,7 @@ import {
getTemPlateConfig,
setStorefrontConfig,
} from './utils/storefrontConfig';
import { CHECKOUT_URL } from './constants';
import { CHECKOUT_URL, PATH_ROUTES } from './constants';
import {
isB2BUserSelector,
rolePermissionSelector,
Expand All @@ -46,6 +46,10 @@ const B3MasqueradeGlobalTip = lazy(
() => import('@/components/outSideComponents/B3MasqueradeGlobalTip'),
);

const B3CompanyHierarchyExternalButton = lazy(
() => import('@/components/outSideComponents/B3CompanyHierarchyExternalButton'),
);

const HeadlessController = lazy(() => import('@/components/HeadlessController'));

const ThemeFrame = lazy(() => import('@/components/ThemeFrame'));
Expand All @@ -71,28 +75,12 @@ export default function App() {
const currentClickedUrl = useAppSelector(({ global }) => global.currentClickedUrl);
const isRegisterAndLogin = useAppSelector(({ global }) => global.isRegisterAndLogin);
const bcGraphqlToken = useAppSelector(({ company }) => company.tokens.bcGraphqlToken);
const companyRoleName = useAppSelector((state) => state.company.customer.companyRoleName);

const b2bPermissions = useAppSelector(rolePermissionSelector);

const { getShoppingListPermission, getOrderPermission } = b2bPermissions;
const [authorizedPages, setAuthorizedPages] = useState<string>('/orders');
const IsRealJuniorBuyer =
+role === CustomerRole.JUNIOR_BUYER && companyRoleName === 'Junior Buyer';

useEffect(() => {
let currentAuthorizedPages = authorizedPages;
const { quotesCreateActionsPermission, shoppingListCreateActionsPermission } =
useAppSelector(rolePermissionSelector);

if (isB2BUser) {
currentAuthorizedPages = getShoppingListPermission ? '/shoppingLists' : '/accountSettings';

if (getOrderPermission)
currentAuthorizedPages = IsRealJuniorBuyer ? currentAuthorizedPages : '/orders';
}

setAuthorizedPages(currentAuthorizedPages);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [IsRealJuniorBuyer, getShoppingListPermission, getOrderPermission]);
const authorizedPages = useMemo(() => {
return isB2BUser ? b2bJumpPath(role) : PATH_ROUTES.ORDERS;
}, [role, isB2BUser]);

const handleAccountClick = (href: string, isRegisterAndLogin: boolean) => {
showPageMask(true);
Expand Down Expand Up @@ -233,7 +221,7 @@ export default function App() {

init();
// ignore dispatch, gotoPage, loginAndRegister, setOpenPage, storeDispatch, styleDispatch
// due they are function that do not depend on any reactive value
// due they are functions that do not depend on any reactive value
// ignore href because is not a reactive value
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [b2bId, customerId, emailAddress, isAgenting, isB2BUser, role]);
Expand All @@ -246,9 +234,15 @@ export default function App() {
dispatch({
type: 'common',
payload: {
productQuoteEnabled,
cartQuoteEnabled,
shoppingListEnabled,
productQuoteEnabled: isB2BUser
? productQuoteEnabled && quotesCreateActionsPermission
: productQuoteEnabled,
cartQuoteEnabled: isB2BUser
? cartQuoteEnabled && quotesCreateActionsPermission
: cartQuoteEnabled,
shoppingListEnabled: isB2BUser
? shoppingListEnabled && shoppingListCreateActionsPermission
: shoppingListEnabled,
registerEnabled,
},
});
Expand All @@ -260,7 +254,15 @@ export default function App() {

// ignore dispatch due it's function that doesn't not depend on any reactive value
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isB2BUser, isAgenting, role, quoteConfig, storefrontConfig]);
}, [
isB2BUser,
isAgenting,
role,
quoteConfig,
storefrontConfig,
quotesCreateActionsPermission,
shoppingListCreateActionsPermission,
]);

useEffect(() => {
if (isOpen) {
Expand All @@ -280,7 +282,6 @@ export default function App() {
role,
isRegisterAndLogin,
isAgenting,
IsRealJuniorBuyer,
authorizedPages,
});

Expand Down Expand Up @@ -372,6 +373,7 @@ export default function App() {
</div>
</HashRouter>
<B3MasqueradeGlobalTip setOpenPage={setOpenPage} isOpen={isOpen} />
<B3CompanyHierarchyExternalButton setOpenPage={setOpenPage} isOpen={isOpen} />
<B3HoverButton
isOpen={isOpen}
productQuoteEnabled={productQuoteEnabled}
Expand Down
33 changes: 29 additions & 4 deletions apps/storefront/src/components/B3Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { ReactElement, ReactNode, useRef } from 'react';
import { useB3Lang } from '@b3/lang';
import { Box, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
import {
Box,
Breakpoint,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
SxProps,
Theme,
} from '@mui/material';

import useMobile from '@/hooks/useMobile';
import useScrollBar from '@/hooks/useScrollBar';
Expand All @@ -9,7 +18,7 @@ import { useAppSelector } from '@/store';
import CustomButton from './button/CustomButton';
import B3Spin from './spin/B3Spin';

interface B3DialogProps<T> {
export interface B3DialogProps<T> {
customActions?: () => ReactElement;
isOpen: boolean;
leftStyleBtn?: { [key: string]: string };
Expand All @@ -25,10 +34,12 @@ interface B3DialogProps<T> {
isShowBordered?: boolean;
showRightBtn?: boolean;
showLeftBtn?: boolean;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
maxWidth?: Breakpoint | false;
fullWidth?: boolean;
disabledSaveBtn?: boolean;
dialogContentSx?: { [key: string]: string };
dialogContentSx?: SxProps<Theme>;
dialogSx?: SxProps<Theme>;
dialogWidth?: string;
}

export default function B3Dialog<T>({
Expand All @@ -49,15 +60,28 @@ export default function B3Dialog<T>({
showLeftBtn = true,
maxWidth = 'sm',
dialogContentSx = {},
dialogSx = {},
fullWidth = false,
disabledSaveBtn = false,
dialogWidth = '',
}: B3DialogProps<T>) {
const container = useRef<HTMLInputElement | null>(null);

const [isMobile] = useMobile();

const isAgenting = useAppSelector(({ b2bFeatures }) => b2bFeatures.masqueradeCompany.isAgenting);

const customStyle = dialogWidth
? {
'& .MuiPaper-elevation': {
width: isMobile ? '100%' : dialogWidth,
},
...dialogSx,
}
: {
...dialogSx,
};

const handleSaveClick = () => {
if (handRightClick) {
if (row) handRightClick(row);
Expand Down Expand Up @@ -88,6 +112,7 @@ export default function B3Dialog<T>({
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
id="b2b-dialog-container"
sx={customStyle}
>
{title && (
<DialogTitle
Expand Down
72 changes: 42 additions & 30 deletions apps/storefront/src/components/B3DropDown.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useRef, useState } from 'react';
import { useB3Lang } from '@b3/lang';
import { forwardRef, Ref, useImperativeHandle, useRef, useState } from 'react';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp';
import { Box } from '@mui/material';
import { Box, MenuProps } from '@mui/material';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Menu from '@mui/material/Menu';
Expand All @@ -11,39 +10,48 @@ import MenuItem from '@mui/material/MenuItem';
import { useMobile } from '@/hooks';
import { disableLogoutButton } from '@/utils';

type ConfigProps = {
export interface ListItemProps {
name: string;
key: string | number;
};
}

export interface DropDownHandle {
setOpenDropDown: () => void;
}

interface B3DropDownProps<T> {
interface B3DropDownProps extends Partial<MenuProps> {
width?: string;
list: Array<T>;
config?: ConfigProps;
list: Array<ListItemProps>;
title: string;
handleItemClick: (arg0: T) => void;
handleItemClick?: (key: string | number) => void;
value?: string;
menuRenderItemName?: (item: ListItemProps) => JSX.Element | string;
}

export default function B3DropDown<T>({
width,
list,
config,
title,
value,
handleItemClick,
}: B3DropDownProps<T>) {
function B3DropDown(
{
width,
list,
title,
value,
handleItemClick,
menuRenderItemName = (item) => item.name,
...menu
}: B3DropDownProps,
ref: Ref<DropDownHandle>,
) {
const [isMobile] = useMobile();
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement | null>(null);
const b3Lang = useB3Lang();
const listRef = useRef<HTMLDivElement | null>(null);

useImperativeHandle(ref, () => ({
setOpenDropDown: () => setIsOpen(true),
}));

const close = () => {
setIsOpen(false);
};

const keyName = config?.name || 'name';

return (
<Box
sx={{
Expand All @@ -52,7 +60,7 @@ export default function B3DropDown<T>({
>
{!disableLogoutButton ? (
<ListItemButton
ref={ref}
ref={listRef}
onClick={() => setIsOpen(true)}
sx={{
pr: 0,
Expand Down Expand Up @@ -81,7 +89,7 @@ export default function B3DropDown<T>({
/>
)}
<Menu
anchorEl={ref.current}
anchorEl={listRef.current}
open={isOpen}
anchorOrigin={{
vertical: 'bottom',
Expand All @@ -98,30 +106,34 @@ export default function B3DropDown<T>({
'& .MuiList-root.MuiList-padding.MuiMenu-list': {
pt: isMobile ? 0 : '8px',
pb: isMobile ? 0 : '8px',
maxHeight: isMobile ? 'auto' : '200px',
},
}}
{...(menu || {})}
>
{list.length &&
list.map((item: any) => {
const name = item[keyName];
const color = value === item.key ? '#3385d6' : 'black';
list.map((item) => {
const { key } = item;
const color = value === key ? '#3385d6' : 'black';
return (
<MenuItem
sx={{
color,
width: isMobile ? 'auto' : width || '155px',
minWidth: isMobile ? 'auto' : width || '155px',
}}
key={name}
key={key}
onClick={() => {
close();
handleItemClick(item);
if (handleItemClick) handleItemClick(key);
}}
>
{b3Lang('global.button.logout')}
{menuRenderItemName(item)}
</MenuItem>
);
})}
</Menu>
</Box>
);
}

export default forwardRef<DropDownHandle, B3DropDownProps>(B3DropDown);
10 changes: 10 additions & 0 deletions apps/storefront/src/components/B3StoreContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode, useContext, useLayoutEffect } from 'react';

import { Z_INDEX } from '@/constants';
import { GlobalContext } from '@/shared/global';
import { getBCStoreChannelId } from '@/shared/service/b2b';
import { getGlobalTranslations, setStoreInfo, setTimeFormat, useAppDispatch } from '@/store';
Expand Down Expand Up @@ -27,6 +28,14 @@ export interface StoreBasicInfo {
storeName: string;
}

type ZIndexType = keyof typeof Z_INDEX;
const setZIndexVariables = () => {
Object.keys(Z_INDEX).forEach((key) => {
const zIndexKey = key as ZIndexType;
document.documentElement.style.setProperty(`--z-index-${key}`, Z_INDEX[zIndexKey].toString());
});
};

export default function B3StoreContainer(props: B3StoreContainerProps) {
const showPageMask = usePageMask();

Expand Down Expand Up @@ -89,6 +98,7 @@ export default function B3StoreContainer(props: B3StoreContainerProps) {
showPageMask(false);
}
};
setZIndexVariables();
getStoreBasicInfo();
// disabling because dispatchers are not supposed to be here
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
Loading

0 comments on commit 8f24c17

Please sign in to comment.