@campxdev/shared 1.8.48 → 1.8.49-alpha.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/ApplicationProfile/ApplicationProfile.tsx +11 -5
- package/src/components/ApplicationProfile/UserProfileRelation.tsx +7 -13
- package/src/components/ErrorBoundary/ErrorFallback.tsx +5 -6
- package/src/components/Institutions/InsititutionsDialog.tsx +80 -0
- package/src/components/Institutions/InstitutionsDropdown.tsx +32 -0
- package/src/components/Institutions/index.tsx +1 -0
- package/src/components/Institutions/services.ts +12 -0
- package/src/components/Layout/Header/AppHeader.tsx +2 -0
- package/src/components/Layout/Header/HeaderActions/HeaderActions.tsx +2 -0
- package/src/components/LoginForm.tsx +21 -26
- package/src/components/ModalButtons/DialogButton.tsx +28 -24
- package/src/config/axios.ts +9 -2
- package/src/contexts/LoginFormProvider.tsx +3 -5
- package/src/contexts/Providers.tsx +27 -18
- package/src/contexts/RootModal.tsx +73 -0
- package/src/hooks/useAuth.ts +50 -25
- package/src/hooks/useInstitution.ts +94 -0
- package/src/shared-state/InstitutionsStore.ts +8 -0
- package/src/shared-state/PermissionsStore.ts +1 -1
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
Typography,
|
|
8
8
|
styled,
|
|
9
9
|
} from '@mui/material'
|
|
10
|
-
import { useState } from 'react'
|
|
10
|
+
import { useEffect, useState } from 'react'
|
|
11
11
|
import { useMutation, useQuery } from 'react-query'
|
|
12
12
|
import { toast } from 'react-toastify'
|
|
13
13
|
import { useImmer } from 'use-immer'
|
|
@@ -80,14 +80,14 @@ function ApplicationProfile({
|
|
|
80
80
|
const columns = [
|
|
81
81
|
{
|
|
82
82
|
title: 'User',
|
|
83
|
-
key: '',
|
|
84
|
-
dataIndex: '',
|
|
83
|
+
key: 'user',
|
|
84
|
+
dataIndex: 'user',
|
|
85
85
|
render: (_, row) => <UserComponent userData={row} />,
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
title: 'Profile',
|
|
89
|
-
key: '',
|
|
90
|
-
dataIndex: '',
|
|
89
|
+
key: 'profile',
|
|
90
|
+
dataIndex: 'profile',
|
|
91
91
|
render: (_, row) => (
|
|
92
92
|
<RenderProfileDropDown
|
|
93
93
|
profiles={profiles?.profiles}
|
|
@@ -218,6 +218,12 @@ export const RenderProfileDropDown = ({
|
|
|
218
218
|
profileId: data.profiles[0].id,
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
setState((pre) => ({
|
|
223
|
+
userId: data.id,
|
|
224
|
+
profileId: data.profiles[0].id,
|
|
225
|
+
}))
|
|
226
|
+
}, [data])
|
|
221
227
|
const { mutate, isLoading } = useMutation(updateUserApplicationProfile, {
|
|
222
228
|
onSuccess: (res) => {
|
|
223
229
|
refetchFn()
|
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
import { yupResolver } from '@hookform/resolvers/yup'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
CircularProgress,
|
|
5
|
-
Popper,
|
|
6
|
-
Stack,
|
|
7
|
-
styled,
|
|
8
|
-
} from '@mui/material'
|
|
9
|
-
import { useState } from 'react'
|
|
10
|
-
import { Controller, useForm } from 'react-hook-form'
|
|
2
|
+
import { Popper, Stack, styled } from '@mui/material'
|
|
3
|
+
import { useForm } from 'react-hook-form'
|
|
11
4
|
import { useMutation, useQueryClient } from 'react-query'
|
|
12
5
|
import { toast } from 'react-toastify'
|
|
6
|
+
import { useImmer } from 'use-immer'
|
|
7
|
+
import { axiosErrorToast } from '../../config/axios'
|
|
8
|
+
import ActionButton from '../ActionButton'
|
|
9
|
+
import { FormMultiSelect, FormSingleSelect } from '../HookForm'
|
|
13
10
|
import {
|
|
14
11
|
createApplicationUserProfile,
|
|
15
12
|
fetchUsers,
|
|
16
13
|
userProfileSchema,
|
|
17
14
|
} from './Service'
|
|
18
|
-
import { useImmer } from 'use-immer'
|
|
19
|
-
import { axiosErrorToast } from '../../config/axios'
|
|
20
|
-
import { FormMultiSelect, FormSingleSelect } from '../HookForm'
|
|
21
|
-
import ActionButton from '../ActionButton'
|
|
22
15
|
interface UserProps {
|
|
23
16
|
options: {
|
|
24
17
|
label: any
|
|
@@ -80,6 +73,7 @@ function UserProfileRelation({ close, application, profiles }) {
|
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
const handleInputChange = (e) => {
|
|
76
|
+
//TODO : debounce
|
|
83
77
|
if (e) {
|
|
84
78
|
setState((s) => {
|
|
85
79
|
s.inputValue = e.target.value
|
|
@@ -12,6 +12,7 @@ import LoginForm from '../LoginForm'
|
|
|
12
12
|
import { useModal } from '../DrawerWrapper/DrawerWrapper'
|
|
13
13
|
import { PermissionsStore } from '../../shared-state'
|
|
14
14
|
import axios from '../../config/axios'
|
|
15
|
+
import { openRootModal } from '../../contexts/RootModal'
|
|
15
16
|
|
|
16
17
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
17
18
|
height: '60px',
|
|
@@ -187,18 +188,16 @@ export function UnAuth({ resetBoundary }) {
|
|
|
187
188
|
setLoading(false)
|
|
188
189
|
})
|
|
189
190
|
.catch((e) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
content: () => <LoginForm />,
|
|
191
|
+
openRootModal({
|
|
192
|
+
key: 'login',
|
|
193
193
|
})
|
|
194
194
|
})
|
|
195
195
|
}
|
|
196
196
|
function LoginPage() {
|
|
197
197
|
if (isLocalHost) {
|
|
198
198
|
if (!sessionCookie) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
content: () => <LoginForm />,
|
|
199
|
+
openRootModal({
|
|
200
|
+
key: 'login',
|
|
202
201
|
})
|
|
203
202
|
} else {
|
|
204
203
|
appinit()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Box, Typography } from '@mui/material'
|
|
2
|
+
import Image from '../Image/Image'
|
|
3
|
+
import axios from '../../config/axios'
|
|
4
|
+
import { useQuery } from 'react-query'
|
|
5
|
+
import Spinner from '../Spinner'
|
|
6
|
+
import { institutions } from './services'
|
|
7
|
+
import { InsititutionsStore } from '../../shared-state/InstitutionsStore'
|
|
8
|
+
|
|
9
|
+
export default function InsititutionsDialog({ close }) {
|
|
10
|
+
// const { data, isLoading } = useQuery(
|
|
11
|
+
// 'institutions',
|
|
12
|
+
// institutions.fetchInsititutions,
|
|
13
|
+
// )
|
|
14
|
+
|
|
15
|
+
// if (isLoading) return <Spinner />
|
|
16
|
+
const { institutions } = InsititutionsStore.useState((s) => s)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Box
|
|
20
|
+
sx={{
|
|
21
|
+
padding: '20px',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<Typography variant="h3" textAlign={'center'}>
|
|
25
|
+
Select an Instituition
|
|
26
|
+
</Typography>
|
|
27
|
+
<Box>
|
|
28
|
+
<Box
|
|
29
|
+
sx={{
|
|
30
|
+
marginTop: '30px',
|
|
31
|
+
display: 'flex',
|
|
32
|
+
gap: '30px',
|
|
33
|
+
justifyContent: 'center',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{institutions?.map((item, index) => (
|
|
38
|
+
<InstitutionCard institution={item} key={index} />
|
|
39
|
+
))}
|
|
40
|
+
</Box>
|
|
41
|
+
</Box>
|
|
42
|
+
</Box>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const InstitutionCard = ({ institution }) => {
|
|
47
|
+
const urlTenantKey = window.location.pathname.split('/')[1]
|
|
48
|
+
const handleClick = () => {
|
|
49
|
+
localStorage.setItem('institution_key', institution?.code)
|
|
50
|
+
window.location.replace(
|
|
51
|
+
`${window.location.origin}/${urlTenantKey}/${institution?.code}`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Box
|
|
57
|
+
sx={{
|
|
58
|
+
display: 'flex',
|
|
59
|
+
flexDirection: 'column',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
gap: '20px',
|
|
62
|
+
padding: '14px',
|
|
63
|
+
border: '1px solid black',
|
|
64
|
+
borderRadius: '10px',
|
|
65
|
+
cursor: 'pointer',
|
|
66
|
+
width: '200px',
|
|
67
|
+
flexShrink: 0,
|
|
68
|
+
}}
|
|
69
|
+
onClick={handleClick}
|
|
70
|
+
>
|
|
71
|
+
<Image
|
|
72
|
+
alt="logo"
|
|
73
|
+
height={100}
|
|
74
|
+
width="auto"
|
|
75
|
+
src={institution?.images?.url}
|
|
76
|
+
/>
|
|
77
|
+
<Typography variant="body2">{institution?.name}</Typography>
|
|
78
|
+
</Box>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { FormSingleSelect } from '../HookForm'
|
|
3
|
+
import { SingleSelect } from '../Input'
|
|
4
|
+
import { InsititutionsStore } from '../../shared-state/InstitutionsStore'
|
|
5
|
+
import { urlTenantKey } from '../../contexts/Providers'
|
|
6
|
+
|
|
7
|
+
export default function SchoolSwitch() {
|
|
8
|
+
const { current, institutions } = InsititutionsStore.useState((s) => s)
|
|
9
|
+
|
|
10
|
+
const handleChange = (e) => {
|
|
11
|
+
window.location.replace(
|
|
12
|
+
`${window.location.origin}/${urlTenantKey}/${e.target.value}`,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<SingleSelect
|
|
18
|
+
containerProps={{
|
|
19
|
+
sx: {
|
|
20
|
+
minWidth: '200px',
|
|
21
|
+
},
|
|
22
|
+
}}
|
|
23
|
+
size="small"
|
|
24
|
+
options={institutions?.map((item) => ({
|
|
25
|
+
label: item.name,
|
|
26
|
+
value: item.code,
|
|
27
|
+
}))}
|
|
28
|
+
value={current?.code}
|
|
29
|
+
onChange={handleChange}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './InsititutionsDialog'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import axios from '../../config/axios'
|
|
2
|
+
|
|
3
|
+
export const institutions = {
|
|
4
|
+
async fetchInsititutions() {
|
|
5
|
+
const res = await axios.get('/square/institutions')
|
|
6
|
+
return res.data
|
|
7
|
+
},
|
|
8
|
+
async fetchInsititutionByCode(code: string) {
|
|
9
|
+
const res = await axios.get(`/square/institutions/code/${code}`)
|
|
10
|
+
return res.data
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -46,6 +46,7 @@ interface AppHeaderProps {
|
|
|
46
46
|
}[]
|
|
47
47
|
customHeaderActions?: ReactNode
|
|
48
48
|
cogWheelMenu?: { label: string; path: string }[]
|
|
49
|
+
institutions: any[]
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export default function AppHeader({
|
|
@@ -54,6 +55,7 @@ export default function AppHeader({
|
|
|
54
55
|
userBoxActions = [],
|
|
55
56
|
cogWheelMenu = [],
|
|
56
57
|
customHeaderActions,
|
|
58
|
+
institutions,
|
|
57
59
|
}: AppHeaderProps) {
|
|
58
60
|
return (
|
|
59
61
|
<StyledHeader>
|
|
@@ -2,6 +2,7 @@ import { Box } from '@mui/material'
|
|
|
2
2
|
import UserBox from './UserBox'
|
|
3
3
|
import CogWheelMenu from './CogWheelMenu'
|
|
4
4
|
import FreshDeskHelpButton from './FreshDeskHelpButton'
|
|
5
|
+
import InstitutionsDropDown from '../../../Institutions/InstitutionsDropdown'
|
|
5
6
|
|
|
6
7
|
export default function HeaderActions({
|
|
7
8
|
cogWheelMenu,
|
|
@@ -10,6 +11,7 @@ export default function HeaderActions({
|
|
|
10
11
|
}) {
|
|
11
12
|
return (
|
|
12
13
|
<>
|
|
14
|
+
<InstitutionsDropDown />
|
|
13
15
|
<FreshDeskHelpButton />
|
|
14
16
|
{cogWheelMenu?.length ? <CogWheelMenu menu={cogWheelMenu} /> : null}
|
|
15
17
|
<UserBox fullName={fullName} actions={userBoxActions} />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import { Visibility, VisibilityOff } from '@mui/icons-material'
|
|
2
3
|
import {
|
|
3
4
|
Alert,
|
|
@@ -13,20 +14,21 @@ import axiosBase from 'axios'
|
|
|
13
14
|
import Cookies from 'js-cookie'
|
|
14
15
|
import { useEffect, useState } from 'react'
|
|
15
16
|
import { useForm } from 'react-hook-form'
|
|
17
|
+
import { Link } from 'react-router-dom'
|
|
18
|
+
import axios from '../config/axios'
|
|
19
|
+
import adminAxios from '../utils/adminAxios'
|
|
16
20
|
import ActionButton from './ActionButton'
|
|
17
21
|
import { FormTextField } from './HookForm'
|
|
18
|
-
import axios from '../config/axios'
|
|
19
22
|
import ResetPassword from './ResetPassword'
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import adminAxios from '../utils/adminAxios'
|
|
23
|
+
import Image from './Image/Image'
|
|
24
|
+
import { campxLogoPrimary } from '../assets/images'
|
|
23
25
|
|
|
24
26
|
export function LoginForm({
|
|
25
27
|
loginUrl,
|
|
26
|
-
|
|
28
|
+
close,
|
|
27
29
|
}: {
|
|
28
30
|
loginUrl?: string
|
|
29
|
-
|
|
31
|
+
close: any
|
|
30
32
|
}) {
|
|
31
33
|
const [showPassword, setShowPassword] = useState(false)
|
|
32
34
|
const [forgotMail, setForgotMail] = useState(null)
|
|
@@ -86,6 +88,19 @@ export function LoginForm({
|
|
|
86
88
|
|
|
87
89
|
return (
|
|
88
90
|
<Box sx={{ position: 'relative', height: '100%' }}>
|
|
91
|
+
<Box
|
|
92
|
+
sx={{
|
|
93
|
+
display: 'flex',
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
margin: '30px 0',
|
|
96
|
+
flexDirection: 'column',
|
|
97
|
+
gap: '10px',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<Image alt="" height="auto" width={'200px'} src={campxLogoPrimary} />
|
|
102
|
+
<Typography variant="body2">Developer Login</Typography>
|
|
103
|
+
</Box>
|
|
89
104
|
{resetPassword ? (
|
|
90
105
|
<>
|
|
91
106
|
<ResetPassword />
|
|
@@ -168,26 +183,6 @@ export function LoginForm({
|
|
|
168
183
|
{error}
|
|
169
184
|
</Alert>
|
|
170
185
|
)}
|
|
171
|
-
|
|
172
|
-
{showSuperAdminForm && (
|
|
173
|
-
<Box
|
|
174
|
-
sx={{
|
|
175
|
-
position: 'absolute',
|
|
176
|
-
bottom: '20px',
|
|
177
|
-
right: '20px',
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
<DialogButton
|
|
181
|
-
title="Super Admin Login"
|
|
182
|
-
anchor={({ open }) => (
|
|
183
|
-
<Button onClick={open} size="small" variant="text">
|
|
184
|
-
Super Admin Login
|
|
185
|
-
</Button>
|
|
186
|
-
)}
|
|
187
|
-
content={({ close }) => <SuperAdminLoginForm close={close} />}
|
|
188
|
-
/>
|
|
189
|
-
</Box>
|
|
190
|
-
)}
|
|
191
186
|
</Box>
|
|
192
187
|
)
|
|
193
188
|
}
|
|
@@ -83,7 +83,7 @@ export default function DialogButton({
|
|
|
83
83
|
|
|
84
84
|
interface CustomDialogProps {
|
|
85
85
|
content: (props: { close: () => void }) => ReactNode
|
|
86
|
-
title
|
|
86
|
+
title?: string
|
|
87
87
|
onClose: () => void
|
|
88
88
|
open: boolean
|
|
89
89
|
dialogProps?: Omit<DialogProps, 'open'>
|
|
@@ -96,30 +96,34 @@ export const CustomDialog = ({
|
|
|
96
96
|
content,
|
|
97
97
|
open,
|
|
98
98
|
}: CustomDialogProps) => {
|
|
99
|
+
const props = {
|
|
100
|
+
PaperProps: {
|
|
101
|
+
...dialogProps?.PaperProps,
|
|
102
|
+
elevation: 2,
|
|
103
|
+
sx: { borderRadius: '10px' },
|
|
104
|
+
},
|
|
105
|
+
fullWidth: true,
|
|
106
|
+
onClose: onClose,
|
|
107
|
+
open: open,
|
|
108
|
+
transitionDuration: 140,
|
|
109
|
+
TransitionComponent: Transition,
|
|
110
|
+
sx: {
|
|
111
|
+
...dialogProps?.sx,
|
|
112
|
+
'& .MuiBackdrop-root': { backgroundColor: 'rgba(0, 0, 0, 0.4)' },
|
|
113
|
+
},
|
|
114
|
+
...dialogProps,
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
return (
|
|
100
|
-
<Dialog
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
transitionDuration={140}
|
|
110
|
-
TransitionComponent={Transition}
|
|
111
|
-
sx={{
|
|
112
|
-
...dialogProps?.sx,
|
|
113
|
-
'& .MuiBackdrop-root': { backgroundColor: 'rgba(0, 0, 0, 0.4)' },
|
|
114
|
-
}}
|
|
115
|
-
{...dialogProps}
|
|
116
|
-
>
|
|
117
|
-
<StyledDialogHeader>
|
|
118
|
-
<DialogTitle>{title}</DialogTitle>
|
|
119
|
-
<IconButton onClick={onClose} sx={{ color: 'black' }}>
|
|
120
|
-
<Close />
|
|
121
|
-
</IconButton>
|
|
122
|
-
</StyledDialogHeader>
|
|
118
|
+
<Dialog {...props}>
|
|
119
|
+
{title && (
|
|
120
|
+
<StyledDialogHeader>
|
|
121
|
+
<DialogTitle>{title}</DialogTitle>
|
|
122
|
+
<IconButton onClick={onClose} sx={{ color: 'black' }}>
|
|
123
|
+
<Close />
|
|
124
|
+
</IconButton>
|
|
125
|
+
</StyledDialogHeader>
|
|
126
|
+
)}
|
|
123
127
|
<StyledDialogContent>{content({ close: onClose })}</StyledDialogContent>
|
|
124
128
|
</Dialog>
|
|
125
129
|
)
|
package/src/config/axios.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import Axios from 'axios'
|
|
1
|
+
import Axios, { AxiosRequestConfig } from 'axios'
|
|
2
2
|
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
6
|
import { isDevelopment } from '../constants'
|
|
7
|
+
import { UserStore } from '../shared-state'
|
|
8
|
+
import { InsititutionsStore } from '../shared-state/InstitutionsStore'
|
|
7
9
|
|
|
8
10
|
const sessionKey = Cookies.get('campx_session_key')
|
|
9
11
|
const clientId = window.location.pathname.split('/')[1] ?? 'campx_dev'
|
|
@@ -30,7 +32,12 @@ let axios = Axios.create({
|
|
|
30
32
|
})
|
|
31
33
|
|
|
32
34
|
axios.interceptors.request.use(
|
|
33
|
-
function (config) {
|
|
35
|
+
function (config: AxiosRequestConfig) {
|
|
36
|
+
const { current } = InsititutionsStore.getRawState()
|
|
37
|
+
if (current) {
|
|
38
|
+
config.headers['x-institution-code'] = current.code
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
const params = formatParams(config?.params)
|
|
35
42
|
NetworkStore.update((s) => {
|
|
36
43
|
s.loading = true
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react'
|
|
2
2
|
import { LoginForm } from '../components'
|
|
3
3
|
import { useModal } from '../components/DrawerWrapper/DrawerWrapper'
|
|
4
|
+
import { openRootModal } from './RootModal'
|
|
4
5
|
|
|
5
6
|
const LoginContext = createContext<{
|
|
6
7
|
openLoginForm: (loginUrl: string) => void
|
|
@@ -12,11 +13,8 @@ export default function LoginFormProvider({ children }) {
|
|
|
12
13
|
const modal = useModal()
|
|
13
14
|
|
|
14
15
|
const onLogin = (loginUrl: string) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
content({ close }) {
|
|
18
|
-
return <LoginForm loginUrl={loginUrl} />
|
|
19
|
-
},
|
|
16
|
+
openRootModal({
|
|
17
|
+
key: 'login',
|
|
20
18
|
})
|
|
21
19
|
}
|
|
22
20
|
|
|
@@ -8,40 +8,49 @@ import { ReactNode, useEffect } from 'react'
|
|
|
8
8
|
import { ToastContainer } from '../components'
|
|
9
9
|
import DialogProvider from '../components/DrawerWrapper/DrawerWrapper'
|
|
10
10
|
import GlobalNetworkLoadingIndicator from '../components/ErrorBoundary/GlobalNetworkLoadingIndicator'
|
|
11
|
-
import LoginFormProvider from './LoginFormProvider'
|
|
12
11
|
import FreshChatButton from '../components/Layout/Header/HeaderActions/FreshChatButton'
|
|
12
|
+
import RootModal from './RootModal'
|
|
13
13
|
|
|
14
14
|
export const campxTenantKey = Cookies.get('campx_tenant')
|
|
15
15
|
export const urlTenantKey = window.location.pathname.split('/')[1]
|
|
16
|
+
export const instituitionKey = window.location.pathname.split('/')[2]
|
|
17
|
+
|
|
18
|
+
export default function Providers({ children }: { children: ReactNode }) {
|
|
19
|
+
const localInstituitionKey = localStorage.getItem('institution_key')
|
|
20
|
+
|
|
21
|
+
const insititutionCode = instituitionKey
|
|
22
|
+
? instituitionKey
|
|
23
|
+
: localInstituitionKey
|
|
24
|
+
? localInstituitionKey
|
|
25
|
+
: null
|
|
16
26
|
|
|
17
|
-
export default function Providers({
|
|
18
|
-
children,
|
|
19
|
-
showSuperAdminLoginForm = false,
|
|
20
|
-
}: {
|
|
21
|
-
children: ReactNode
|
|
22
|
-
showSuperAdminLoginForm?: boolean
|
|
23
|
-
}) {
|
|
24
27
|
useEffect(() => {
|
|
25
28
|
if (!urlTenantKey) {
|
|
26
|
-
if (campxTenantKey)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if (campxTenantKey)
|
|
30
|
+
window.location.replace(window.location.origin + `/${campxTenantKey}`)
|
|
31
|
+
} else {
|
|
32
|
+
if (!instituitionKey) {
|
|
33
|
+
if (localInstituitionKey) {
|
|
34
|
+
window.location.replace(
|
|
35
|
+
window.location.origin + `/${urlTenantKey}/${localInstituitionKey}`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
}, [])
|
|
33
41
|
|
|
42
|
+
const baseName = `${urlTenantKey}/${insititutionCode}`
|
|
43
|
+
|
|
34
44
|
return (
|
|
35
|
-
<BrowserRouter basename={
|
|
45
|
+
<BrowserRouter basename={baseName}>
|
|
36
46
|
<QueryClientProvider>
|
|
37
47
|
<MuiThemeProvider>
|
|
38
48
|
<ConfirmContextProvider>
|
|
39
49
|
<DialogProvider>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</LoginFormProvider>
|
|
50
|
+
{children}
|
|
51
|
+
<GlobalNetworkLoadingIndicator />
|
|
52
|
+
<FreshChatButton />
|
|
53
|
+
<RootModal />
|
|
45
54
|
<ToastContainer />
|
|
46
55
|
</DialogProvider>
|
|
47
56
|
</ConfirmContextProvider>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Store } from 'pullstate'
|
|
2
|
+
import { CustomDialog, LoginForm } from '../components'
|
|
3
|
+
import InsititutionsDialog from '../components/Institutions'
|
|
4
|
+
import { DialogProps } from '@mui/material'
|
|
5
|
+
|
|
6
|
+
const getRootDialogContent = (
|
|
7
|
+
key: string,
|
|
8
|
+
): (({ close, data }) => JSX.Element) => {
|
|
9
|
+
switch (key) {
|
|
10
|
+
case 'login':
|
|
11
|
+
return ({ close, data }) => (
|
|
12
|
+
<LoginForm loginUrl={data?.loginUrl} close={close} />
|
|
13
|
+
)
|
|
14
|
+
case 'institutions':
|
|
15
|
+
return ({ close }) => <InsititutionsDialog close={close} />
|
|
16
|
+
default:
|
|
17
|
+
return () => <></>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const initState = {
|
|
22
|
+
isOpen: false,
|
|
23
|
+
key: '',
|
|
24
|
+
content: ({ close, data }) => <></>,
|
|
25
|
+
contentData: null,
|
|
26
|
+
dialogProps: null,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const rootModalStore = new Store(initState)
|
|
30
|
+
|
|
31
|
+
export default function RootModal() {
|
|
32
|
+
const {
|
|
33
|
+
isOpen,
|
|
34
|
+
content: Content,
|
|
35
|
+
contentData,
|
|
36
|
+
dialogProps,
|
|
37
|
+
} = rootModalStore.useState()
|
|
38
|
+
|
|
39
|
+
const onClose = () => {
|
|
40
|
+
rootModalStore.update((s) => {
|
|
41
|
+
s.isOpen = false
|
|
42
|
+
s.key = ''
|
|
43
|
+
s.content = ({ close }) => <></>
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dialogContent = () => (
|
|
48
|
+
<Content close={dialogProps?.onClose ?? onClose} data={contentData} />
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<CustomDialog
|
|
53
|
+
content={dialogContent}
|
|
54
|
+
onClose={dialogProps?.onClose ?? onClose}
|
|
55
|
+
open={isOpen}
|
|
56
|
+
dialogProps={dialogProps}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const openRootModal = (props: {
|
|
62
|
+
key: string
|
|
63
|
+
contentData?: any
|
|
64
|
+
dialogProps?: Omit<DialogProps, 'open'>
|
|
65
|
+
}) => {
|
|
66
|
+
rootModalStore.update((s) => {
|
|
67
|
+
s.isOpen = true
|
|
68
|
+
s.key = props.key
|
|
69
|
+
s.content = getRootDialogContent(props.key)
|
|
70
|
+
s.contentData = props.contentData
|
|
71
|
+
s.dialogProps = props.dialogProps
|
|
72
|
+
})
|
|
73
|
+
}
|
package/src/hooks/useAuth.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { AxiosError } from 'axios'
|
|
2
2
|
import { useEffect, useState } from 'react'
|
|
3
3
|
import { toast } from 'react-toastify'
|
|
4
|
+
import { institutions } from '../components/Institutions/services'
|
|
4
5
|
import axios from '../config/axios'
|
|
5
6
|
import { isDevelopment } from '../constants'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { urlTenantKey } from '../contexts/Providers'
|
|
8
|
+
import { openRootModal } from '../contexts/RootModal'
|
|
9
|
+
import { AssetsStore, PermissionsStore, UserStore } from '../shared-state'
|
|
10
|
+
import { InsititutionsStore } from '../shared-state/InstitutionsStore'
|
|
11
|
+
import useInstitution from './useInstitution'
|
|
8
12
|
|
|
9
13
|
const url = window.location.origin
|
|
10
14
|
|
|
@@ -32,8 +36,8 @@ const ApplicationObj = {
|
|
|
32
36
|
ums: 'square',
|
|
33
37
|
payments: 'payments',
|
|
34
38
|
exams: 'exams',
|
|
39
|
+
hostel: 'hostels',
|
|
35
40
|
}
|
|
36
|
-
|
|
37
41
|
const checkIsAdmin = (user) => {
|
|
38
42
|
let subDomain = window.location.host.split('.')?.slice(-3)[0]
|
|
39
43
|
const localSubDomain = process.env.REACT_APP_SUBDOMAIN
|
|
@@ -62,10 +66,50 @@ const checkIsAdmin = (user) => {
|
|
|
62
66
|
return profile ? (profile.isAdmin == true ? 1 : 0) : 0
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
const loginErrorHandler = ({
|
|
70
|
+
loginUrl,
|
|
71
|
+
setLoading,
|
|
72
|
+
err,
|
|
73
|
+
}: {
|
|
74
|
+
loginUrl: string
|
|
75
|
+
setLoading?: any
|
|
76
|
+
err: AxiosError
|
|
77
|
+
}) => {
|
|
78
|
+
// setLoading && setLoading(false)
|
|
79
|
+
const origin = window.location.origin
|
|
80
|
+
const isStaging = origin.split('campx')[1] === '.dev'
|
|
81
|
+
|
|
82
|
+
if (isDevelopment || isStaging) {
|
|
83
|
+
openRootModal({
|
|
84
|
+
key: 'login',
|
|
85
|
+
contentData: {
|
|
86
|
+
loginUrl,
|
|
87
|
+
},
|
|
88
|
+
dialogProps: {
|
|
89
|
+
disableEscapeKeyDown: true,
|
|
90
|
+
onClose: () => {},
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
return
|
|
94
|
+
} else {
|
|
95
|
+
window.location.replace(`https://www.id.campx.in/?redirect_to=${url}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (err.response.status !== 401) {
|
|
99
|
+
if (err.response.status > 400 && err.response.status < 500) {
|
|
100
|
+
window.location.replace(`https://www.id.campx.in/?redirect_to=${url}`)
|
|
101
|
+
} else {
|
|
102
|
+
toast.error('Server Error')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
function useAuth({ permissionsEndpoint, loginUrl }: AuthParams): AuthResponse {
|
|
66
|
-
const { openLoginForm } = useLoginForm()
|
|
67
108
|
const [loading, setLoading] = useState<boolean>(false)
|
|
68
109
|
const [data, setData] = useState(null)
|
|
110
|
+
const { current } = InsititutionsStore.useState((s) => s)
|
|
111
|
+
|
|
112
|
+
const { loading: loadingInstituition } = useInstitution()
|
|
69
113
|
|
|
70
114
|
const appInit = async () => {
|
|
71
115
|
setLoading(true)
|
|
@@ -116,26 +160,7 @@ function useAuth({ permissionsEndpoint, loginUrl }: AuthParams): AuthResponse {
|
|
|
116
160
|
})
|
|
117
161
|
})
|
|
118
162
|
.catch((err: AxiosError) => {
|
|
119
|
-
setLoading
|
|
120
|
-
const origin = window.location.origin
|
|
121
|
-
const isStaging = origin.split('campx')[1] === '.dev'
|
|
122
|
-
|
|
123
|
-
if (isDevelopment || isStaging) {
|
|
124
|
-
openLoginForm(loginUrl)
|
|
125
|
-
return
|
|
126
|
-
} else {
|
|
127
|
-
window.location.replace(`https://www.id.campx.in/?redirect_to=${url}`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (err.response.status !== 401) {
|
|
131
|
-
if (err.response.status > 400 && err.response.status < 500) {
|
|
132
|
-
window.location.replace(
|
|
133
|
-
`https://www.id.campx.in/?redirect_to=${url}`,
|
|
134
|
-
)
|
|
135
|
-
} else {
|
|
136
|
-
toast.error('Server Error')
|
|
137
|
-
}
|
|
138
|
-
}
|
|
163
|
+
loginErrorHandler({ loginUrl, setLoading, err })
|
|
139
164
|
})
|
|
140
165
|
}
|
|
141
166
|
|
|
@@ -144,7 +169,7 @@ function useAuth({ permissionsEndpoint, loginUrl }: AuthParams): AuthResponse {
|
|
|
144
169
|
}, [])
|
|
145
170
|
|
|
146
171
|
return {
|
|
147
|
-
loading: loading || !data?.permissions,
|
|
172
|
+
loading: loading || !data?.permissions || !current || loadingInstituition,
|
|
148
173
|
data,
|
|
149
174
|
}
|
|
150
175
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { institutions } from '../components/Institutions/services'
|
|
3
|
+
import { urlTenantKey } from '../contexts/Providers'
|
|
4
|
+
import { openRootModal } from '../contexts/RootModal'
|
|
5
|
+
import { InsititutionsStore } from '../shared-state/InstitutionsStore'
|
|
6
|
+
|
|
7
|
+
const getInstitutionKey = () => {
|
|
8
|
+
const instituitionKey = window.location.pathname.split('/')[2]
|
|
9
|
+
// if (!instituitionKey) {
|
|
10
|
+
// const localInstituitionKey = localStorage.getItem('institution_key')
|
|
11
|
+
// if (localInstituitionKey) {
|
|
12
|
+
// return localInstituitionKey
|
|
13
|
+
// } else {
|
|
14
|
+
// return null
|
|
15
|
+
// }
|
|
16
|
+
// } else {
|
|
17
|
+
// return instituitionKey
|
|
18
|
+
// }
|
|
19
|
+
return instituitionKey
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getInstitutions = async (startLoading, stopLoading) => {
|
|
23
|
+
const insititutionKey = getInstitutionKey()
|
|
24
|
+
try {
|
|
25
|
+
startLoading()
|
|
26
|
+
const data = await institutions.fetchInsititutions()
|
|
27
|
+
InsititutionsStore.update((s) => {
|
|
28
|
+
s.institutions = data?.institutions
|
|
29
|
+
})
|
|
30
|
+
if (data?.institutions?.length === 1 && !insititutionKey) {
|
|
31
|
+
window.location.replace(
|
|
32
|
+
`${window.location.origin}/${urlTenantKey}/${data?.institutions[0]?.code}`,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
if (data?.institutions?.length > 1 && !insititutionKey) {
|
|
36
|
+
openRootModal({
|
|
37
|
+
key: 'institutions',
|
|
38
|
+
dialogProps: {
|
|
39
|
+
disableEscapeKeyDown: true,
|
|
40
|
+
onClose: () => {},
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
stopLoading()
|
|
45
|
+
} catch (error) {
|
|
46
|
+
stopLoading()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getInstitutionsByCode = async (code, startLoading, stopLoading) => {
|
|
51
|
+
try {
|
|
52
|
+
startLoading()
|
|
53
|
+
const data = await institutions.fetchInsititutionByCode(code)
|
|
54
|
+
InsititutionsStore.update((s) => {
|
|
55
|
+
s.current = data
|
|
56
|
+
})
|
|
57
|
+
stopLoading()
|
|
58
|
+
} catch (error) {
|
|
59
|
+
stopLoading()
|
|
60
|
+
openRootModal({
|
|
61
|
+
key: 'institutions',
|
|
62
|
+
dialogProps: {
|
|
63
|
+
disableEscapeKeyDown: true,
|
|
64
|
+
onClose: () => {},
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default function useInstitution() {
|
|
71
|
+
const insititutionKey = getInstitutionKey()
|
|
72
|
+
const [loading, setLoading] = useState(false)
|
|
73
|
+
|
|
74
|
+
const startLoading = () => {
|
|
75
|
+
setLoading(true)
|
|
76
|
+
}
|
|
77
|
+
const stopLoading = () => {
|
|
78
|
+
setLoading(false)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
getInstitutions(startLoading, stopLoading)
|
|
83
|
+
}, [insititutionKey])
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (insititutionKey) {
|
|
87
|
+
getInstitutionsByCode(insititutionKey, startLoading, stopLoading)
|
|
88
|
+
}
|
|
89
|
+
}, [insititutionKey])
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
loading: loading,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -593,7 +593,7 @@ export enum Permission {
|
|
|
593
593
|
//Hostels
|
|
594
594
|
|
|
595
595
|
// manage hostels profile_permissions
|
|
596
|
-
CAN_MANAGE_HOSTELS_PROFILE_PERMISSIONS_VIEW = '
|
|
596
|
+
CAN_MANAGE_HOSTELS_PROFILE_PERMISSIONS_VIEW = 'can_manage_hostels_profile_permissions_view',
|
|
597
597
|
CAN_MANAGE_HOSTELS_PROFILE_PERMISSIONS_ADD = 'can_manage_hostels_profile_permissions_add',
|
|
598
598
|
CAN_MANAGE_HOSTELS_PROFILE_PERMISSIONS_EDIT = 'can_manage_hostels_profile_permissions_edit',
|
|
599
599
|
CAN_MANAGE_HOSTELS_PROFILE_PERMISSIONS_DELETE = 'can_manage_hostels_profile_permissions_delete',
|