@campxdev/campx-web-utils 0.1.18 → 0.1.20
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/.prettierrc +7 -0
- package/craco.config.js +6 -10
- package/export.ts +3 -0
- package/package.json +3 -3
- package/src/App.tsx +3 -3
- package/src/AppContent.tsx +18 -29
- package/src/Layout/AppLayout.tsx +1 -1
- package/src/Pages/main.tsx +6 -6
- package/src/config/axios.ts +40 -31
- package/src/context/ConfirmDialogProvider.tsx +70 -0
- package/src/context/ErrorBoundary/ErrorBoundary.tsx +23 -23
- package/src/context/ErrorBoundary/Login.tsx +38 -36
- package/src/context/Providers.tsx +19 -20
- package/src/context/SnackbarProvider.tsx +12 -17
- package/src/context/application-store.ts +29 -2
- package/src/context/export.ts +4 -4
- package/src/hooks/export.ts +1 -0
- package/src/hooks/useConfirm.ts +51 -0
- package/src/index.tsx +2 -2
- package/src/utils/breadcrumbs.ts +23 -0
- package/src/utils/export.ts +1 -0
- package/tsconfig.json +5 -13
- package/exports.ts +0 -2
package/.prettierrc
ADDED
package/craco.config.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const { getLoader, loaderByName } = require(
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { getLoader, loaderByName } = require('@craco/craco');
|
|
3
3
|
const packages = [];
|
|
4
|
-
packages.push(path.dirname(require.resolve(
|
|
4
|
+
packages.push(path.dirname(require.resolve('@campxdev/react-blueprint')));
|
|
5
|
+
packages.push(path.dirname(require.resolve('@campxdev/campx-web-utils')));
|
|
5
6
|
module.exports = {
|
|
6
7
|
webpack: {
|
|
7
8
|
configure: (webpackConfig) => {
|
|
8
|
-
const { isFound, match } = getLoader(
|
|
9
|
-
webpackConfig,
|
|
10
|
-
loaderByName("babel-loader")
|
|
11
|
-
);
|
|
9
|
+
const { isFound, match } = getLoader(webpackConfig, loaderByName('babel-loader'));
|
|
12
10
|
if (isFound) {
|
|
13
|
-
const include = Array.isArray(match.loader.include)
|
|
14
|
-
? match.loader.include
|
|
15
|
-
: [match.loader.include];
|
|
11
|
+
const include = Array.isArray(match.loader.include) ? match.loader.include : [match.loader.include];
|
|
16
12
|
match.loader.include = include.concat(packages);
|
|
17
13
|
}
|
|
18
14
|
|
package/export.ts
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campxdev/campx-web-utils",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"main": "./
|
|
3
|
+
"version": "0.1.20",
|
|
4
|
+
"main": "./export.ts",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@campxdev/react-blueprint": "
|
|
7
|
+
"@campxdev/react-blueprint": "1.1.8",
|
|
8
8
|
"@hookform/resolvers": "^3.9.0",
|
|
9
9
|
"@mui/x-date-pickers": "^7.12.1",
|
|
10
10
|
"@testing-library/jest-dom": "^5.14.1",
|
package/src/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useRoutes } from
|
|
1
|
+
import { useRoutes } from 'react-router-dom';
|
|
2
2
|
|
|
3
|
-
import { mainRoutes } from
|
|
4
|
-
import { Providers } from
|
|
3
|
+
import { mainRoutes } from './Pages/main';
|
|
4
|
+
import { Providers } from './context/export';
|
|
5
5
|
|
|
6
6
|
export default function App() {
|
|
7
7
|
return (
|
package/src/AppContent.tsx
CHANGED
|
@@ -1,37 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const fetchClubRequests = () => {
|
|
5
|
-
return axios
|
|
6
|
-
.get(`/hrms/departments`, {
|
|
7
|
-
headers: {
|
|
8
|
-
"x-tenant-id": "aupulse",
|
|
9
|
-
"X-Institution-Code": "aupulse",
|
|
10
|
-
campx_session_key: "66a36c752e28b1200876b034",
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
.then((res) => res.data);
|
|
14
|
-
};
|
|
1
|
+
import { Button } from '@campxdev/react-blueprint';
|
|
2
|
+
import useConfirm from './hooks/useConfirm';
|
|
15
3
|
|
|
16
4
|
function AppContent() {
|
|
17
|
-
const
|
|
18
|
-
data: requests,
|
|
19
|
-
isLoading,
|
|
20
|
-
error,
|
|
21
|
-
refetch,
|
|
22
|
-
} = useQuery("club-requests", () => fetchClubRequests());
|
|
5
|
+
const isConfirm = useConfirm();
|
|
23
6
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
const handleConfirm = async () => {
|
|
8
|
+
const confirm = await isConfirm({
|
|
9
|
+
type: 'delete',
|
|
10
|
+
title: 'Are you sure?',
|
|
11
|
+
message: 'Please confirm before submission to action description',
|
|
12
|
+
});
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
// If user cancel the action
|
|
15
|
+
if (!confirm) {
|
|
16
|
+
console.log('User cancelled the action');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// If user confirm the action
|
|
20
|
+
console.log('User confirmed the action');
|
|
21
|
+
};
|
|
33
22
|
|
|
34
|
-
return
|
|
23
|
+
return <Button onClick={handleConfirm}>Confirm</Button>;
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
export default AppContent;
|
package/src/Layout/AppLayout.tsx
CHANGED
package/src/Pages/main.tsx
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { Navigate } from
|
|
2
|
-
import AppContent from
|
|
3
|
-
import AppLayout from
|
|
1
|
+
import { Navigate } from 'react-router-dom';
|
|
2
|
+
import AppContent from '../AppContent';
|
|
3
|
+
import AppLayout from '../Layout/AppLayout';
|
|
4
4
|
|
|
5
5
|
export const mainRoutes = [
|
|
6
6
|
{
|
|
7
|
-
path:
|
|
7
|
+
path: '/',
|
|
8
8
|
element: <AppLayout />,
|
|
9
9
|
children: [
|
|
10
10
|
{
|
|
11
11
|
index: true,
|
|
12
|
-
element: <Navigate to={
|
|
12
|
+
element: <Navigate to={'app-content'} />,
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
path:
|
|
15
|
+
path: 'app-content',
|
|
16
16
|
element: <AppContent />,
|
|
17
17
|
},
|
|
18
18
|
],
|
package/src/config/axios.ts
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
|
-
import Axios, { InternalAxiosRequestConfig } from
|
|
2
|
-
import Cookies from
|
|
3
|
-
import {
|
|
1
|
+
import Axios, { InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
import Cookies from 'js-cookie';
|
|
3
|
+
import { ApplicationStore } from '../context/application-store';
|
|
4
4
|
|
|
5
|
-
const isDevelopment = process.env.NODE_ENV ==
|
|
5
|
+
const isDevelopment = process.env.NODE_ENV == 'development';
|
|
6
6
|
|
|
7
7
|
const tenantCode =
|
|
8
|
-
window.location.hostname ===
|
|
9
|
-
? Cookies.get(
|
|
10
|
-
: window.location.hostname.split(
|
|
11
|
-
const institutionCode = window.location.pathname.split(
|
|
8
|
+
window.location.hostname === 'localhost'
|
|
9
|
+
? Cookies.get('campx_tenant')
|
|
10
|
+
: window.location.hostname.split('.')[0];
|
|
11
|
+
const institutionCode = window.location.pathname.split('/')[1];
|
|
12
|
+
|
|
13
|
+
export const formatParams = (params: any) => {
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
Object.entries(params ?? {})?.map((i) => [
|
|
16
|
+
i[0],
|
|
17
|
+
i[1] === '__empty__' ? '' : i[1],
|
|
18
|
+
]),
|
|
19
|
+
);
|
|
20
|
+
};
|
|
12
21
|
|
|
13
22
|
export const axios = Axios.create({
|
|
14
23
|
baseURL: process.env.REACT_APP_API_HOST,
|
|
15
24
|
withCredentials: true,
|
|
16
25
|
headers: {
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
'x-tenant-id': tenantCode,
|
|
27
|
+
'x-institution-code': institutionCode,
|
|
19
28
|
},
|
|
20
29
|
});
|
|
21
30
|
|
|
22
31
|
axios.interceptors.request.use(
|
|
23
32
|
function (config: InternalAxiosRequestConfig) {
|
|
24
|
-
const params = config?.params;
|
|
33
|
+
const params = formatParams(config?.params);
|
|
25
34
|
|
|
26
|
-
const sessionKey = Cookies.get(
|
|
27
|
-
const openPaymentsKey = Cookies.get(
|
|
35
|
+
const sessionKey = Cookies.get('campx_session_key');
|
|
36
|
+
const openPaymentsKey = Cookies.get('campx_open_payments_key');
|
|
28
37
|
|
|
29
38
|
if (isDevelopment && sessionKey) {
|
|
30
|
-
config.headers.set(
|
|
39
|
+
config.headers.set('campx_session_key', sessionKey);
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
if (openPaymentsKey) {
|
|
34
|
-
config.headers.set(
|
|
43
|
+
config.headers.set('campx_open_payments_key', openPaymentsKey);
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
return {
|
|
@@ -41,7 +50,7 @@ axios.interceptors.request.use(
|
|
|
41
50
|
},
|
|
42
51
|
function (error) {
|
|
43
52
|
return Promise.reject(error);
|
|
44
|
-
}
|
|
53
|
+
},
|
|
45
54
|
);
|
|
46
55
|
|
|
47
56
|
axios.interceptors.response.use(
|
|
@@ -49,13 +58,13 @@ axios.interceptors.response.use(
|
|
|
49
58
|
console.log(response);
|
|
50
59
|
if (
|
|
51
60
|
response.config.method &&
|
|
52
|
-
[
|
|
61
|
+
['put', 'post', 'delete', 'patch'].includes(response.config.method) &&
|
|
53
62
|
[200, 201, 202].includes(response.status)
|
|
54
63
|
) {
|
|
55
|
-
|
|
56
|
-
s.open = true;
|
|
57
|
-
s.message = response.data.message;
|
|
58
|
-
s.severity =
|
|
64
|
+
ApplicationStore.update((s) => {
|
|
65
|
+
s.snackbar.open = true;
|
|
66
|
+
s.snackbar.message = response.data.message;
|
|
67
|
+
s.snackbar.severity = 'success';
|
|
59
68
|
});
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -63,24 +72,24 @@ axios.interceptors.response.use(
|
|
|
63
72
|
},
|
|
64
73
|
function (err) {
|
|
65
74
|
if ([400, 422].includes(err.response.status)) {
|
|
66
|
-
|
|
67
|
-
s.open = true;
|
|
68
|
-
s.message = err.response.data.message ||
|
|
69
|
-
s.severity =
|
|
75
|
+
ApplicationStore.update((s) => {
|
|
76
|
+
s.snackbar.open = true;
|
|
77
|
+
s.snackbar.message = err.response.data.message || 'Bad Request';
|
|
78
|
+
s.snackbar.severity = 'error';
|
|
70
79
|
});
|
|
71
80
|
}
|
|
72
81
|
if (
|
|
73
82
|
err.response.config.method &&
|
|
74
|
-
[
|
|
83
|
+
['put', 'post', 'delete', 'patch'].includes(err.response.config.method) &&
|
|
75
84
|
[404].includes(err.response.status)
|
|
76
85
|
) {
|
|
77
|
-
|
|
78
|
-
s.open = true;
|
|
79
|
-
s.message = err.response.data.message;
|
|
80
|
-
s.severity =
|
|
86
|
+
ApplicationStore.update((s) => {
|
|
87
|
+
s.snackbar.open = true;
|
|
88
|
+
s.snackbar.message = err.response.data.message;
|
|
89
|
+
s.snackbar.severity = 'error';
|
|
81
90
|
});
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
return Promise.reject(err);
|
|
85
|
-
}
|
|
94
|
+
},
|
|
86
95
|
);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ConfirmDialog, ConfirmDialogType } from '@campxdev/react-blueprint';
|
|
2
|
+
import { createContext, ReactNode } from 'react';
|
|
3
|
+
import { ApplicationStore } from './application-store';
|
|
4
|
+
|
|
5
|
+
interface ConfirmStateProps {
|
|
6
|
+
showConfirmDialog: (
|
|
7
|
+
title: string,
|
|
8
|
+
message: string,
|
|
9
|
+
onConfirm: () => void,
|
|
10
|
+
onCancel: () => void,
|
|
11
|
+
type?: ConfirmDialogType,
|
|
12
|
+
) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ConfirmContext = createContext<ConfirmStateProps>({
|
|
16
|
+
showConfirmDialog: () => {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const ConfirmDialogProvider = ({
|
|
20
|
+
children,
|
|
21
|
+
}: {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}) => {
|
|
24
|
+
const confirmDialog = ApplicationStore.useState((s) => s.confirmDialog);
|
|
25
|
+
|
|
26
|
+
const showConfirmDialog = (
|
|
27
|
+
title: string,
|
|
28
|
+
message: string,
|
|
29
|
+
onConfirm: () => void,
|
|
30
|
+
onCancel: () => void,
|
|
31
|
+
type: ConfirmDialogType = 'confirm',
|
|
32
|
+
) => {
|
|
33
|
+
ApplicationStore.update((s) => {
|
|
34
|
+
s.confirmDialog = {
|
|
35
|
+
isOpen: true,
|
|
36
|
+
title,
|
|
37
|
+
message,
|
|
38
|
+
onConfirm,
|
|
39
|
+
onCancel,
|
|
40
|
+
type,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleCloseDialog = () => {
|
|
46
|
+
ApplicationStore.update((s) => {
|
|
47
|
+
s.confirmDialog.isOpen = false;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<ConfirmContext.Provider value={{ showConfirmDialog }}>
|
|
53
|
+
{children}
|
|
54
|
+
<ConfirmDialog
|
|
55
|
+
isOpen={confirmDialog.isOpen}
|
|
56
|
+
title={confirmDialog.title}
|
|
57
|
+
message={confirmDialog.message}
|
|
58
|
+
type={confirmDialog.type}
|
|
59
|
+
onConfirm={() => {
|
|
60
|
+
confirmDialog.onConfirm();
|
|
61
|
+
handleCloseDialog();
|
|
62
|
+
}}
|
|
63
|
+
onCancel={() => {
|
|
64
|
+
confirmDialog.onCancel();
|
|
65
|
+
handleCloseDialog();
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</ConfirmContext.Provider>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -5,34 +5,34 @@ import {
|
|
|
5
5
|
NoInterneConnection,
|
|
6
6
|
PageNotFound,
|
|
7
7
|
UnAuthorized,
|
|
8
|
-
} from
|
|
9
|
-
import { Alert, Box, styled } from
|
|
10
|
-
import Cookies from
|
|
11
|
-
import { ReactNode, useState } from
|
|
12
|
-
import { ErrorBoundary as ReactErrorBoundary } from
|
|
13
|
-
import { QueryErrorResetBoundary } from
|
|
14
|
-
import { useLocation, useNavigate } from
|
|
15
|
-
import { Login } from
|
|
8
|
+
} from '@campxdev/react-blueprint';
|
|
9
|
+
import { Alert, Box, styled } from '@mui/material';
|
|
10
|
+
import Cookies from 'js-cookie';
|
|
11
|
+
import { ReactNode, useState } from 'react';
|
|
12
|
+
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
|
|
13
|
+
import { QueryErrorResetBoundary } from 'react-query';
|
|
14
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
15
|
+
import { Login } from './Login';
|
|
16
16
|
|
|
17
17
|
export type ErrorBoundaryProps = {
|
|
18
18
|
children: ReactNode;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
22
|
-
height:
|
|
22
|
+
height: '60px',
|
|
23
23
|
border: `1px solid ${theme.palette.error.main}`,
|
|
24
|
-
display:
|
|
25
|
-
alignItems:
|
|
26
|
-
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
'& .MuiAlert-message': {
|
|
27
27
|
padding: 0,
|
|
28
28
|
},
|
|
29
|
-
|
|
29
|
+
'& .MuiTypography-root': {
|
|
30
30
|
margin: 0,
|
|
31
31
|
},
|
|
32
|
-
position:
|
|
33
|
-
|
|
34
|
-
color:
|
|
35
|
-
position:
|
|
32
|
+
position: 'relative',
|
|
33
|
+
'& .retryBtn': {
|
|
34
|
+
color: '#661B2A',
|
|
35
|
+
position: 'absolute',
|
|
36
36
|
right: 8,
|
|
37
37
|
top: 8,
|
|
38
38
|
},
|
|
@@ -68,12 +68,12 @@ const ErrorFallback = ({ error, resetErrorBoundary }: any) => {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
if (error?.code ===
|
|
71
|
+
if (error?.code === 'ERR_NETWORK') {
|
|
72
72
|
return <NoInterneConnection resetBoundary={resetErrorBoundary} />;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
|
-
<Box sx={{ marginTop:
|
|
76
|
+
<Box sx={{ marginTop: '16px', padding: '20px' }}>
|
|
77
77
|
<StyledAlert severity="error">
|
|
78
78
|
{error?.response?.data?.message ?? error?.message}
|
|
79
79
|
<Button
|
|
@@ -94,14 +94,14 @@ const UnAuth = () => {
|
|
|
94
94
|
const navigate = useNavigate();
|
|
95
95
|
const [isModalOpen, setModalOpen] = useState(false);
|
|
96
96
|
|
|
97
|
-
const sessionCookie = Cookies.get(
|
|
97
|
+
const sessionCookie = Cookies.get('campx_session_key');
|
|
98
98
|
|
|
99
99
|
const handleLoginClick = () => {
|
|
100
|
-
if (window.location.hostname
|
|
100
|
+
if (window.location.hostname === 'localhost') {
|
|
101
101
|
setModalOpen(true);
|
|
102
102
|
} else {
|
|
103
103
|
if (!sessionCookie) {
|
|
104
|
-
navigate(
|
|
104
|
+
navigate('/auth/login');
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
};
|
|
@@ -112,7 +112,7 @@ const UnAuth = () => {
|
|
|
112
112
|
<>
|
|
113
113
|
<Button
|
|
114
114
|
sx={{
|
|
115
|
-
marginTop:
|
|
115
|
+
marginTop: '20px',
|
|
116
116
|
}}
|
|
117
117
|
variant="contained"
|
|
118
118
|
onClick={handleLoginClick}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { axios } from
|
|
2
|
-
import { Button, Icons, TextField } from
|
|
3
|
-
import { yupResolver } from
|
|
4
|
-
import { Box, Stack } from
|
|
5
|
-
import DeviceDetector from
|
|
6
|
-
import Cookies from
|
|
7
|
-
import { useEffect, useState } from
|
|
8
|
-
import { Controller, useForm } from
|
|
9
|
-
import { useMutation } from
|
|
1
|
+
import { axios } from '@campxdev/campx-web-utils';
|
|
2
|
+
import { Button, Icons, TextField } from '@campxdev/react-blueprint';
|
|
3
|
+
import { yupResolver } from '@hookform/resolvers/yup';
|
|
4
|
+
import { Box, Stack } from '@mui/material';
|
|
5
|
+
import DeviceDetector from 'device-detector-js';
|
|
6
|
+
import Cookies from 'js-cookie';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
9
|
+
import { useMutation } from 'react-query';
|
|
10
10
|
|
|
11
|
-
import * as Yup from
|
|
11
|
+
import * as Yup from 'yup';
|
|
12
12
|
|
|
13
13
|
type DeviceInformation = {
|
|
14
14
|
deviceType: string;
|
|
@@ -33,16 +33,16 @@ type LoginDetails = {
|
|
|
33
33
|
const performLogin = async (body: any) => {
|
|
34
34
|
if (!body.latitude || !body.longitude) {
|
|
35
35
|
const response = await fetch(
|
|
36
|
-
|
|
36
|
+
'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyB2YCpo1yi107RYj1LdZu2DCcpcO93reFY',
|
|
37
37
|
{
|
|
38
|
-
method:
|
|
38
|
+
method: 'POST',
|
|
39
39
|
headers: {
|
|
40
|
-
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
41
|
},
|
|
42
42
|
body: JSON.stringify({
|
|
43
|
-
considerIp:
|
|
43
|
+
considerIp: 'true',
|
|
44
44
|
}),
|
|
45
|
-
}
|
|
45
|
+
},
|
|
46
46
|
);
|
|
47
47
|
const data = await response.json();
|
|
48
48
|
body.latitude = data.location.lat;
|
|
@@ -52,20 +52,20 @@ const performLogin = async (body: any) => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const validationSchema = Yup.object().shape({
|
|
55
|
-
username: Yup.string().required(
|
|
56
|
-
password: Yup.string().required(
|
|
55
|
+
username: Yup.string().required('Username is required'),
|
|
56
|
+
password: Yup.string().required('Password is required'),
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
export const Login = ({ close }: { close: () => void }) => {
|
|
60
60
|
const [deviceState, setDeviceState] = useState<DeviceState>({
|
|
61
61
|
deviceInformation: {
|
|
62
|
-
deviceType:
|
|
63
|
-
clientName:
|
|
64
|
-
os:
|
|
65
|
-
osVersion:
|
|
62
|
+
deviceType: 'browser',
|
|
63
|
+
clientName: 'unknown',
|
|
64
|
+
os: 'unknown',
|
|
65
|
+
osVersion: 'unknown',
|
|
66
66
|
latitude: null,
|
|
67
67
|
longitude: null,
|
|
68
|
-
tokenType:
|
|
68
|
+
tokenType: 'WEB',
|
|
69
69
|
},
|
|
70
70
|
isLocationAllowed: true,
|
|
71
71
|
});
|
|
@@ -82,9 +82,9 @@ export const Login = ({ close }: { close: () => void }) => {
|
|
|
82
82
|
}));
|
|
83
83
|
});
|
|
84
84
|
navigator.permissions
|
|
85
|
-
.query({ name:
|
|
85
|
+
.query({ name: 'geolocation' })
|
|
86
86
|
.then((permissionStatus) => {
|
|
87
|
-
if (permissionStatus.state ===
|
|
87
|
+
if (permissionStatus.state === 'denied') {
|
|
88
88
|
setDeviceState((s) => ({
|
|
89
89
|
...s,
|
|
90
90
|
isLocationAllowed: false,
|
|
@@ -109,17 +109,19 @@ export const Login = ({ close }: { close: () => void }) => {
|
|
|
109
109
|
handleSubmit,
|
|
110
110
|
control,
|
|
111
111
|
formState: { errors },
|
|
112
|
-
} = useForm<LoginDetails>({
|
|
112
|
+
} = useForm<LoginDetails>({
|
|
113
|
+
resolver: yupResolver<LoginDetails>(validationSchema),
|
|
114
|
+
});
|
|
113
115
|
|
|
114
116
|
const { mutate, isLoading } = useMutation(performLogin, {
|
|
115
117
|
onSuccess(data) {
|
|
116
118
|
if (data.loginSuccessful) {
|
|
117
|
-
Cookies.remove(
|
|
118
|
-
Cookies.remove(
|
|
119
|
-
Cookies.remove(
|
|
120
|
-
Cookies.set(
|
|
121
|
-
Cookies.set(
|
|
122
|
-
Cookies.set(
|
|
119
|
+
Cookies.remove('campx_session_key');
|
|
120
|
+
Cookies.remove('campx_tenant');
|
|
121
|
+
Cookies.remove('campx_institution');
|
|
122
|
+
Cookies.set('campx_session_key', data?.token);
|
|
123
|
+
Cookies.set('campx_tenant', data?.subDomain);
|
|
124
|
+
Cookies.set('campx_institution', data?.institutionCode);
|
|
123
125
|
close();
|
|
124
126
|
window.location.reload();
|
|
125
127
|
}
|
|
@@ -145,10 +147,10 @@ export const Login = ({ close }: { close: () => void }) => {
|
|
|
145
147
|
size="medium"
|
|
146
148
|
name="username"
|
|
147
149
|
sx={{
|
|
148
|
-
width:
|
|
150
|
+
width: '400px',
|
|
149
151
|
}}
|
|
150
152
|
placeholder="Enter Username or Email"
|
|
151
|
-
containerProps={{ my:
|
|
153
|
+
containerProps={{ my: '0' }}
|
|
152
154
|
onChange={field.onChange}
|
|
153
155
|
label="Username or Email"
|
|
154
156
|
error={!!errors.username}
|
|
@@ -166,11 +168,11 @@ export const Login = ({ close }: { close: () => void }) => {
|
|
|
166
168
|
size="medium"
|
|
167
169
|
name="password"
|
|
168
170
|
sx={{
|
|
169
|
-
width:
|
|
171
|
+
width: '400px',
|
|
170
172
|
}}
|
|
171
173
|
placeholder="Enter password"
|
|
172
174
|
onChange={field.onChange}
|
|
173
|
-
containerProps={{ my:
|
|
175
|
+
containerProps={{ my: '0' }}
|
|
174
176
|
label="Password"
|
|
175
177
|
type="password"
|
|
176
178
|
error={!!errors.password}
|
|
@@ -185,7 +187,7 @@ export const Login = ({ close }: { close: () => void }) => {
|
|
|
185
187
|
color="primary"
|
|
186
188
|
variant="contained"
|
|
187
189
|
sx={{
|
|
188
|
-
width:
|
|
190
|
+
width: '400px',
|
|
189
191
|
}}
|
|
190
192
|
onClick={handleSubmit(onSubmit)}
|
|
191
193
|
disabled={isLoading}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { lightTheme, MuiThemeProvider } from
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { SnackbarProvider } from "./SnackbarProvider";
|
|
1
|
+
import { lightTheme, MuiThemeProvider } from '@campxdev/react-blueprint';
|
|
2
|
+
import Cookies from 'js-cookie';
|
|
3
|
+
import { ReactNode, useEffect } from 'react';
|
|
4
|
+
import { QueryClient, QueryClientProvider } from 'react-query';
|
|
5
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
6
|
+
import { ConfirmDialogProvider } from './ConfirmDialogProvider';
|
|
7
|
+
import { ErrorBoundary } from './export';
|
|
8
|
+
import { SnackbarProvider } from './SnackbarProvider';
|
|
10
9
|
|
|
11
10
|
export const Providers = ({
|
|
12
11
|
children,
|
|
@@ -28,28 +27,28 @@ export const Providers = ({
|
|
|
28
27
|
});
|
|
29
28
|
|
|
30
29
|
var tenantCode =
|
|
31
|
-
window.location.hostname ===
|
|
32
|
-
? Cookies.get(
|
|
33
|
-
: window.location.hostname.split(
|
|
30
|
+
window.location.hostname === 'localhost'
|
|
31
|
+
? Cookies.get('campx_tenant')
|
|
32
|
+
: window.location.hostname.split('.')[0];
|
|
34
33
|
var institutionCode =
|
|
35
|
-
window.location.pathname.split(
|
|
36
|
-
? window.location.pathname.split(
|
|
37
|
-
: Cookies.get(
|
|
38
|
-
var baseName = tenantCode && institutionCode ? `/${institutionCode}` :
|
|
34
|
+
window.location.pathname.split('/')[1] !== ''
|
|
35
|
+
? window.location.pathname.split('/')[1]
|
|
36
|
+
: Cookies.get('campx_institution');
|
|
37
|
+
var baseName = tenantCode && institutionCode ? `/${institutionCode}` : '/';
|
|
39
38
|
useEffect(() => {
|
|
40
|
-
if (window.location.pathname ===
|
|
39
|
+
if (window.location.pathname === '/' && institutionCode && tenantCode) {
|
|
41
40
|
window.location.replace(window.location.origin + `/${institutionCode}`);
|
|
42
41
|
}
|
|
43
|
-
}, []);
|
|
42
|
+
}, [institutionCode, tenantCode]);
|
|
44
43
|
|
|
45
44
|
return (
|
|
46
45
|
<BrowserRouter basename={basename ?? baseName}>
|
|
47
46
|
<QueryClientProvider client={queryClient}>
|
|
48
47
|
<MuiThemeProvider theme={theme}>
|
|
49
48
|
<SnackbarProvider>
|
|
50
|
-
<
|
|
49
|
+
<ConfirmDialogProvider>
|
|
51
50
|
<ErrorBoundary>{children}</ErrorBoundary>
|
|
52
|
-
</
|
|
51
|
+
</ConfirmDialogProvider>
|
|
53
52
|
</SnackbarProvider>
|
|
54
53
|
</MuiThemeProvider>
|
|
55
54
|
</QueryClientProvider>
|
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
import { Severity, Snackbar } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Severity, Snackbar } from '@campxdev/react-blueprint';
|
|
2
|
+
import { createContext, ReactNode } from 'react';
|
|
3
|
+
import { ApplicationStore } from './application-store';
|
|
4
4
|
|
|
5
5
|
interface SnackbarContextProps {
|
|
6
6
|
showSnackbar: (message: string, severity?: Severity) => void;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const SnackbarContext = createContext<SnackbarContextProps | undefined>(
|
|
10
|
-
undefined
|
|
10
|
+
undefined,
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
interface SnackbarProviderProps {
|
|
14
14
|
children: ReactNode;
|
|
15
15
|
}
|
|
16
|
-
export const SnackbarStore = new Store({
|
|
17
|
-
open: false,
|
|
18
|
-
message: "",
|
|
19
|
-
severity: "success" as Severity,
|
|
20
|
-
});
|
|
21
16
|
|
|
22
17
|
export const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
|
|
23
|
-
const snackbar =
|
|
24
|
-
const showSnackbar = (message: string, severity: Severity =
|
|
25
|
-
|
|
26
|
-
s.open = true;
|
|
27
|
-
s.message = message;
|
|
28
|
-
s.severity = severity;
|
|
18
|
+
const snackbar = ApplicationStore.useState((s) => s.snackbar);
|
|
19
|
+
const showSnackbar = (message: string, severity: Severity = 'info') => {
|
|
20
|
+
ApplicationStore.update((s) => {
|
|
21
|
+
s.snackbar.open = true;
|
|
22
|
+
s.snackbar.message = message;
|
|
23
|
+
s.snackbar.severity = severity;
|
|
29
24
|
});
|
|
30
25
|
};
|
|
31
26
|
return (
|
|
@@ -37,8 +32,8 @@ export const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
|
|
|
37
32
|
severity={snackbar.severity}
|
|
38
33
|
autoHideDuration={1500}
|
|
39
34
|
onClose={() => {
|
|
40
|
-
|
|
41
|
-
s.open = false;
|
|
35
|
+
ApplicationStore.update((s) => {
|
|
36
|
+
s.snackbar.open = false;
|
|
42
37
|
});
|
|
43
38
|
}}
|
|
44
39
|
/>
|
|
@@ -1,12 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConfirmDialogType, Severity } from '@campxdev/react-blueprint';
|
|
2
|
+
import { Store } from 'pullstate';
|
|
2
3
|
|
|
3
4
|
export type ApplicationStoreType = {
|
|
4
5
|
isLoginDialogOpen: boolean;
|
|
6
|
+
snackbar: {
|
|
7
|
+
open: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
severity: Severity;
|
|
10
|
+
};
|
|
11
|
+
confirmDialog: {
|
|
12
|
+
isOpen: boolean;
|
|
13
|
+
title: string;
|
|
14
|
+
message: string;
|
|
15
|
+
onConfirm: () => void;
|
|
16
|
+
onCancel: () => void;
|
|
17
|
+
type: ConfirmDialogType;
|
|
18
|
+
};
|
|
5
19
|
};
|
|
6
20
|
|
|
7
21
|
export const initialApplicationState: ApplicationStoreType = {
|
|
8
22
|
isLoginDialogOpen: false,
|
|
23
|
+
snackbar: {
|
|
24
|
+
open: false,
|
|
25
|
+
message: '',
|
|
26
|
+
severity: 'success',
|
|
27
|
+
},
|
|
28
|
+
confirmDialog: {
|
|
29
|
+
isOpen: false,
|
|
30
|
+
title: '',
|
|
31
|
+
message: '',
|
|
32
|
+
onConfirm: () => {},
|
|
33
|
+
onCancel: () => {},
|
|
34
|
+
type: 'confirm',
|
|
35
|
+
},
|
|
9
36
|
};
|
|
10
37
|
export const ApplicationStore = new Store<ApplicationStoreType>(
|
|
11
|
-
initialApplicationState
|
|
38
|
+
initialApplicationState,
|
|
12
39
|
);
|
package/src/context/export.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './application-store';
|
|
2
|
+
export * from './ErrorBoundary/ErrorBoundary';
|
|
3
|
+
export * from './Providers';
|
|
4
|
+
export * from './SnackbarProvider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useConfirm';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ConfirmDialogType } from '@campxdev/react-blueprint';
|
|
2
|
+
import { ApplicationStore } from '../context/application-store';
|
|
3
|
+
|
|
4
|
+
const defaultConfirmDialogState = {
|
|
5
|
+
isOpen: false,
|
|
6
|
+
title: '',
|
|
7
|
+
message: '',
|
|
8
|
+
type: 'confirm' as ConfirmDialogType,
|
|
9
|
+
onConfirm: () => {},
|
|
10
|
+
onCancel: () => {},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const useConfirm = () => {
|
|
14
|
+
const isConfirmed = ({
|
|
15
|
+
title,
|
|
16
|
+
message,
|
|
17
|
+
type = 'confirm',
|
|
18
|
+
}: {
|
|
19
|
+
title: string;
|
|
20
|
+
message: string;
|
|
21
|
+
type?: ConfirmDialogType;
|
|
22
|
+
}): Promise<boolean> =>
|
|
23
|
+
new Promise((resolve) => {
|
|
24
|
+
ApplicationStore.update((s) => {
|
|
25
|
+
s.confirmDialog = {
|
|
26
|
+
isOpen: true,
|
|
27
|
+
title,
|
|
28
|
+
message,
|
|
29
|
+
type,
|
|
30
|
+
onConfirm: () => {
|
|
31
|
+
resolve(true);
|
|
32
|
+
resetDialog();
|
|
33
|
+
},
|
|
34
|
+
onCancel: () => {
|
|
35
|
+
resolve(false);
|
|
36
|
+
resetDialog();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const resetDialog = () => {
|
|
43
|
+
ApplicationStore.update((s) => {
|
|
44
|
+
s.confirmDialog = defaultConfirmDialogState;
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return isConfirmed;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default useConfirm;
|
package/src/index.tsx
CHANGED
|
@@ -5,12 +5,12 @@ import App from './App';
|
|
|
5
5
|
import reportWebVitals from './reportWebVitals';
|
|
6
6
|
|
|
7
7
|
const root = ReactDOM.createRoot(
|
|
8
|
-
document.getElementById('root') as HTMLElement
|
|
8
|
+
document.getElementById('root') as HTMLElement,
|
|
9
9
|
);
|
|
10
10
|
root.render(
|
|
11
11
|
<React.StrictMode>
|
|
12
12
|
<App />
|
|
13
|
-
</React.StrictMode
|
|
13
|
+
</React.StrictMode>,
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
// If you want to start measuring performance in your app, pass a function
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const getBreadcrumbsCharacter = () => {
|
|
2
|
+
const unicodeChar = String.fromCharCode(0x21e5);
|
|
3
|
+
const testElement = document.createElement('span');
|
|
4
|
+
testElement.innerHTML = unicodeChar;
|
|
5
|
+
document.body.appendChild(testElement);
|
|
6
|
+
const isRendered = testElement.offsetWidth > 0;
|
|
7
|
+
document.body.removeChild(testElement);
|
|
8
|
+
|
|
9
|
+
console.log(isRendered);
|
|
10
|
+
|
|
11
|
+
const fallbackChar = '~~';
|
|
12
|
+
const s = isRendered ? unicodeChar : fallbackChar;
|
|
13
|
+
|
|
14
|
+
return s;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface GetUrlSegment {
|
|
18
|
+
name: string;
|
|
19
|
+
id: string | number;
|
|
20
|
+
}
|
|
21
|
+
export const getUrlSegment = ({ name, id }: GetUrlSegment) => {
|
|
22
|
+
return `${name}${getBreadcrumbsCharacter()}${id}`;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './breadcrumbs';
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "es5",
|
|
4
|
-
"lib": [
|
|
5
|
-
"dom",
|
|
6
|
-
"dom.iterable",
|
|
7
|
-
"esnext"
|
|
8
|
-
],
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
9
5
|
"allowJs": true,
|
|
10
6
|
"skipLibCheck": true,
|
|
11
7
|
"esModuleInterop": true,
|
|
@@ -18,13 +14,9 @@
|
|
|
18
14
|
"resolveJsonModule": true,
|
|
19
15
|
"isolatedModules": true,
|
|
20
16
|
"noEmit": true,
|
|
21
|
-
"jsx": "react-jsx"
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"types": ["node"]
|
|
22
19
|
},
|
|
23
|
-
"include": [
|
|
24
|
-
|
|
25
|
-
"./types"
|
|
26
|
-
],
|
|
27
|
-
"exclude": [
|
|
28
|
-
"node_modules"
|
|
29
|
-
]
|
|
20
|
+
"include": ["src", "./types"],
|
|
21
|
+
"exclude": ["node_modules", ".yalc"]
|
|
30
22
|
}
|
package/exports.ts
DELETED