@campxdev/shared 1.11.15 → 1.11.17

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.
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="18.125" height="20" viewBox="0 0 18.125 20">
2
+ <path id="bell_10_" data-name="bell (10)" d="M18.958,11.384l-1.583-5.7a7.767,7.767,0,0,0-15.064.395L1.085,11.595a4.166,4.166,0,0,0,4.067,5.07H6.08a4.166,4.166,0,0,0,8.166,0h.7a4.166,4.166,0,0,0,4.015-5.281Zm-8.795,6.948a2.5,2.5,0,0,1-2.346-1.666H12.51A2.5,2.5,0,0,1,10.163,18.332Zm6.771-4.32A2.481,2.481,0,0,1,14.944,15H5.152a2.5,2.5,0,0,1-2.44-3.042L3.937,6.444a6.1,6.1,0,0,1,11.832-.31l1.583,5.7a2.481,2.481,0,0,1-.418,2.181Z" transform="translate(-0.986 -0.002)" fill="#121212"/>
3
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="17.049" height="22.632" viewBox="0 0 17.049 22.632">
2
+ <g id="user" transform="translate(-2.85 0.15)">
3
+ <path id="Path_38566" data-name="Path 38566" d="M11.583,11.166A5.583,5.583,0,1,0,6,5.583a5.583,5.583,0,0,0,5.583,5.583Zm0-9.305A3.722,3.722,0,1,1,7.861,5.583,3.722,3.722,0,0,1,11.583,1.861Z" transform="translate(-0.208 0)" stroke="#fff" stroke-width="0.3"/>
4
+ <path id="Path_38567" data-name="Path 38567" d="M11.375,14A8.384,8.384,0,0,0,3,22.375a.931.931,0,1,0,1.861,0,6.514,6.514,0,1,1,13.027,0,.931.931,0,0,0,1.861,0A8.384,8.384,0,0,0,11.375,14Z" transform="translate(0 -0.973)" stroke="#fff" stroke-width="0.3"/>
5
+ </g>
6
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+ <g id="Group_5289" data-name="Group 5289" transform="translate(-5577 -13602)">
3
+ <g id="Group_5288" data-name="Group 5288">
4
+ <g id="Group_5287" data-name="Group 5287">
5
+ <path id="Vector" d="M4.44,0H15.55C19.11,0,20,.89,20,4.44v6.33c0,3.56-.89,4.44-4.44,4.44H4.44C.89,15.22,0,14.33,0,10.78V4.44C0,.89.89,0,4.44,0Z" transform="translate(5579 13604)" fill="none" stroke="#292d32" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
6
+ <path id="Vector-2" data-name="Vector" d="M0,0V4.78" transform="translate(5589 13619.22)" fill="none" stroke="#292d32" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
7
+ <path id="Vector-3" data-name="Vector" d="M0,0H20" transform="translate(5579 13615)" fill="none" stroke="#292d32" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
8
+ <path id="Vector-4" data-name="Vector" d="M0,0H9" transform="translate(5584.5 13624)" fill="none" stroke="#292d32" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
9
+ <path id="Vector-5" data-name="Vector" d="M0,0H24V24H0Z" transform="translate(5577 13602)" fill="none" opacity="0"/>
10
+ </g>
11
+ </g>
12
+ </g>
13
+ </svg>
@@ -0,0 +1,60 @@
1
+ import { Button, Stack } from '@mui/material'
2
+
3
+ import { useMutation, useQuery } from 'react-query'
4
+ import { NoDataIllustration } from '..'
5
+ import { noDevices } from '../../assets/images'
6
+ import axios from '../../config/axios'
7
+ import useConfirm from '../PopupConfirm/useConfirm'
8
+ import Spinner from '../Spinner'
9
+ import { DeviceInformationCard } from './DeviceInformationCard'
10
+
11
+ const ActiveDevices = ({ close }) => {
12
+ const { isConfirmed } = useConfirm()
13
+ const {
14
+ data: activeSessions,
15
+ isLoading,
16
+ isRefetching,
17
+ refetch,
18
+ } = useQuery('activeDevices', () =>
19
+ axios.get('/auth-server/auth/active-sessions').then((res) => res?.data),
20
+ )
21
+
22
+ const { mutate: logoutAllOtherDevices } = useMutation(
23
+ () => axios.post('/auth-server/auth/logout-active-sessions'),
24
+ {
25
+ onSuccess: () => {
26
+ close()
27
+ },
28
+ },
29
+ )
30
+
31
+ const handleLogoutAllOtherDevices = async () => {
32
+ const confirm = await isConfirmed(
33
+ 'Are you sure you want to logout from all other devices?',
34
+ )
35
+ if (confirm) {
36
+ logoutAllOtherDevices()
37
+ }
38
+ }
39
+ if (isLoading || isRefetching) return <Spinner />
40
+ if (activeSessions?.length == 0)
41
+ return (
42
+ <NoDataIllustration
43
+ height="60vh"
44
+ message="No active devices found"
45
+ imageSrc={noDevices.default}
46
+ />
47
+ )
48
+ return (
49
+ <Stack height="60vh" padding="10px 50px" gap={3} alignItems="flex-end">
50
+ <Button onClick={handleLogoutAllOtherDevices} sx={{ width: '300px' }}>
51
+ Logout from All Other Devices
52
+ </Button>
53
+ {activeSessions?.map((sessionData) => (
54
+ <DeviceInformationCard sessionData={sessionData} refetch={refetch} />
55
+ ))}
56
+ </Stack>
57
+ )
58
+ }
59
+
60
+ export default ActiveDevices
@@ -0,0 +1,97 @@
1
+ import { Box, Button, Stack, Typography, styled } from '@mui/material'
2
+ import { format } from 'date-fns-tz'
3
+ import { useMutation } from 'react-query'
4
+ import { toast } from 'react-toastify'
5
+ import { location, mobile, web } from '../../assets/images'
6
+ import axios from '../../config/axios'
7
+ import useConfirm from '../PopupConfirm/useConfirm'
8
+
9
+ const StyledIconContainer = styled(Box)(({ theme }) => ({
10
+ backgroundColor: '#ED90350F',
11
+ borderRadius: '10px',
12
+ border: '1px solid #ED903580',
13
+ width: '60px',
14
+ height: '60px',
15
+ padding: '17px 0px 0px 17px',
16
+ }))
17
+
18
+ const StyledTrustedContainer = styled(Box)(({ theme }) => ({
19
+ backgroundColor: '#F4A101',
20
+ borderRadius: '15px',
21
+ padding: '3px 15px',
22
+ }))
23
+
24
+ export const DeviceInformationCard = ({ sessionData, refetch }) => {
25
+ const { isConfirmed } = useConfirm()
26
+
27
+ const { mutate: logout } = useMutation(
28
+ () =>
29
+ axios.post('/auth-server/auth/logout-active-sessions', {
30
+ deviceInformationId: sessionData?.deviceInformation?._id,
31
+ }),
32
+ {
33
+ onSuccess: () => {
34
+ refetch()
35
+ },
36
+ onError: (error: any) => {
37
+ toast.error(error?.response?.data?.message)
38
+ },
39
+ },
40
+ )
41
+ const handleLogout = async () => {
42
+ const confirmed = await isConfirmed(
43
+ 'Are you sure you want to logout from this device?',
44
+ )
45
+ if (confirmed) logout()
46
+ }
47
+ return (
48
+ <Box
49
+ width="100%"
50
+ border="1px solid #1212121A"
51
+ borderRadius="10px"
52
+ padding="20px 0px 20px 0px"
53
+ >
54
+ <Stack
55
+ direction="row"
56
+ gap={2}
57
+ alignItems="center"
58
+ justifyContent="space-around"
59
+ >
60
+ <Stack direction="row" alignItems="center" gap={2}>
61
+ <StyledIconContainer>
62
+ <img
63
+ src={(sessionData.type = 'WEB' ? web.default : mobile.default)}
64
+ />
65
+ </StyledIconContainer>
66
+ <Stack>
67
+ <Stack direction="row" alignItems="center" gap={1}>
68
+ <Typography variant="h1" fontSize="15px">
69
+ {`${sessionData?.deviceInformation?.clientName} (${sessionData?.deviceInformation?.os})`}
70
+ </Typography>
71
+ {sessionData?.deviceInformation?.trusted && (
72
+ <StyledTrustedContainer>
73
+ <Typography variant="subtitle1" color="white" fontSize="12px">
74
+ Trusted
75
+ </Typography>
76
+ </StyledTrustedContainer>
77
+ )}
78
+ </Stack>
79
+ <Typography variant="caption">{`Last Active on ${format(
80
+ new Date(sessionData?.lastAccessedAt),
81
+ 'dd MMM, yyyy hh:mm a',
82
+ )}`}</Typography>
83
+ </Stack>
84
+ </Stack>
85
+ <Stack direction="row" alignItems="center" gap={1}>
86
+ <img src={location.default} />
87
+ <Typography variant="caption">
88
+ {sessionData?.deviceInformation?.locationName}
89
+ </Typography>
90
+ </Stack>
91
+ <Button onClick={handleLogout} variant="outlined">
92
+ Logout
93
+ </Button>
94
+ </Stack>
95
+ </Box>
96
+ )
97
+ }
@@ -0,0 +1 @@
1
+ export { default } from './ActiveDevices'
@@ -1,6 +1,6 @@
1
- import { SettingsOutlined } from '@mui/icons-material'
2
1
  import { IconButton } from '@mui/material'
3
2
  import { useNavigate } from 'react-router-dom'
3
+ import { clogWheel } from '../../../../assets/images'
4
4
  import DropDownButton from '../../../DropDownButton/DropDownButton'
5
5
 
6
6
  const CogWheelMenu = ({ menu }) => {
@@ -10,7 +10,7 @@ const CogWheelMenu = ({ menu }) => {
10
10
  <DropDownButton
11
11
  anchor={({ open }) => (
12
12
  <IconButton color="secondary" onClick={open}>
13
- <SettingsOutlined />
13
+ <img src={clogWheel.default} alt="Settings" />
14
14
  </IconButton>
15
15
  )}
16
16
  menu={menu?.map((item) => ({
@@ -1,10 +1,15 @@
1
- import { Box, IconButton, styled } from '@mui/material'
1
+ import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
2
+ import { IconButton, Stack } from '@mui/material'
2
3
  import InstitutionsDropDown from '../../../Institutions/InstitutionsDropdown'
3
4
  import CogWheelMenu from './CogWheelMenu'
4
5
  import UserBox from './UserBox'
5
- const StyledBeamedBox = styled(Box)(() => ({
6
- width: '60px',
7
- }))
6
+ import { CareerIcon, ExamResultIcon } from '../../../../assets/icons'
7
+ import { useNavigate } from 'react-router-dom'
8
+
9
+ const profileMenu = [
10
+ { label: 'Student Results', path: '/results' },
11
+ { label: 'Careers', path: '/careers' },
12
+ ]
8
13
 
9
14
  export default function HeaderActions({
10
15
  cogWheelMenu,
@@ -12,25 +17,42 @@ export default function HeaderActions({
12
17
  userBoxActions,
13
18
  profileUrl = '',
14
19
  }) {
20
+ const navigate = useNavigate()
15
21
  return (
16
- <>
22
+ <Stack direction="row" gap={6}>
17
23
  <InstitutionsDropDown />
18
24
  {/* <SearchButton /> */}
19
25
  {/* <FreshDeskHelpButton /> */}
20
- <StyledBeamedBox></StyledBeamedBox>
21
- <IconButton
22
- href={'https://campx.atlassian.net/servicedesk/customer/portal/2'}
23
- target="_blank"
24
- sx={{ color: 'black', padding: '0px' }}
25
- >
26
- Help ?
27
- </IconButton>
28
- {cogWheelMenu?.length ? <CogWheelMenu menu={cogWheelMenu} /> : null}
29
- <UserBox
30
- fullName={fullName}
31
- actions={userBoxActions}
32
- profileUrl={profileUrl}
33
- />
34
- </>
26
+ <Stack direction="row" gap={2}>
27
+ <IconButton
28
+ href={'https://campx.atlassian.net/servicedesk/customer/portal/2'}
29
+ target="_blank"
30
+ sx={{ color: 'black', padding: '0px' }}
31
+ >
32
+ <HelpOutlineIcon />
33
+ </IconButton>
34
+ <IconButton
35
+ onClick={() => {
36
+ navigate('/results')
37
+ }}
38
+ >
39
+ <ExamResultIcon />
40
+ </IconButton>
41
+ <IconButton
42
+ onClick={() => {
43
+ navigate('/careers')
44
+ }}
45
+ >
46
+ <CareerIcon />
47
+ </IconButton>
48
+
49
+ {/* {cogWheelMenu?.length ? <CogWheelMenu menu={cogWheelMenu} /> : null} */}
50
+ <UserBox
51
+ fullName={fullName}
52
+ actions={userBoxActions}
53
+ profileUrl={profileUrl}
54
+ />
55
+ </Stack>
56
+ </Stack>
35
57
  )
36
58
  }
@@ -1,6 +1,13 @@
1
- import { ExitToAppOutlined, HttpsOutlined, Person } from '@mui/icons-material'
2
1
  import { useNavigate } from 'react-router-dom'
2
+ import {
3
+ activeDevices,
4
+ changePassword,
5
+ logoutIcon,
6
+ profile,
7
+ } from '../../../../assets/images'
8
+
3
9
  import logout from '../../../../utils/logout'
10
+ import ActiveDevices from '../../../ActiveDevices'
4
11
  import ChangePassword from '../../../ChangePassword'
5
12
  import DropDownButton from '../../../DropDownButton/DropDownButton'
6
13
  import { IMenuItemProps } from '../../../DropDownButton/DropdownMenuItem'
@@ -42,7 +49,9 @@ export default function UserBox({
42
49
  {
43
50
  label: 'My Profile',
44
51
  actionType: 'dialog',
45
- icon: <Person />,
52
+ icon: (
53
+ <img style={{ marginRight: '10px' }} src={profile.default} />
54
+ ),
46
55
  content: ({ close }) => <MyProfile close={close} />,
47
56
  dialogProps: {
48
57
  maxWidth: 'xl',
@@ -53,17 +62,46 @@ export default function UserBox({
53
62
  },
54
63
  },
55
64
  },
56
-
65
+ {
66
+ label: 'Active Devices',
67
+ actionType: 'dialog',
68
+ icon: (
69
+ <img
70
+ style={{ marginRight: '10px' }}
71
+ src={activeDevices.default}
72
+ />
73
+ ),
74
+ content: ({ close }) => <ActiveDevices close={close} />,
75
+ contentTitle: 'Active Devices',
76
+ dialogProps: {
77
+ maxWidth: 'lg',
78
+ PaperProps: {
79
+ sx: {
80
+ padding: 0,
81
+ },
82
+ },
83
+ },
84
+ },
57
85
  {
58
86
  label: 'Change Password',
59
87
  actionType: 'dialog',
60
- icon: <HttpsOutlined />,
88
+ icon: (
89
+ <img
90
+ style={{ marginRight: '10px' }}
91
+ src={changePassword.default}
92
+ />
93
+ ),
61
94
  content: ({ close }) => <ChangePassword close={close} />,
62
95
  contentTitle: 'Change Password',
63
96
  },
64
97
  {
65
98
  label: 'Logout',
66
- icon: <ExitToAppOutlined />,
99
+ icon: (
100
+ <img
101
+ style={{ marginRight: '10px' }}
102
+ src={logoutIcon.default}
103
+ />
104
+ ),
67
105
  onClick: logout,
68
106
  },
69
107
  ]
@@ -11,6 +11,7 @@ import {
11
11
  styled,
12
12
  } from '@mui/material'
13
13
  import axiosBase from 'axios'
14
+ import DeviceDetector from 'device-detector-js'
14
15
  import Cookies from 'js-cookie'
15
16
  import { useEffect, useState } from 'react'
16
17
  import { useForm } from 'react-hook-form'
@@ -36,16 +37,28 @@ export function LoginForm({
36
37
  const [resetPassword, setResetPassword] = useState(false)
37
38
  const { handleSubmit, control } = useForm()
38
39
  const [error, setError] = useState('')
40
+ const [deviceState, setDeviceState] = useState({
41
+ deviceInformation: {
42
+ deviceType: 'browser',
43
+ clientName: 'unknown',
44
+ os: 'unknown',
45
+ osVersion: 'unknown',
46
+ latitude: null,
47
+ longitude: null,
48
+ tokenType: 'WEB',
49
+ },
50
+ isLocationAllowed: true,
51
+ })
39
52
 
40
53
  const onSubmit = async (values) => {
41
54
  try {
42
55
  const res = await axiosBase({
43
56
  method: 'POST',
44
57
  baseURL: process.env.REACT_APP_API_HOST,
45
- url: loginUrl ? loginUrl : `/auth-server/auth/login-dev`,
58
+ url: loginUrl ? loginUrl : `/auth-server/auth/login`,
46
59
  data: {
47
60
  ...values,
48
- type: 'WEB',
61
+ ...deviceState.deviceInformation,
49
62
  },
50
63
  })
51
64
  Cookies.set('campx_tenant', res?.data?.subDomain)
@@ -83,6 +96,39 @@ export function LoginForm({
83
96
  }
84
97
 
85
98
  useEffect(() => {
99
+ navigator.geolocation.getCurrentPosition((position) => {
100
+ setDeviceState((s) => ({
101
+ ...s,
102
+ deviceInformation: {
103
+ ...s.deviceInformation,
104
+ latitude: position.coords.latitude,
105
+ longitude: position.coords.longitude,
106
+ },
107
+ }))
108
+ })
109
+ navigator.permissions
110
+ .query({ name: 'geolocation' })
111
+ .then((permissionStatus) => {
112
+ if (permissionStatus.state === 'denied') {
113
+ setDeviceState((s) => ({
114
+ ...s,
115
+ isLocationAllowed: false,
116
+ }))
117
+ }
118
+ })
119
+
120
+ const detector = new DeviceDetector()
121
+ const deviceInfo = detector.parse(navigator.userAgent)
122
+ setDeviceState((s) => ({
123
+ ...s,
124
+ deviceInformation: {
125
+ ...s.deviceInformation,
126
+ clientName: deviceInfo.client?.name || s.deviceInformation.clientName,
127
+ os: deviceInfo.os?.name || s.deviceInformation.os,
128
+ osVersion: deviceInfo.os?.version || s.deviceInformation.osVersion,
129
+ },
130
+ }))
131
+
86
132
  const restLink = window.location.pathname.split('/')[1]
87
133
  if (restLink == 'reset-password') {
88
134
  setResetPassword(true)
@@ -138,6 +184,11 @@ export function LoginForm({
138
184
  ),
139
185
  }}
140
186
  />
187
+ {!deviceState.isLocationAllowed && (
188
+ <Alert severity="warning" sx={{ marginTop: '20px' }}>
189
+ Please Enable Location Services
190
+ </Alert>
191
+ )}
141
192
  <ActionButton type="submit">Login</ActionButton>
142
193
  <Typography
143
194
  variant="h6"