@smartbooks-ai/layout 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/PageWithMenuLayout/AppSelect/index.d.ts +0 -1
- package/dist/components/PageWithMenuLayout/AppSelect/index.d.ts.map +1 -1
- package/dist/components/PageWithMenuLayout/AppSelect/index.js +2 -2
- package/dist/components/PageWithMenuLayout/PageWithMenuLayout.d.ts +0 -1
- package/dist/components/PageWithMenuLayout/PageWithMenuLayout.d.ts.map +1 -1
- package/dist/components/PageWithMenuLayout/PageWithMenuLayout.js +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/theme/colorPrimitives.d.ts +1 -1
- package/dist/theme/colorPrimitives.d.ts.map +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/typography.d.ts.map +1 -1
- package/package.json +35 -32
- package/src/components/PageHeader/PageHeader.tsx +0 -15
- package/src/components/PageHeader/index.ts +0 -1
- package/src/components/PageHeader/styles.ts +0 -34
- package/src/components/PageWithMenuLayout/AppSelect/index.tsx +0 -66
- package/src/components/PageWithMenuLayout/AppSelect/styles.ts +0 -33
- package/src/components/PageWithMenuLayout/LogoHeaderImage.tsx +0 -44
- package/src/components/PageWithMenuLayout/LogoHeaderText.tsx +0 -48
- package/src/components/PageWithMenuLayout/MenuItemWithChildren/MenuItemWithChildren.tsx +0 -149
- package/src/components/PageWithMenuLayout/MenuItemWithChildren/styles.ts +0 -179
- package/src/components/PageWithMenuLayout/MenuSelect/index.tsx +0 -78
- package/src/components/PageWithMenuLayout/MenuSelect/styles.ts +0 -97
- package/src/components/PageWithMenuLayout/MultiSubscriptionsMenuItems/ConsolidationIcon.tsx +0 -6
- package/src/components/PageWithMenuLayout/MultiSubscriptionsMenuItems/MultiSubscriptionsMenuItems.tsx +0 -120
- package/src/components/PageWithMenuLayout/MultiSubscriptionsMenuItems/consolidation.svg +0 -8
- package/src/components/PageWithMenuLayout/MultiSubscriptionsMenuItems/index.ts +0 -1
- package/src/components/PageWithMenuLayout/MultiSubscriptionsMenuItems/styles.ts +0 -10
- package/src/components/PageWithMenuLayout/PageWithMenuLayout.tsx +0 -103
- package/src/components/PageWithMenuLayout/UserProfileSelect/index.tsx +0 -64
- package/src/components/PageWithMenuLayout/UserProfileSelect/styles.ts +0 -8
- package/src/components/PageWithMenuLayout/index.ts +0 -8
- package/src/components/PageWithMenuLayout/styles.ts +0 -110
- package/src/components/PageWithMenuLayout/types.ts +0 -7
- package/src/components/PageWithMenuLayout/useMenuToggle.ts +0 -19
- package/src/components/index.ts +0 -3
- package/src/emotion.d.ts +0 -76
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useIsAuthorized.ts +0 -35
- package/src/hooks/useToggle.ts +0 -27
- package/src/index.ts +0 -7
- package/src/package-isolation.test.ts +0 -60
- package/src/security/AuthorizedContent/index.tsx +0 -77
- package/src/security/AuthorizedContent/state.ts +0 -8
- package/src/security/AuthorizedContent/useAuthorizationState.ts +0 -42
- package/src/security/ProfileContext/ProfileContext.tsx +0 -37
- package/src/security/ProfileContext/index.ts +0 -4
- package/src/security/ProfileContext/types.ts +0 -7
- package/src/security/ProfileContext/useProfile.tsx +0 -7
- package/src/security/UserProfile.ts +0 -48
- package/src/security/index.ts +0 -2
- package/src/theme/colorPrimitives.ts +0 -107
- package/src/theme/colors.ts +0 -78
- package/src/theme/font.ts +0 -27
- package/src/theme/globalStyles.tsx +0 -55
- package/src/theme/index.tsx +0 -228
- package/src/theme/radius.ts +0 -12
- package/src/theme/spacing.ts +0 -12
- package/src/theme/typography.ts +0 -40
- package/src/utils/assertNever.ts +0 -14
- package/src/utils/index.ts +0 -2
- package/src/utils/shouldNotForwardPropsWithKeys.ts +0 -7
- package/tsconfig.json +0 -34
- package/tsconfig.layout.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -10
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { css } from '@emotion/react';
|
|
2
|
-
import styled from '@emotion/styled';
|
|
3
|
-
import MuiDrawer, { drawerClasses } from '@mui/material/Drawer';
|
|
4
|
-
import MuiIconButton from '@mui/material/IconButton';
|
|
5
|
-
import MenuList from '@mui/material/MenuList';
|
|
6
|
-
|
|
7
|
-
import { shouldNotForwardPropsWithKeys } from '../../utils/shouldNotForwardPropsWithKeys';
|
|
8
|
-
|
|
9
|
-
const drawerOpenWidthRem = 15.625;
|
|
10
|
-
const drawerClosedWidthRem = 3.75;
|
|
11
|
-
|
|
12
|
-
export const Drawer = styled(MuiDrawer)`
|
|
13
|
-
flex-shrink: 0;
|
|
14
|
-
|
|
15
|
-
overflow-x: hidden;
|
|
16
|
-
.${drawerClasses.paper} {
|
|
17
|
-
width: ${({ open }) => (open ? drawerOpenWidthRem : drawerClosedWidthRem)}rem;
|
|
18
|
-
|
|
19
|
-
padding: ${({ theme }) => theme.my.spacing.xs};
|
|
20
|
-
|
|
21
|
-
overflow: hidden;
|
|
22
|
-
|
|
23
|
-
background-color: ${({ theme }) => theme.my.colors.primary.main};
|
|
24
|
-
border: unset;
|
|
25
|
-
|
|
26
|
-
transition: width 0.3s;
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
29
|
-
|
|
30
|
-
export const Header = styled.div`
|
|
31
|
-
display: flex;
|
|
32
|
-
|
|
33
|
-
column-gap: ${({ theme }) => theme.my.spacing.xxs};
|
|
34
|
-
align-items: center;
|
|
35
|
-
|
|
36
|
-
width: max-content;
|
|
37
|
-
min-width: 100%;
|
|
38
|
-
|
|
39
|
-
padding-bottom: ${({ theme }) => theme.my.spacing.xs};
|
|
40
|
-
|
|
41
|
-
overflow: hidden;
|
|
42
|
-
`;
|
|
43
|
-
|
|
44
|
-
export const LogoContainer = styled.div`
|
|
45
|
-
display: flex;
|
|
46
|
-
|
|
47
|
-
align-items: center;
|
|
48
|
-
justify-content: center;
|
|
49
|
-
|
|
50
|
-
width: 2.25rem;
|
|
51
|
-
height: 2.25rem;
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
type LockMenuButtonProps = {
|
|
55
|
-
isExpanded: boolean;
|
|
56
|
-
};
|
|
57
|
-
export const LockMenuButton = styled(
|
|
58
|
-
MuiIconButton,
|
|
59
|
-
shouldNotForwardPropsWithKeys<LockMenuButtonProps>(['isExpanded']),
|
|
60
|
-
)<LockMenuButtonProps>(
|
|
61
|
-
({ theme, isExpanded }) => css`
|
|
62
|
-
position: absolute;
|
|
63
|
-
top: ${theme.my.spacing.sm};
|
|
64
|
-
left: ${isExpanded ? drawerOpenWidthRem : drawerClosedWidthRem}rem;
|
|
65
|
-
z-index: ${(theme.zIndex?.drawer ?? 0) + 1};
|
|
66
|
-
|
|
67
|
-
padding: ${theme.my.spacing.xxxs};
|
|
68
|
-
|
|
69
|
-
font-size: 1rem;
|
|
70
|
-
|
|
71
|
-
color: ${theme.my.colors.text.white};
|
|
72
|
-
|
|
73
|
-
background-color: ${theme.my.colors.primitives.darkNavy[700]};
|
|
74
|
-
border-radius: ${theme.my.radius.xxl};
|
|
75
|
-
|
|
76
|
-
transform: translateX(-50%) rotate(${isExpanded ? 0 : 180}deg);
|
|
77
|
-
|
|
78
|
-
transition: 0.3s;
|
|
79
|
-
|
|
80
|
-
&:hover {
|
|
81
|
-
background-color: ${theme.my.colors.primitives.darkNavy[600]};
|
|
82
|
-
}
|
|
83
|
-
`,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
export const TopMenuList = styled(MenuList)`
|
|
87
|
-
display: flex;
|
|
88
|
-
|
|
89
|
-
flex: 1;
|
|
90
|
-
flex-direction: column;
|
|
91
|
-
|
|
92
|
-
gap: ${({ theme }) => theme.my.spacing.xxxs};
|
|
93
|
-
|
|
94
|
-
overflow: hidden auto;
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
type MainProps = {
|
|
98
|
-
isLocked: boolean;
|
|
99
|
-
};
|
|
100
|
-
export const Main = styled('div', shouldNotForwardPropsWithKeys<MainProps>(['isLocked']))<MainProps>`
|
|
101
|
-
position: relative;
|
|
102
|
-
|
|
103
|
-
flex-grow: 1;
|
|
104
|
-
|
|
105
|
-
margin-left: ${({ isLocked }) => (isLocked ? drawerOpenWidthRem : drawerClosedWidthRem)}rem;
|
|
106
|
-
|
|
107
|
-
overflow: auto;
|
|
108
|
-
|
|
109
|
-
transition: margin 0.3s;
|
|
110
|
-
`;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
import { useProfile, useToggle } from '@smartbooks-ai/layout';
|
|
4
|
-
|
|
5
|
-
export const useMenuToggle = () => {
|
|
6
|
-
const { value: isExpanded, toggle: toggleIsExpanded, switchOn: openMenu, switchOff: closeMenu } = useToggle(true);
|
|
7
|
-
|
|
8
|
-
const { profile } = useProfile();
|
|
9
|
-
|
|
10
|
-
const tenants = profile?.allowedTenants;
|
|
11
|
-
|
|
12
|
-
const tenantsWithCompanies = useMemo(() => tenants?.filter(({ companies }) => companies.length > 0) ?? [], [tenants]);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (tenants && !tenantsWithCompanies.length) closeMenu();
|
|
16
|
-
}, [closeMenu, tenants, tenantsWithCompanies.length]);
|
|
17
|
-
|
|
18
|
-
return { isExpanded, toggleIsExpanded, openMenu };
|
|
19
|
-
};
|
package/src/components/index.ts
DELETED
package/src/emotion.d.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import '@emotion/react';
|
|
2
|
-
import { ThemeOptions as MuiThemeOptions } from '@mui/material/styles';
|
|
3
|
-
|
|
4
|
-
type PaletteShade = 'main' | 'light' | 'dark' | 'contrast';
|
|
5
|
-
|
|
6
|
-
type CommonColorShade = '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | '950';
|
|
7
|
-
type SignalColorShade = '100' | '300' | '600' | '900' | '950';
|
|
8
|
-
|
|
9
|
-
type ColorPrimitives = {
|
|
10
|
-
darkNavy: Record<CommonColorShade, string>;
|
|
11
|
-
coolMint: Record<CommonColorShade, string>;
|
|
12
|
-
coral: Record<CommonColorShade, string>;
|
|
13
|
-
navy: Record<CommonColorShade, string>;
|
|
14
|
-
skyBlue: Record<CommonColorShade, string>;
|
|
15
|
-
grey: Record<CommonColorShade, string>;
|
|
16
|
-
common: Record<'black' | 'white', string>;
|
|
17
|
-
warning: Record<SignalColorShade, string>;
|
|
18
|
-
error: Record<SignalColorShade, string>;
|
|
19
|
-
success: Record<SignalColorShade, string>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type ColorPalette = {
|
|
23
|
-
primitives: ColorPrimitives;
|
|
24
|
-
text: Record<'main' | 'light' | 'lighter' | 'disabled' | 'placeholder' | 'white', string>;
|
|
25
|
-
primary: Record<PaletteShade, string>;
|
|
26
|
-
secondary: Record<PaletteShade, string>;
|
|
27
|
-
tertiary: Record<PaletteShade, string>;
|
|
28
|
-
quaternary: Record<PaletteShade, string>;
|
|
29
|
-
quinary: Record<PaletteShade, string>;
|
|
30
|
-
neutral: Record<PaletteShade, string>;
|
|
31
|
-
warning: Record<PaletteShade, string>;
|
|
32
|
-
error: Record<PaletteShade, string>;
|
|
33
|
-
success: Record<PaletteShade, string>;
|
|
34
|
-
background: Record<
|
|
35
|
-
'white' | 'lightGrey' | 'transparent' | 'light' | 'hover' | 'hoverDark' | 'hoverDarkNonTransparent',
|
|
36
|
-
string
|
|
37
|
-
>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type FontStyleName =
|
|
41
|
-
| 'h1'
|
|
42
|
-
| 'h2'
|
|
43
|
-
| 'h3'
|
|
44
|
-
| 'h4'
|
|
45
|
-
| 'h5'
|
|
46
|
-
| 'h6'
|
|
47
|
-
| 'subtitle1'
|
|
48
|
-
| 'subtitle2'
|
|
49
|
-
| 'body1'
|
|
50
|
-
| 'body2'
|
|
51
|
-
| 'body3'
|
|
52
|
-
| 'highlight1'
|
|
53
|
-
| 'highlight2'
|
|
54
|
-
| 'highlight3'
|
|
55
|
-
| 'button1'
|
|
56
|
-
| 'button2'
|
|
57
|
-
| 'caption'
|
|
58
|
-
| 'overline'
|
|
59
|
-
| 'label'
|
|
60
|
-
| 'nav1'
|
|
61
|
-
| 'nav2';
|
|
62
|
-
|
|
63
|
-
type ThemeOptions = {
|
|
64
|
-
my: {
|
|
65
|
-
colors: ColorPalette;
|
|
66
|
-
spacing: Record<'xxxs' | 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', string>;
|
|
67
|
-
radius: Record<'sq' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl', string>;
|
|
68
|
-
font: Record<FontStyleName, string>;
|
|
69
|
-
};
|
|
70
|
-
} & MuiThemeOptions;
|
|
71
|
-
|
|
72
|
-
declare module '@emotion/react' {
|
|
73
|
-
// This declaration is necessary to merge the custom theme with the default theme
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/consistent-type-definitions
|
|
75
|
-
export interface Theme extends ThemeOptions {}
|
|
76
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
import { CompanyRole, GlobalRole, ResourceType, TenantRole } from '@smartbooks-ai/api-client';
|
|
4
|
-
|
|
5
|
-
import { useProfile } from '../security/ProfileContext';
|
|
6
|
-
|
|
7
|
-
type AuthorizationParams = {
|
|
8
|
-
companyRole?: CompanyRole;
|
|
9
|
-
tenantCode?: string;
|
|
10
|
-
companyCode?: string;
|
|
11
|
-
tenantRole?: TenantRole;
|
|
12
|
-
globalRole?: GlobalRole;
|
|
13
|
-
resourceType?: ResourceType;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type Output = (params: AuthorizationParams) => boolean | null;
|
|
17
|
-
|
|
18
|
-
export const useIsAuthorized = (): Output => {
|
|
19
|
-
const { profile } = useProfile();
|
|
20
|
-
|
|
21
|
-
return useCallback(
|
|
22
|
-
({ companyCode, companyRole, globalRole, tenantCode, tenantRole, resourceType }) =>
|
|
23
|
-
profile &&
|
|
24
|
-
(globalRole === undefined || profile.globalRoles.includes(globalRole)) &&
|
|
25
|
-
(tenantRole === undefined ||
|
|
26
|
-
!!profile.allowedTenants.find(({ code }) => code === tenantCode)?.roles.includes(tenantRole)) &&
|
|
27
|
-
(companyRole === undefined ||
|
|
28
|
-
!!profile.allowedCompanies.find(({ code }) => code === companyCode)?.roles.includes(companyRole)) &&
|
|
29
|
-
(resourceType === undefined ||
|
|
30
|
-
!profile.allowedCompanies
|
|
31
|
-
.find(({ code }) => code === companyCode)
|
|
32
|
-
?.resources?.find(({ resource }) => resource === resourceType)?.isRestricted),
|
|
33
|
-
[profile],
|
|
34
|
-
);
|
|
35
|
-
};
|
package/src/hooks/useToggle.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { useCallback, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
type Output = {
|
|
4
|
-
value: boolean;
|
|
5
|
-
switchOn: () => void;
|
|
6
|
-
switchOff: () => void;
|
|
7
|
-
toggle: () => void;
|
|
8
|
-
set: (value: boolean) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const useToggle = (defaultValue = false): Output => {
|
|
12
|
-
const [value, setValue] = useState(defaultValue);
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
value,
|
|
16
|
-
switchOn: useCallback(() => {
|
|
17
|
-
setValue(true);
|
|
18
|
-
}, []),
|
|
19
|
-
switchOff: useCallback(() => {
|
|
20
|
-
setValue(false);
|
|
21
|
-
}, []),
|
|
22
|
-
toggle: useCallback(() => {
|
|
23
|
-
setValue((previous) => !previous);
|
|
24
|
-
}, []),
|
|
25
|
-
set: setValue,
|
|
26
|
-
};
|
|
27
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { glob } from 'glob';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
|
|
6
|
-
describe('package isolation', () => {
|
|
7
|
-
it('should not reference files outside of src folder', async () => {
|
|
8
|
-
expect.assertions(1);
|
|
9
|
-
|
|
10
|
-
// Find all TypeScript files in src directory
|
|
11
|
-
const srcFiles = await glob('src/**/*.{ts,tsx}', {
|
|
12
|
-
cwd: path.resolve(__dirname, '..'),
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const violations: string[] = [];
|
|
16
|
-
|
|
17
|
-
for (const file of srcFiles) {
|
|
18
|
-
const filePath = path.resolve(__dirname, '..', file);
|
|
19
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
20
|
-
|
|
21
|
-
// Extract import statements using regex
|
|
22
|
-
const importRegex = /^import\s+.*?from\s+['"]([^'"]+)['"]/gm;
|
|
23
|
-
let match;
|
|
24
|
-
|
|
25
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
26
|
-
const importPath = match[1];
|
|
27
|
-
|
|
28
|
-
// Check for relative imports that go outside the src folder
|
|
29
|
-
if (importPath.startsWith('../')) {
|
|
30
|
-
const resolvedPath = path.resolve(path.dirname(filePath), importPath);
|
|
31
|
-
const srcRoot = path.resolve(__dirname, '..', 'src');
|
|
32
|
-
|
|
33
|
-
// Only flag as violation if the resolved path is outside the src folder
|
|
34
|
-
if (!resolvedPath.startsWith(srcRoot + path.sep) && resolvedPath !== srcRoot) {
|
|
35
|
-
violations.push(`${file}: ${importPath}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Also check for dynamic imports that go outside the src folder
|
|
41
|
-
const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
42
|
-
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
43
|
-
const importPath = match[1];
|
|
44
|
-
|
|
45
|
-
if (importPath.startsWith('../')) {
|
|
46
|
-
const resolvedPath = path.resolve(path.dirname(filePath), importPath);
|
|
47
|
-
const srcRoot = path.resolve(__dirname, '..', 'src');
|
|
48
|
-
|
|
49
|
-
if (!resolvedPath.startsWith(srcRoot + path.sep) && resolvedPath !== srcRoot) {
|
|
50
|
-
violations.push(`${file}: ${importPath} (dynamic import)`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
console.log(violations);
|
|
57
|
-
|
|
58
|
-
expect(violations).toHaveLength(0);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { JSX, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
import { CompanyRole, GlobalRole, TenantRole } from '@smartbooks-ai/api-client';
|
|
4
|
-
|
|
5
|
-
import { State } from './state';
|
|
6
|
-
import { useAuthorizationState } from './useAuthorizationState';
|
|
7
|
-
|
|
8
|
-
import { ProfileState, useProfile } from '../../security/ProfileContext';
|
|
9
|
-
import { assertNever } from '../../utils/assertNever';
|
|
10
|
-
|
|
11
|
-
type Props = {
|
|
12
|
-
requireCompanyRole?: CompanyRole;
|
|
13
|
-
requireTenantRole?: TenantRole;
|
|
14
|
-
requireGlobalRole?: GlobalRole;
|
|
15
|
-
companyCode?: string;
|
|
16
|
-
tenantCode?: string;
|
|
17
|
-
children?: React.ReactNode;
|
|
18
|
-
errorElement?: JSX.Element;
|
|
19
|
-
loadingElement?: JSX.Element;
|
|
20
|
-
notLoggedInElement?: JSX.Element;
|
|
21
|
-
unauthorizedElement?: React.ReactNode;
|
|
22
|
-
shouldInitiateLogin?: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const AuthorizedContent: React.FC<Props> = ({
|
|
26
|
-
children,
|
|
27
|
-
companyCode,
|
|
28
|
-
errorElement,
|
|
29
|
-
shouldInitiateLogin,
|
|
30
|
-
loadingElement,
|
|
31
|
-
notLoggedInElement,
|
|
32
|
-
requireCompanyRole,
|
|
33
|
-
requireGlobalRole,
|
|
34
|
-
requireTenantRole,
|
|
35
|
-
tenantCode,
|
|
36
|
-
unauthorizedElement,
|
|
37
|
-
}) => {
|
|
38
|
-
const { state, initiateLogin } = useProfile();
|
|
39
|
-
|
|
40
|
-
const authorizationState = useAuthorizationState({
|
|
41
|
-
companyCode,
|
|
42
|
-
companyRole: requireCompanyRole,
|
|
43
|
-
globalRole: requireGlobalRole,
|
|
44
|
-
tenantCode,
|
|
45
|
-
tenantRole: requireTenantRole,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (state === ProfileState.notLoggedIn && shouldInitiateLogin) {
|
|
50
|
-
initiateLogin();
|
|
51
|
-
}
|
|
52
|
-
}, [initiateLogin, shouldInitiateLogin, state]);
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (authorizationState === State.needsProfileLinking) {
|
|
56
|
-
window.location.href = '/link-identities'; // Cannot use navigate outside of a RouterContext
|
|
57
|
-
}
|
|
58
|
-
}, [authorizationState]);
|
|
59
|
-
|
|
60
|
-
switch (authorizationState) {
|
|
61
|
-
case State.loading:
|
|
62
|
-
case State.needsProfileLinking:
|
|
63
|
-
return loadingElement;
|
|
64
|
-
case State.authorized:
|
|
65
|
-
return children;
|
|
66
|
-
case State.notLoggedIn:
|
|
67
|
-
return notLoggedInElement;
|
|
68
|
-
case State.unauthorized:
|
|
69
|
-
return unauthorizedElement;
|
|
70
|
-
case State.error:
|
|
71
|
-
return errorElement;
|
|
72
|
-
default:
|
|
73
|
-
return assertNever(authorizationState);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export { AuthorizedContent };
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { CompanyRole, GlobalRole, TenantRole } from '@smartbooks-ai/api-client';
|
|
2
|
-
|
|
3
|
-
import { State } from './state';
|
|
4
|
-
|
|
5
|
-
import { useIsAuthorized } from '../../hooks/useIsAuthorized';
|
|
6
|
-
import { ProfileState, useProfile } from '../../security/ProfileContext';
|
|
7
|
-
|
|
8
|
-
type Input = {
|
|
9
|
-
companyRole?: CompanyRole;
|
|
10
|
-
tenantCode?: string;
|
|
11
|
-
companyCode?: string;
|
|
12
|
-
tenantRole?: TenantRole;
|
|
13
|
-
globalRole?: GlobalRole;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const useAuthorizationState = ({
|
|
17
|
-
companyCode,
|
|
18
|
-
companyRole,
|
|
19
|
-
globalRole,
|
|
20
|
-
tenantCode,
|
|
21
|
-
tenantRole,
|
|
22
|
-
}: Input): State => {
|
|
23
|
-
const { profile, state } = useProfile();
|
|
24
|
-
|
|
25
|
-
const getIsAuthorized = useIsAuthorized();
|
|
26
|
-
|
|
27
|
-
if (profile) {
|
|
28
|
-
return getIsAuthorized({ companyCode, companyRole, globalRole, tenantCode, tenantRole })
|
|
29
|
-
? State.authorized
|
|
30
|
-
: State.unauthorized;
|
|
31
|
-
}
|
|
32
|
-
switch (state) {
|
|
33
|
-
case ProfileState.notLoggedIn:
|
|
34
|
-
return State.notLoggedIn;
|
|
35
|
-
case ProfileState.loading:
|
|
36
|
-
return State.loading;
|
|
37
|
-
case ProfileState.needsLinking:
|
|
38
|
-
return State.needsProfileLinking;
|
|
39
|
-
default:
|
|
40
|
-
return State.error;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createContext } from 'react';
|
|
2
|
-
|
|
3
|
-
import { ProfileState } from './types';
|
|
4
|
-
|
|
5
|
-
import { UserProfile } from '../UserProfile';
|
|
6
|
-
|
|
7
|
-
export type InitiateLoginArgs = {
|
|
8
|
-
forceSignUp?: boolean;
|
|
9
|
-
forceSignIn?: boolean;
|
|
10
|
-
returnTo?: string;
|
|
11
|
-
loginHint?: string;
|
|
12
|
-
connection?: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type PrimaryProfileInformation = {
|
|
16
|
-
connection: string;
|
|
17
|
-
canUseLoginHint: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type ProfileContextValue = {
|
|
21
|
-
profile: UserProfile | null;
|
|
22
|
-
state: ProfileState;
|
|
23
|
-
primaryProfileInformation?: PrimaryProfileInformation;
|
|
24
|
-
initiateLogin: (args?: InitiateLoginArgs) => void;
|
|
25
|
-
reload: () => void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const unsetFn = () => {
|
|
29
|
-
throw new Error('Unable interact before initializing the profile provider');
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const ProfileContext = createContext<ProfileContextValue>({
|
|
33
|
-
profile: null,
|
|
34
|
-
state: ProfileState.loading,
|
|
35
|
-
initiateLogin: unsetFn,
|
|
36
|
-
reload: unsetFn,
|
|
37
|
-
});
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { AvailableCompany, AvailableTenant, GlobalRole, Profile } from '@smartbooks-ai/api-client';
|
|
2
|
-
|
|
3
|
-
export class UserProfile {
|
|
4
|
-
displayName: string;
|
|
5
|
-
email: string;
|
|
6
|
-
allowedTenants: AvailableTenant[];
|
|
7
|
-
allowedCompanies: AvailableCompany[];
|
|
8
|
-
globalRoles: GlobalRole[];
|
|
9
|
-
userId: string;
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
userId: string,
|
|
13
|
-
displayName: string,
|
|
14
|
-
email: string,
|
|
15
|
-
allowedTenants: AvailableTenant[],
|
|
16
|
-
globalRoles: GlobalRole[],
|
|
17
|
-
) {
|
|
18
|
-
this.userId = userId;
|
|
19
|
-
this.displayName = displayName;
|
|
20
|
-
this.email = email;
|
|
21
|
-
this.allowedTenants = allowedTenants;
|
|
22
|
-
this.globalRoles = globalRoles;
|
|
23
|
-
this.allowedCompanies = allowedTenants
|
|
24
|
-
.flatMap((t) => t.companies)
|
|
25
|
-
.sort((a, b) => a.description.localeCompare(b.description));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public static create(
|
|
29
|
-
given_name: string | undefined,
|
|
30
|
-
family_name: string | undefined,
|
|
31
|
-
email_address: string | undefined,
|
|
32
|
-
profileResponse: Profile,
|
|
33
|
-
) {
|
|
34
|
-
const firstName = given_name;
|
|
35
|
-
const lastName = family_name;
|
|
36
|
-
const email = email_address || 'Email Unknown';
|
|
37
|
-
|
|
38
|
-
const displayName = firstName || lastName ? `${firstName} ${lastName}`.trim() : email;
|
|
39
|
-
|
|
40
|
-
return new UserProfile(
|
|
41
|
-
profileResponse.userId,
|
|
42
|
-
displayName,
|
|
43
|
-
email,
|
|
44
|
-
profileResponse.tenants,
|
|
45
|
-
profileResponse.globalRoles,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/security/index.ts
DELETED