@campxdev/shared 1.8.4 → 1.8.5-0.alpha-2

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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/components/ApplicationProfile/ApplicationProfile.tsx +342 -0
  3. package/src/components/ApplicationProfile/Service.ts +68 -0
  4. package/src/components/ApplicationProfile/UserProfileRelation.tsx +174 -0
  5. package/src/components/ApplicationProfile/index.tsx +1 -0
  6. package/src/components/DropDownButton/DropdownMenuItem.tsx +12 -0
  7. package/src/components/ErrorBoundary/ErrorBoundary.tsx +8 -3
  8. package/src/components/ErrorBoundary/ErrorFallback.tsx +7 -2
  9. package/src/components/FloatingContainer.tsx +1 -1
  10. package/src/components/HookForm/MultiSelect.tsx +8 -0
  11. package/src/components/Input/DatePicker.tsx +11 -0
  12. package/src/components/Input/MultiSelect.tsx +11 -0
  13. package/src/components/Layout/Header/AppHeader.tsx +2 -1
  14. package/src/components/Layout/Header/AppsMenu.tsx +51 -23
  15. package/src/components/Layout/Header/HeaderActions/FreshChatButton.tsx +61 -0
  16. package/src/components/Layout/Header/HeaderActions/FreshDeskHelpButton.tsx +36 -7
  17. package/src/components/Layout/Header/HeaderActions/HeaderActions.tsx +2 -0
  18. package/src/components/Layout/Header/SchoolSwitch/SchoolSwitch.tsx +33 -0
  19. package/src/components/Layout/Header/applications.ts +8 -9
  20. package/src/components/Layout/Header/assets/chat_with_us.png +0 -0
  21. package/src/components/Layout/Header/assets/index.ts +2 -0
  22. package/src/components/Layout/Helmet.tsx +91 -32
  23. package/src/components/ModalButtons/PopoverButton.tsx +99 -0
  24. package/src/components/Tabs/TabsContainer.tsx +3 -0
  25. package/src/components/index.ts +4 -0
  26. package/src/config/axios.ts +8 -2
  27. package/src/constants/UIConstants.ts +27 -0
  28. package/src/contexts/LoginFormProvider.tsx +2 -7
  29. package/src/contexts/Providers.tsx +3 -1
  30. package/src/hooks/index.ts +1 -0
  31. package/src/hooks/useAuth.ts +58 -2
  32. package/src/hooks/useExternalScript.ts +38 -0
  33. package/src/hooks/useFilters.ts +6 -3
  34. package/src/shared-state/PermissionsStore.ts +937 -130
  35. package/src/theme/muiTheme.ts +6 -4
  36. package/src/theme/theme.d.ts +2 -0
  37. package/tsconfig.json +19 -19
@@ -2,6 +2,7 @@ import { ReactNode } from 'react'
2
2
  import { Controller } from 'react-hook-form'
3
3
  import { MultiSelect } from '../Input'
4
4
  import { IOption } from '../Input/types'
5
+ import { AutocompleteInputChangeReason } from '@mui/material'
5
6
 
6
7
  interface MultiSelectProps {
7
8
  control: any
@@ -10,6 +11,11 @@ interface MultiSelectProps {
10
11
  options: IOption[]
11
12
  placeholder?: string
12
13
  loading?: boolean
14
+ onInputChange?: (
15
+ event: React.SyntheticEvent,
16
+ value: string,
17
+ reason: AutocompleteInputChangeReason,
18
+ ) => void
13
19
  required?: boolean
14
20
  value?: IOption[] | IOption
15
21
  onChange?: (value: IOption[] | IOption) => void
@@ -24,6 +30,7 @@ export default function FormMultiSelect({
24
30
  loading,
25
31
  required,
26
32
  multiple = true,
33
+ onInputChange,
27
34
  ...props
28
35
  }: MultiSelectProps) {
29
36
  return (
@@ -39,6 +46,7 @@ export default function FormMultiSelect({
39
46
  onChange={(value) => {
40
47
  field.onChange(value)
41
48
  }}
49
+ onInputChange={onInputChange}
42
50
  loading={loading}
43
51
  options={options || []}
44
52
  error={!!error}
@@ -3,6 +3,7 @@ import { InputAdornment, TextFieldProps } from '@mui/material'
3
3
  import 'flatpickr/dist/flatpickr.css'
4
4
  import { ReactNode } from 'react'
5
5
  import Flatpickr, { DateTimePickerProps } from 'react-flatpickr'
6
+ import monthSelectPlugin from 'flatpickr/dist/plugins/monthSelect'
6
7
  import TextField from './TextField'
7
8
 
8
9
  export interface IDatePicker {
@@ -17,8 +18,17 @@ export interface IDatePicker {
17
18
  placeholder?: string
18
19
  inputProps?: TextFieldProps
19
20
  size?: 'medium' | 'small'
21
+ monthSelect?: boolean
20
22
  }
21
23
 
24
+ const plugins = [
25
+ monthSelectPlugin({
26
+ shorthand: true, //defaults to false
27
+ dateFormat: 'm.y', //defaults to "F Y"
28
+ altFormat: 'F Y', //defaults to "F Y"
29
+ }),
30
+ ]
31
+
22
32
  export default function FormDatePicker({
23
33
  name,
24
34
  label,
@@ -29,6 +39,7 @@ export default function FormDatePicker({
29
39
  placeholder,
30
40
  size = 'medium',
31
41
  required = false,
42
+ monthSelect,
32
43
  ...rest
33
44
  }: IDatePicker) {
34
45
  return (
@@ -2,6 +2,7 @@ import { Close, KeyboardArrowDown } from '@mui/icons-material'
2
2
  import {
3
3
  alpha,
4
4
  Autocomplete,
5
+ AutocompleteInputChangeReason,
5
6
  Checkbox,
6
7
  CircularProgress,
7
8
  Popper,
@@ -76,10 +77,16 @@ interface MultiSelectProps {
76
77
  loading?: boolean
77
78
  value: IOption[] | IOption
78
79
  onChange: (value: IOption[] | IOption) => void
80
+ onInputChange?: (
81
+ event: React.SyntheticEvent,
82
+ value: string,
83
+ reason: AutocompleteInputChangeReason,
84
+ ) => void
79
85
  required?: boolean
80
86
  error?: boolean
81
87
  helperText?: string
82
88
  multiple?: boolean
89
+ limitTags?: number
83
90
  }
84
91
 
85
92
  export default function MultiSelect({
@@ -89,10 +96,12 @@ export default function MultiSelect({
89
96
  loading,
90
97
  value,
91
98
  onChange,
99
+ onInputChange,
92
100
  required,
93
101
  error,
94
102
  helperText,
95
103
  multiple = true,
104
+ limitTags = -1,
96
105
  ...props
97
106
  }: MultiSelectProps) {
98
107
  return (
@@ -107,6 +116,8 @@ export default function MultiSelect({
107
116
  if (!onChange) return
108
117
  onChange(value)
109
118
  }}
119
+ onInputChange={onInputChange}
120
+ limitTags={limitTags}
110
121
  isOptionEqualToValue={(option: any, value: any) =>
111
122
  option?.value === value?.value
112
123
  }
@@ -79,7 +79,8 @@ export default function AppHeader({
79
79
  const AppLogo = ({ clientLogo }) => {
80
80
  const originSubdomain = window.location.host.split('.')?.slice(-3)[0] ?? 'ums'
81
81
  const currentApp =
82
- applications.find((item) => item.key === originSubdomain)?.key ?? 'campx'
82
+ applications.find((item) => item.domainName === originSubdomain)
83
+ ?.domainName ?? 'campx'
83
84
 
84
85
  return (
85
86
  <StyledRouterLink to={'/'}>
@@ -1,4 +1,4 @@
1
- import { Box, Menu, Typography } from '@mui/material'
1
+ import { Box, Menu, Typography, styled } from '@mui/material'
2
2
  import Cookies from 'js-cookie'
3
3
  import { useState } from 'react'
4
4
  import { applications } from './applications'
@@ -11,12 +11,17 @@ import {
11
11
  StyledLink,
12
12
  StyledImageBox,
13
13
  } from './styles'
14
-
14
+ import { PermissionsStore } from '../../../shared-state'
15
+ import AppsOutageIcon from '@mui/icons-material/AppsOutage'
15
16
  export const campxTenantKey = Cookies.get('campx_tenant')
16
17
 
17
18
  const AppsMenu = () => {
19
+ const permissionState = PermissionsStore.useState((s) => s)
18
20
  const [anchorEl, setAnchorEl] = useState<any>(null)
19
21
  const open = Boolean(anchorEl)
22
+ const applicationList = applications?.filter((item) =>
23
+ permissionState.applications.includes(item.key),
24
+ )
20
25
 
21
26
  const handleClick = (event) => {
22
27
  setAnchorEl(event.currentTarget)
@@ -60,27 +65,40 @@ const AppsMenu = () => {
60
65
  },
61
66
  }}
62
67
  >
63
- <Box sx={{ padding: '0.3rem 1rem' }}>
64
- <Typography variant="body2">Switch to</Typography>
65
- </Box>
66
- <Box>
67
- {applications.map((item, index) => (
68
- <StyledLink
69
- href={item.path + `/${campxTenantKey ?? ''}`}
70
- key={index}
71
- >
72
- <StyledMenuItemContainer
73
- key={index}
74
- onClick={() => {
75
- window.location.href = item.path
76
- handleClose()
77
- }}
78
- >
79
- <MenuItem data={item} />
80
- </StyledMenuItemContainer>
81
- </StyledLink>
82
- ))}
83
- </Box>
68
+ {applicationList.length != 0 ? (
69
+ <>
70
+ <Box sx={{ padding: '0.3rem 1rem' }}>
71
+ <Typography variant="body2">Switch to</Typography>
72
+ </Box>
73
+ <Box>
74
+ {applicationList.map((item, index) => {
75
+ return (
76
+ <StyledLink
77
+ href={item.path + `/${campxTenantKey ?? ''}`}
78
+ key={index}
79
+ >
80
+ <StyledMenuItemContainer
81
+ key={index}
82
+ onClick={() => {
83
+ window.location.href = item.path
84
+ handleClose()
85
+ }}
86
+ >
87
+ <MenuItem data={item} />
88
+ </StyledMenuItemContainer>
89
+ </StyledLink>
90
+ )
91
+ })}
92
+ </Box>
93
+ </>
94
+ ) : (
95
+ <StyledNoAppContainer>
96
+ <AppsOutageIcon sx={{ width: '40px', height: '40px' }} />
97
+ <Typography variant="subtitle1">
98
+ No Application have been assigned
99
+ </Typography>
100
+ </StyledNoAppContainer>
101
+ )}
84
102
  </Menu>
85
103
  </>
86
104
  )
@@ -103,3 +121,13 @@ const MenuItem = ({ data }) => {
103
121
  </StyledMenuItem>
104
122
  )
105
123
  }
124
+
125
+ const StyledNoAppContainer = styled(Box)({
126
+ width: '300px',
127
+ height: '300px',
128
+ display: 'flex',
129
+ alignItems: 'center',
130
+ flexDirection: 'column',
131
+ justifyContent: 'center',
132
+ gap: '10px',
133
+ })
@@ -0,0 +1,61 @@
1
+ /* eslint-disable no-console */
2
+ import { useState } from 'react'
3
+ import { UserStore } from '../../../../shared-state'
4
+ import { chatWithUs } from '../assets'
5
+ import { Box, styled } from '@mui/material'
6
+
7
+ export default function FreshChatButton() {
8
+ const _window = window as any
9
+ const { user } = UserStore.useState()
10
+
11
+ var openWidget = function () {
12
+ const el = document.getElementById('fc_frame')
13
+ if (el) {
14
+ el.style.visibility = 'visible'
15
+ }
16
+ _window.fcWidget.open()
17
+ initEvents()
18
+ }
19
+ var initEvents = function () {
20
+ const el = document.getElementById('custom_fc_button')
21
+ _window.fcWidget.on('widget:opened', function (resp) {
22
+ if (el) {
23
+ el.style.visibility = 'hidden'
24
+ }
25
+ })
26
+ _window.fcWidget.on('widget:closed', function (resp) {
27
+ if (el) {
28
+ el.style.visibility = 'visible'
29
+ }
30
+ })
31
+ }
32
+
33
+ if (!user) return null
34
+ return (
35
+ <Box
36
+ id="custom_fc_button"
37
+ sx={{
38
+ position: 'fixed',
39
+ right: 0,
40
+ bottom: '18px',
41
+ cursor: 'pointer',
42
+ '@media print': {
43
+ display: 'none',
44
+ },
45
+ }}
46
+ >
47
+ <a id="open_fc_widget" onClick={openWidget} style={{ cursor: 'pointer' }}>
48
+ <StyledImg src={chatWithUs} />
49
+ </a>
50
+ </Box>
51
+ )
52
+ }
53
+
54
+ const StyledImg = styled('img')(({ theme }) => ({
55
+ width: '35px',
56
+ height: 'auto',
57
+ '&:hover': {
58
+ width: '50px',
59
+ },
60
+ transition: 'width 0.3s',
61
+ }))
@@ -1,21 +1,50 @@
1
1
  import { HelpOutline } from '@mui/icons-material'
2
2
  import { Button } from '@mui/material'
3
+ import { UserStore } from '../../../../shared-state'
3
4
 
4
5
  export default function FreshDeskHelpButton() {
5
- const handleOpenFreshDeskWidget = () => {
6
- try {
7
- ;(window as any)?.openFreshDeskWidget()
8
- } catch (error) {
9
- // eslint-disable-next-line no-console
10
- console.error('Error launching Freshdesk')
6
+ const { user } = UserStore.useState()
7
+
8
+ // const handleOpenFreshDeskWidget = () => {
9
+ // try {
10
+ // ;(window as any)?.openFreshDeskWidget()
11
+ // } catch (error) {
12
+ // // eslint-disable-next-line no-console
13
+ // console.error('Error launching Freshdesk')
14
+ // }
15
+ // }
16
+
17
+ const _window = window as any
18
+
19
+ var openWidget = function () {
20
+ const el = document.getElementById('fc_frame')
21
+ if (el) {
22
+ el.style.visibility = 'visible'
11
23
  }
24
+ _window.fcWidget.open()
25
+ initEvents()
12
26
  }
27
+ var initEvents = function () {
28
+ const el = document.getElementById('custom_fc_button')
29
+ _window.fcWidget.on('widget:opened', function (resp) {
30
+ if (el) {
31
+ el.style.visibility = 'hidden'
32
+ }
33
+ })
34
+ _window.fcWidget.on('widget:closed', function (resp) {
35
+ if (el) {
36
+ el.style.visibility = 'visible'
37
+ }
38
+ })
39
+ }
40
+
41
+ if (!user) return null
13
42
  return (
14
43
  <Button
15
44
  variant="text"
16
45
  color="secondary"
46
+ onClick={openWidget}
17
47
  startIcon={<HelpOutline />}
18
- onClick={handleOpenFreshDeskWidget}
19
48
  sx={{ padding: '20px' }}
20
49
  >
21
50
  Help
@@ -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 SchoolSwitch from '../SchoolSwitch/SchoolSwitch'
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
+ {/* <SchoolSwitch /> */}
13
15
  <FreshDeskHelpButton />
14
16
  {cogWheelMenu?.length ? <CogWheelMenu menu={cogWheelMenu} /> : null}
15
17
  <UserBox fullName={fullName} actions={userBoxActions} />
@@ -0,0 +1,33 @@
1
+ import { useState } from 'react'
2
+ import { FormSingleSelect } from '../../../HookForm'
3
+ import { SingleSelect } from '../../../Input'
4
+
5
+ export default function SchoolSwitch() {
6
+ const [state, setStatr] = useState()
7
+
8
+ return (
9
+ <SingleSelect
10
+ containerProps={{
11
+ sx: {
12
+ width: '200px',
13
+ },
14
+ }}
15
+ options={[
16
+ {
17
+ label: 'School 1',
18
+ value: 'school1',
19
+ },
20
+ {
21
+ label: 'School 2',
22
+ value: 'school2',
23
+ },
24
+ {
25
+ label: 'School 3',
26
+ value: 'school3',
27
+ },
28
+ ]}
29
+ value={state}
30
+ onChange={(e) => setStatr(e.target.value)}
31
+ />
32
+ )
33
+ }
@@ -45,12 +45,14 @@ export const applications = [
45
45
  title: 'CollegeX',
46
46
  path: isDevelopment ? origins.ums.dev : origins.ums.prod,
47
47
  icon: campxSquareSmall,
48
- key: 'ums',
48
+ key: 'square',
49
+ domainName: 'ums',
49
50
  description: 'Manage Complete Campus Activities',
50
51
  },
51
52
  {
52
53
  title: 'ExamX',
53
54
  key: 'exams',
55
+ domainName: 'exams',
54
56
  path: isDevelopment ? origins.exams.dev : origins.exams.prod,
55
57
  icon: examsSmall,
56
58
  description: 'Manage all Examinations in the Campus',
@@ -58,20 +60,23 @@ export const applications = [
58
60
  {
59
61
  title: 'PayX',
60
62
  key: 'payments',
63
+ domainName: 'payments',
61
64
  path: isDevelopment ? origins.payments.dev : origins.payments.prod,
62
65
  icon: paySmall,
63
66
  description: 'Manage Payments in the Campus',
64
67
  },
65
68
  {
66
69
  title: 'EnrollX',
67
- key: 'enroll',
70
+ key: 'enroll_x',
71
+ domainName: 'enroll',
68
72
  path: isDevelopment ? origins.enroll.dev : origins.enroll.prod,
69
73
  icon: enrollSmall,
70
74
  description: 'Manage Admissions in the Campus',
71
75
  },
72
76
  {
73
77
  title: 'HostelX',
74
- key: 'hostel',
78
+ key: 'hostels',
79
+ domainName: 'hostel',
75
80
  path: isDevelopment ? origins.hostel.dev : origins.hostel.prod,
76
81
  icon: hostelSmall,
77
82
  description: 'Manage Hostels in the Campus',
@@ -94,10 +99,4 @@ export const applications = [
94
99
  },
95
100
  ]
96
101
  : []),
97
- // {
98
- // title: 'EnrollX',
99
- // path: '/hostel',
100
- // icon: enrollHeaderLogo,
101
- // // description: 'Manage Admissions in the Campus',
102
- // },
103
102
  ]
@@ -14,6 +14,7 @@ import commuteSmall from './commutexSmall.svg'
14
14
  import commutex from './commutex.png'
15
15
  import hostelx from './hostelx.png'
16
16
  import enrollSmall from './enroll_logo.svg'
17
+ import chatWithUs from './chat_with_us.png'
17
18
 
18
19
  export {
19
20
  collegex,
@@ -32,4 +33,5 @@ export {
32
33
  commutex,
33
34
  commuteSmall,
34
35
  enrollSmall,
36
+ chatWithUs,
35
37
  }
@@ -1,49 +1,108 @@
1
+ import { ReactNode } from 'react'
1
2
  import { Helmet as ReactHelmet } from 'react-helmet'
2
3
 
3
- type MetaProps = JSX.IntrinsicElements['meta']
4
- type LinkProps = JSX.IntrinsicElements['link']
4
+ const isLocalHost = process.env.NODE_ENV === 'development'
5
+
6
+ const freshDeskInnerHtml = `
7
+ window.fwSettings = {
8
+ widget_id: 85000000149,
9
+ }
10
+ !(function () {
11
+ window.openFreshDeskWidget = function openFreshDeskWidget() {
12
+ FreshworksWidget('open', 'ticketForm')
13
+ }
14
+ if ('function' != typeof window.FreshworksWidget) {
15
+ var n = function () {
16
+ n.q.push(arguments)
17
+ }
18
+ ;(n.q = []), (window.FreshworksWidget = n)
19
+ }
20
+ FreshworksWidget('hide', 'launcher')
21
+ })()
22
+
23
+ `
24
+
25
+ const getInnerHtml = (user: any) => {
26
+ if (isLocalHost) {
27
+ return ''
28
+ }
29
+ const fcWidgetMessengerConfig = {
30
+ config: {
31
+ headerProperty: {
32
+ hideChatButton: true,
33
+ },
34
+ },
35
+ }
36
+
37
+ const phone = user?.mobile?.substring(user?.mobile?.length - 10)
38
+
39
+ return `
40
+ window.fcWidgetMessengerConfig = ${JSON.stringify(fcWidgetMessengerConfig)}
41
+ window.fcSettings = {
42
+ onInit: function() {
43
+ console.log('Fresh Chat Init')
44
+ window.fcWidget.setExternalId('${user?.email}')
45
+ window.fcWidget.user.setFirstName('${user?.fullName}')
46
+ window.fcWidget.user.setEmail('${user?.email}')
47
+ window.fcWidget.user.setPhone('${phone}')
48
+ }
49
+ }
50
+
51
+ `
52
+ }
5
53
 
6
54
  interface IHelmetProps {
7
55
  appTitle: string
8
56
  favicon: string
9
57
  description?: string
10
- meta?: MetaProps[] | undefined
11
- link?: LinkProps[] | undefined
58
+ user: any
59
+ extraMetaTags?: ReactNode
12
60
  }
13
61
 
14
62
  export default function Helmet({
15
63
  appTitle,
16
64
  favicon,
17
65
  description,
18
- meta = [],
19
- link = [],
66
+ user,
67
+ extraMetaTags,
20
68
  }: IHelmetProps) {
21
69
  return (
22
- <ReactHelmet
23
- htmlAttributes={{ lang: 'en' }}
24
- title={appTitle}
25
- meta={[
26
- {
27
- charSet: 'utf-8',
28
- },
29
- {
30
- name: 'description',
31
- content: description,
32
- },
33
- {
34
- name: 'viewport',
35
- content: 'width=device-width, initial-scale=1',
36
- },
37
- ...meta,
38
- ]}
39
- link={[
40
- { rel: 'icon', href: favicon },
41
- {
42
- rel: 'apple-touch-icon',
43
- href: favicon,
44
- },
45
- ...link,
46
- ]}
47
- />
70
+ <ReactHelmet htmlAttributes={{ lang: 'en' }}>
71
+ <title>{appTitle}</title>
72
+ <meta charSet="utf-8" />
73
+ <meta name="description" content={description} />
74
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
75
+ {extraMetaTags}
76
+ <link rel="icon" href={favicon} />
77
+ <link rel="apple-touch-icon" href={favicon} />
78
+ {!isLocalHost && (
79
+ <script
80
+ type="text/javascript"
81
+ src="//in.fw-cdn.com/30814322/430238.js"
82
+ defer
83
+ />
84
+ )}
85
+ {!isLocalHost && <script defer>{getInnerHtml(user)}</script>}
86
+ {/* {!isLocalHost && (
87
+ <script async defer>
88
+ {freshDeskInnerHtml}
89
+ </script>
90
+ )} */}
91
+ {/* {!isLocalHost && (
92
+ <script
93
+ type="text/javascript"
94
+ src="https://ind-widget.freshworks.com/widgets/85000000149.js"
95
+ defer
96
+ ></script>
97
+ )} */}
98
+ <style type="text/css">
99
+ {`
100
+ #fc_frame {
101
+ bottom: 0 !important;
102
+ right: 0 !important;
103
+ }
104
+ `}
105
+ </style>
106
+ </ReactHelmet>
48
107
  )
49
108
  }