@campxdev/shared 0.4.1 → 0.5.1
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/package.json +1 -1
- package/src/components/ListItemButton.tsx +84 -91
- package/src/components/LoginForm.tsx +3 -1
- package/src/components/SideNav.tsx +145 -85
- package/src/config/axios.ts +8 -8
- package/src/contexts/Providers.tsx +10 -2
- package/src/utils/withRouteWrapper.tsx +0 -1
- package/todo.md +8 -0
package/package.json
CHANGED
|
@@ -1,101 +1,94 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ChevronRight} from '@mui/icons-material'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
3
|
+
Box,
|
|
4
|
+
ListItemButton as MuiListItemButton,
|
|
5
|
+
ListItemButtonProps,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
ListItemText,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import {ReactNode} from 'react'
|
|
11
11
|
|
|
12
12
|
interface Props extends ListItemButtonProps {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
hasChildren?: boolean
|
|
14
|
+
isActive?: boolean
|
|
15
|
+
label: String
|
|
16
|
+
icon?: any
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const ListItemButton = ({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
isActive,
|
|
21
|
+
hasChildren,
|
|
22
|
+
onClick,
|
|
23
|
+
label,
|
|
24
|
+
icon: Icon,
|
|
25
25
|
}: Props) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{hasChildren ? (
|
|
38
|
-
<>
|
|
39
|
-
<StyledChevronIcon open={isActive} />
|
|
40
|
-
</>
|
|
41
|
-
) : null}
|
|
42
|
-
</MyListItemButton>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
26
|
+
return (
|
|
27
|
+
<StyledListItemButton isActive={isActive} onClick={onClick}>
|
|
28
|
+
{Icon ? (
|
|
29
|
+
<ListItemIcon>{<Icon />}</ListItemIcon>
|
|
30
|
+
) : (
|
|
31
|
+
<Box minWidth={16}></Box>
|
|
32
|
+
)}
|
|
33
|
+
<ListItemText primary={label} />
|
|
34
|
+
</StyledListItemButton>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
45
37
|
|
|
46
|
-
const StyledChevronIcon = styled(ChevronRight)<{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
38
|
+
export const StyledChevronIcon = styled(ChevronRight)<{open: boolean}>(
|
|
39
|
+
({theme, open}) => ({
|
|
40
|
+
transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
41
|
+
transition: 'transform 0.2s ease-in-out',
|
|
42
|
+
})
|
|
43
|
+
)
|
|
52
44
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
45
|
+
export const StyledListItemButton = styled(MuiListItemButton)<{
|
|
46
|
+
isActive: boolean
|
|
47
|
+
children: ReactNode
|
|
48
|
+
dropDown?: boolean
|
|
49
|
+
}>(({theme, isActive, dropDown = false}) => ({
|
|
50
|
+
gap: '10px',
|
|
51
|
+
fontWeight: 'bold',
|
|
52
|
+
backgroundColor: isActive && '#1d1d1d',
|
|
53
|
+
'&:hover': {
|
|
54
|
+
backgroundColor: isActive && '#1d1d1d',
|
|
55
|
+
},
|
|
56
|
+
'&:after': {
|
|
57
|
+
content: '""',
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
height: '100%',
|
|
60
|
+
left: 0,
|
|
61
|
+
top: 0,
|
|
62
|
+
width: isActive ? '5px' : 0,
|
|
63
|
+
opacity: isActive ? (dropDown ? 0 : 1) : 0,
|
|
64
|
+
transition: theme.transitions.create(['opacity', 'width']),
|
|
65
|
+
background: theme.palette.common.yellow,
|
|
66
|
+
borderTopRightRadius: '3px',
|
|
67
|
+
borderBottomRightRadius: '3px',
|
|
68
|
+
},
|
|
69
|
+
'&.MuiButton-endIcon, &.MuiButton-startIcon': {
|
|
70
|
+
transition: theme.transitions.create(['color']),
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}))
|
|
72
|
+
'&.MuiSvgIcon-root': {
|
|
73
|
+
fontSize: 'inherit',
|
|
74
|
+
transition: 'none',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
'& .MuiSvgIcon-root': {
|
|
78
|
+
fontSize: theme.typography.pxToRem(20),
|
|
79
|
+
marginRight: theme.spacing(1),
|
|
80
|
+
// color: isActive
|
|
81
|
+
// ? theme.sidebar.menuItemColorActive
|
|
82
|
+
// : theme.sidebar.menuItemIconColor,
|
|
83
|
+
},
|
|
84
|
+
'& .MuiTypography-root': {
|
|
85
|
+
color: theme.palette.common.white,
|
|
86
|
+
},
|
|
87
|
+
'& svg': {
|
|
88
|
+
color: theme.palette.common.white,
|
|
89
|
+
},
|
|
90
|
+
'& .MuiListItemIcon-root': {
|
|
91
|
+
minWidth: 0,
|
|
92
|
+
maxWidth: '1rem',
|
|
93
|
+
},
|
|
94
|
+
}))
|
|
@@ -14,6 +14,8 @@ import {useState} from 'react'
|
|
|
14
14
|
import {useForm} from 'react-hook-form'
|
|
15
15
|
import {FormTextField} from './HookForm'
|
|
16
16
|
|
|
17
|
+
const developmentOrigin = window.location.origin + '/campx_dev'
|
|
18
|
+
|
|
17
19
|
export const StyledTextField = styled(FormTextField)(({theme}) => ({
|
|
18
20
|
'& .MuiInputBase-root': {
|
|
19
21
|
minHeight: '60px',
|
|
@@ -42,7 +44,7 @@ export function LoginForm() {
|
|
|
42
44
|
data: values,
|
|
43
45
|
})
|
|
44
46
|
Cookies.set('campx_session_key', res.data.cookie)
|
|
45
|
-
window.location.href =
|
|
47
|
+
window.location.href = developmentOrigin
|
|
46
48
|
} catch (err) {
|
|
47
49
|
// eslint-disable-next-line no-console
|
|
48
50
|
console.log(err)
|
|
@@ -1,99 +1,159 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {ChevronLeft} from '@mui/icons-material'
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Collapse,
|
|
5
|
+
List,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
ListItemText,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import {ReactNode, useState} from 'react'
|
|
11
|
+
import {Link, useMatch, useResolvedPath} from 'react-router-dom'
|
|
12
|
+
import {PermissionsStore} from '../permissions'
|
|
13
|
+
import {
|
|
14
|
+
ListItemButton,
|
|
15
|
+
StyledChevronIcon,
|
|
16
|
+
StyledListItemButton,
|
|
17
|
+
} from './ListItemButton'
|
|
18
|
+
const accessIfNoKey = process.env.NODE_ENV === 'development' ? false : false
|
|
7
19
|
const StyledLink = styled(Link)({
|
|
8
|
-
|
|
9
|
-
})
|
|
20
|
+
textDecoration: 'none',
|
|
21
|
+
})
|
|
10
22
|
|
|
11
23
|
export default function SideNav({
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
menuItems,
|
|
25
|
+
header,
|
|
14
26
|
}: {
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
menuItems: any[]
|
|
28
|
+
header?: ReactNode
|
|
17
29
|
}) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
sx={{
|
|
33
|
+
background: (theme) => theme.palette.secondary.main,
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{header ? header : null}
|
|
37
|
+
<Box>
|
|
38
|
+
{menuItems.map((item, index) => (
|
|
39
|
+
<RenderMenuItem key={index} menuItem={item} />
|
|
40
|
+
))}
|
|
41
|
+
</Box>
|
|
42
|
+
</Box>
|
|
43
|
+
)
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
const RenderMenuItem = ({
|
|
31
|
-
|
|
46
|
+
const RenderMenuItem = ({menuItem}) => {
|
|
47
|
+
const {path, title, children, icon, permissionKey} = menuItem
|
|
48
|
+
let resolved = useResolvedPath(path)
|
|
49
|
+
let match = useMatch({path: resolved.pathname, end: false})
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
const permissions = PermissionsStore.useState((s) => s).permissions
|
|
52
|
+
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
if (!hasAccess) return null
|
|
55
|
+
if (children?.length)
|
|
56
|
+
return (
|
|
57
|
+
<Box
|
|
58
|
+
sx={{
|
|
59
|
+
position: 'relative',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<DropDownMenu
|
|
63
|
+
icon={icon}
|
|
64
|
+
menuItems={children}
|
|
65
|
+
path={path}
|
|
66
|
+
title={title}
|
|
67
|
+
permissionKey={permissionKey}
|
|
68
|
+
/>
|
|
69
|
+
</Box>
|
|
70
|
+
)
|
|
38
71
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
return (
|
|
73
|
+
<StyledLink to={path}>
|
|
74
|
+
<ListItemButton
|
|
75
|
+
hasChildren={false}
|
|
76
|
+
label={title}
|
|
77
|
+
isActive={!!match}
|
|
78
|
+
icon={icon}
|
|
79
|
+
/>
|
|
80
|
+
</StyledLink>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const DropDownMenu = ({path, title, icon, menuItems, permissionKey}) => {
|
|
85
|
+
const [open, setOpen] = useState(false)
|
|
86
|
+
const permissions = PermissionsStore.useState((s) => s).permissions
|
|
87
|
+
|
|
88
|
+
const validateDropdownAccess = () => {
|
|
89
|
+
if (accessIfNoKey && !permissions) return true
|
|
90
|
+
let arr = []
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < menuItems.length; i++) {
|
|
93
|
+
if (permissions[menuItems[i].permissionKey]) {
|
|
94
|
+
arr.push(true)
|
|
95
|
+
} else {
|
|
96
|
+
arr.push(accessIfNoKey)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
42
99
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
arr.push(true);
|
|
46
|
-
} else {
|
|
47
|
-
arr.push(accessIfNoKey);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
100
|
+
return arr.includes(true)
|
|
101
|
+
}
|
|
50
102
|
|
|
51
|
-
|
|
52
|
-
};
|
|
53
|
-
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey;
|
|
54
|
-
const dropdownAccess = children?.length
|
|
55
|
-
? validateDropdownAccess()
|
|
56
|
-
: hasAccess;
|
|
103
|
+
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Box
|
|
62
|
-
sx={{
|
|
63
|
-
position: "relative",
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<ListItemButton
|
|
67
|
-
onClick={() => setOpen((prev) => !prev)}
|
|
68
|
-
label={title}
|
|
69
|
-
isActive={open}
|
|
70
|
-
hasChildren={true}
|
|
71
|
-
icon={icon}
|
|
72
|
-
/>
|
|
73
|
-
<Collapse in={open}>
|
|
74
|
-
<List>
|
|
75
|
-
{children?.map((child) => (
|
|
76
|
-
<RenderMenuItem
|
|
77
|
-
key={child.path}
|
|
78
|
-
menuItem={{
|
|
79
|
-
...child,
|
|
80
|
-
path: path + child.path,
|
|
81
|
-
}}
|
|
82
|
-
/>
|
|
83
|
-
))}
|
|
84
|
-
</List>
|
|
85
|
-
</Collapse>
|
|
86
|
-
</Box>
|
|
87
|
-
);
|
|
105
|
+
const dropdownAccess = menuItems?.length
|
|
106
|
+
? validateDropdownAccess()
|
|
107
|
+
: hasAccess
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
109
|
+
if (!dropdownAccess) return null
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Box
|
|
113
|
+
sx={{
|
|
114
|
+
position: 'relative',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<MenuDropDownButton
|
|
118
|
+
isActive={open}
|
|
119
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
120
|
+
label={title}
|
|
121
|
+
icon={icon}
|
|
122
|
+
/>
|
|
123
|
+
<Collapse in={open}>
|
|
124
|
+
<List>
|
|
125
|
+
{menuItems?.map((child) => (
|
|
126
|
+
<RenderMenuItem
|
|
127
|
+
key={child.path}
|
|
128
|
+
menuItem={{
|
|
129
|
+
...child,
|
|
130
|
+
path: path + child.path,
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</List>
|
|
135
|
+
</Collapse>
|
|
136
|
+
</Box>
|
|
137
|
+
</>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const MenuDropDownButton = ({icon: Icon, label, onClick, isActive}) => {
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<StyledListItemButton
|
|
145
|
+
dropDown={true}
|
|
146
|
+
onClick={onClick}
|
|
147
|
+
isActive={isActive}
|
|
148
|
+
>
|
|
149
|
+
{Icon ? (
|
|
150
|
+
<ListItemIcon>{<Icon />}</ListItemIcon>
|
|
151
|
+
) : (
|
|
152
|
+
<Box minWidth={16}></Box>
|
|
153
|
+
)}
|
|
154
|
+
<ListItemText primary={label} />
|
|
155
|
+
<StyledChevronIcon open={isActive} />
|
|
156
|
+
</StyledListItemButton>
|
|
157
|
+
</>
|
|
158
|
+
)
|
|
159
|
+
}
|
package/src/config/axios.ts
CHANGED
|
@@ -3,11 +3,10 @@ import _ from 'lodash'
|
|
|
3
3
|
import {toast} from 'react-toastify'
|
|
4
4
|
import Cookies from 'js-cookie'
|
|
5
5
|
import {NetworkStore} from '../components/ErrorBoundary/GlobalNetworkLoadingIndicator'
|
|
6
|
+
import {isDevelopment} from '../constants'
|
|
6
7
|
|
|
7
|
-
const isDevelopment =
|
|
8
|
-
process.env.NODE_ENV === 'development' ||
|
|
9
|
-
window.location.origin.split('.').slice(-2).join('.') === 'campx.dev'
|
|
10
8
|
const sessionKey = Cookies.get('campx_session_key')
|
|
9
|
+
const clientId = window.location.pathname.split('/')[0] ?? 'campx_dev'
|
|
11
10
|
|
|
12
11
|
const formatParams = (params) => {
|
|
13
12
|
return Object.fromEntries(
|
|
@@ -21,12 +20,13 @@ const formatParams = (params) => {
|
|
|
21
20
|
let axios = Axios.create({
|
|
22
21
|
baseURL: process.env.REACT_APP_API_HOST,
|
|
23
22
|
withCredentials: true,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
headers: {
|
|
24
|
+
'x-tenent-id': isDevelopment ? 'campx_dev' : clientId,
|
|
25
|
+
...(isDevelopment &&
|
|
26
|
+
sessionKey && {
|
|
27
27
|
campx_session_key: sessionKey,
|
|
28
|
-
},
|
|
29
|
-
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
axios.interceptors.request.use(
|
|
@@ -7,10 +7,18 @@ import QueryClientProvider from './QueryClientProvider'
|
|
|
7
7
|
import DialogProvider from '../components/DrawerWrapper/DrawerWrapper'
|
|
8
8
|
import {ToastContainer} from '../components'
|
|
9
9
|
import LoginFormProvider from './LoginFormProvider'
|
|
10
|
+
import {ReactNode} from 'react'
|
|
11
|
+
import {isDevelopment} from '../constants'
|
|
10
12
|
|
|
11
|
-
export default function Providers({
|
|
13
|
+
export default function Providers({
|
|
14
|
+
children,
|
|
15
|
+
basename,
|
|
16
|
+
}: {
|
|
17
|
+
children: ReactNode
|
|
18
|
+
basename: string
|
|
19
|
+
}) {
|
|
12
20
|
return (
|
|
13
|
-
<BrowserRouter>
|
|
21
|
+
<BrowserRouter basename={isDevelopment ? 'campx_dev' : basename}>
|
|
14
22
|
<QueryClientProvider>
|
|
15
23
|
<MuiThemeProvider>
|
|
16
24
|
<DialogProvider>
|