@campxdev/campx-web-utils 0.1.9 → 0.1.11
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/craco.config.js +0 -3
- package/exports.ts +1 -0
- package/package.json +7 -3
- package/src/ErrorBoundary/ErrorBoundary.tsx +27 -11
- package/src/ErrorBoundary/Login.tsx +194 -0
- package/src/config/axios.ts +2 -2
- package/src/context/SnackbarProvider.tsx +6 -8
package/craco.config.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
|
|
2
|
-
|
|
3
1
|
const path = require("path");
|
|
4
|
-
const deps = require("./package.json").dependencies;
|
|
5
2
|
const { getLoader, loaderByName } = require("@craco/craco");
|
|
6
3
|
const packages = [];
|
|
7
4
|
packages.push(path.dirname(require.resolve("@campxdev/react-blueprint")));
|
package/exports.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campxdev/campx-web-utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"main": "./exports.ts",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@campxdev/react-blueprint": "^1.
|
|
7
|
+
"@campxdev/react-blueprint": "^1.1.2",
|
|
8
|
+
"@hookform/resolvers": "^3.9.0",
|
|
8
9
|
"@mui/x-date-pickers": "^7.11.0",
|
|
9
10
|
"@testing-library/jest-dom": "^5.14.1",
|
|
10
11
|
"@testing-library/react": "^13.0.0",
|
|
@@ -15,14 +16,17 @@
|
|
|
15
16
|
"@types/react-dom": "^18.0.0",
|
|
16
17
|
"axios": "^1.7.2",
|
|
17
18
|
"cookie-js": "^0.0.1",
|
|
19
|
+
"device-detector-js": "^3.0.3",
|
|
18
20
|
"pullstate": "^1.25.0",
|
|
19
21
|
"react": "^18.3.1",
|
|
20
22
|
"react-dom": "^18.3.1",
|
|
23
|
+
"react-hook-form": "^7.52.1",
|
|
21
24
|
"react-query": "^3.39.3",
|
|
22
25
|
"react-scripts": "5.0.1",
|
|
23
26
|
"react-toastify": "^9.0.1",
|
|
24
27
|
"typescript": "^4.4.2",
|
|
25
|
-
"web-vitals": "^2.1.0"
|
|
28
|
+
"web-vitals": "^2.1.0",
|
|
29
|
+
"yup": "^1.4.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
CustomDialog,
|
|
4
|
+
InternalServerError,
|
|
5
|
+
NoInterneConnection,
|
|
6
|
+
PageNotFound,
|
|
7
|
+
UnAuthorized,
|
|
8
|
+
} from "@campxdev/react-blueprint";
|
|
5
9
|
import { Alert, Box, styled } from "@mui/material";
|
|
6
10
|
import Cookies from "js-cookie";
|
|
7
|
-
import { ReactNode } from "react";
|
|
11
|
+
import { ReactNode, useState } from "react";
|
|
8
12
|
import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary";
|
|
9
13
|
import { QueryErrorResetBoundary } from "react-query";
|
|
10
14
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
11
15
|
import { axios } from "../config/axios";
|
|
16
|
+
import { Login } from "./Login";
|
|
12
17
|
|
|
13
18
|
export type ErrorBoundaryProps = {
|
|
14
19
|
children: ReactNode;
|
|
@@ -53,14 +58,15 @@ export const ErrorBoundary = (props: ErrorBoundaryProps) => {
|
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
export const ErrorFallback = ({ error, resetErrorBoundary }: any) => {
|
|
56
|
-
console.log(error, "llll");
|
|
57
61
|
if (error?.response?.status) {
|
|
58
62
|
switch (error?.response?.status) {
|
|
59
63
|
case 401:
|
|
60
|
-
return <UnAuth
|
|
64
|
+
return <UnAuth />;
|
|
61
65
|
|
|
62
66
|
case 500:
|
|
63
67
|
return <InternalServerError resetBoundary={resetErrorBoundary} />;
|
|
68
|
+
case 404:
|
|
69
|
+
return <PageNotFound />;
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -86,8 +92,9 @@ export const ErrorFallback = ({ error, resetErrorBoundary }: any) => {
|
|
|
86
92
|
);
|
|
87
93
|
};
|
|
88
94
|
|
|
89
|
-
const UnAuth = (
|
|
95
|
+
const UnAuth = () => {
|
|
90
96
|
const navigate = useNavigate();
|
|
97
|
+
const [isModalOpen, setModalOpen] = useState(false);
|
|
91
98
|
|
|
92
99
|
const sessionCookie = Cookies.get("campx_session_key");
|
|
93
100
|
|
|
@@ -98,10 +105,14 @@ const UnAuth = ({ resetBoundary }: any) => {
|
|
|
98
105
|
};
|
|
99
106
|
|
|
100
107
|
const handleLoginClick = () => {
|
|
101
|
-
if (
|
|
102
|
-
|
|
108
|
+
if (window.location.hostname == "localhost") {
|
|
109
|
+
setModalOpen(true);
|
|
103
110
|
} else {
|
|
104
|
-
|
|
111
|
+
if (!sessionCookie) {
|
|
112
|
+
navigate("/auth/login");
|
|
113
|
+
} else {
|
|
114
|
+
appinit();
|
|
115
|
+
}
|
|
105
116
|
}
|
|
106
117
|
};
|
|
107
118
|
|
|
@@ -118,6 +129,11 @@ const UnAuth = ({ resetBoundary }: any) => {
|
|
|
118
129
|
>
|
|
119
130
|
Click Here To Login
|
|
120
131
|
</Button>
|
|
132
|
+
<CustomDialog
|
|
133
|
+
open={isModalOpen}
|
|
134
|
+
onClose={() => setModalOpen(false)}
|
|
135
|
+
content={({ close }) => <Login close={close} />}
|
|
136
|
+
/>
|
|
121
137
|
</>
|
|
122
138
|
}
|
|
123
139
|
/>
|
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
|
|
11
|
+
import * as Yup from "yup";
|
|
12
|
+
|
|
13
|
+
type DeviceInformation = {
|
|
14
|
+
deviceType: string;
|
|
15
|
+
clientName: string;
|
|
16
|
+
os: string;
|
|
17
|
+
osVersion: string;
|
|
18
|
+
latitude: number | null;
|
|
19
|
+
longitude: number | null;
|
|
20
|
+
tokenType: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type DeviceState = {
|
|
24
|
+
deviceInformation: DeviceInformation;
|
|
25
|
+
isLocationAllowed: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const performLogin = async (body: any) => {
|
|
29
|
+
if (!body.latitude || !body.longitude) {
|
|
30
|
+
const response = await fetch(
|
|
31
|
+
"https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyB2YCpo1yi107RYj1LdZu2DCcpcO93reFY",
|
|
32
|
+
{
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
considerIp: "true",
|
|
39
|
+
}),
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
body.latitude = data.location.lat;
|
|
44
|
+
body.longitude = data.location.lng;
|
|
45
|
+
}
|
|
46
|
+
return axios.post(`/auth-server/auth/login`, body).then((res) => res.data);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const validationSchema = Yup.object().shape({
|
|
50
|
+
username: Yup.string().required("Username is required"),
|
|
51
|
+
password: Yup.string().required("Password is required"),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const Login = ({ close }: { close: () => void }) => {
|
|
55
|
+
const [deviceState, setDeviceState] = useState<DeviceState>({
|
|
56
|
+
deviceInformation: {
|
|
57
|
+
deviceType: "browser",
|
|
58
|
+
clientName: "unknown",
|
|
59
|
+
os: "unknown",
|
|
60
|
+
osVersion: "unknown",
|
|
61
|
+
latitude: null,
|
|
62
|
+
longitude: null,
|
|
63
|
+
tokenType: "WEB",
|
|
64
|
+
},
|
|
65
|
+
isLocationAllowed: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
70
|
+
setDeviceState((s) => ({
|
|
71
|
+
...s,
|
|
72
|
+
deviceInformation: {
|
|
73
|
+
...s.deviceInformation,
|
|
74
|
+
latitude: position.coords.latitude,
|
|
75
|
+
longitude: position.coords.longitude,
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
});
|
|
79
|
+
navigator.permissions
|
|
80
|
+
.query({ name: "geolocation" })
|
|
81
|
+
.then((permissionStatus) => {
|
|
82
|
+
if (permissionStatus.state === "denied") {
|
|
83
|
+
setDeviceState((s) => ({
|
|
84
|
+
...s,
|
|
85
|
+
isLocationAllowed: false,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const detector = new DeviceDetector();
|
|
91
|
+
const deviceInfo = detector.parse(navigator.userAgent);
|
|
92
|
+
setDeviceState((s) => ({
|
|
93
|
+
...s,
|
|
94
|
+
deviceInformation: {
|
|
95
|
+
...s.deviceInformation,
|
|
96
|
+
clientName: deviceInfo.client?.name || s.deviceInformation.clientName,
|
|
97
|
+
os: deviceInfo.os?.name || s.deviceInformation.os,
|
|
98
|
+
osVersion: deviceInfo.os?.version || s.deviceInformation.osVersion,
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const {
|
|
104
|
+
handleSubmit,
|
|
105
|
+
control,
|
|
106
|
+
formState: { errors },
|
|
107
|
+
} = useForm({ resolver: yupResolver(validationSchema) });
|
|
108
|
+
|
|
109
|
+
const { mutate, isLoading } = useMutation(performLogin, {
|
|
110
|
+
onSuccess(data) {
|
|
111
|
+
if (data.loginSuccessful) {
|
|
112
|
+
Cookies.remove("campx_session_key");
|
|
113
|
+
Cookies.remove("campx_tenant");
|
|
114
|
+
Cookies.remove("campx_institution");
|
|
115
|
+
Cookies.set("campx_session_key", data?.token);
|
|
116
|
+
Cookies.set("campx_tenant", data?.subDomain);
|
|
117
|
+
Cookies.set("campx_institution", data?.institutionCode);
|
|
118
|
+
close();
|
|
119
|
+
window.location.reload();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
const onSubmit = async (body: any) => {
|
|
124
|
+
mutate({ ...body, ...deviceState.deviceInformation });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
<Stack gap="16px" justifyContent="space-between" alignItems="center">
|
|
130
|
+
<Box height="36px" width="194.8px">
|
|
131
|
+
<Icons.CampxFullLogoIcon />
|
|
132
|
+
</Box>
|
|
133
|
+
<Stack gap="10px" justifyContent="center" alignItems="center">
|
|
134
|
+
<Controller
|
|
135
|
+
control={control}
|
|
136
|
+
render={({ field }: { field: any }) => {
|
|
137
|
+
return (
|
|
138
|
+
<TextField
|
|
139
|
+
size="medium"
|
|
140
|
+
name="username"
|
|
141
|
+
sx={{
|
|
142
|
+
width: "400px",
|
|
143
|
+
}}
|
|
144
|
+
placeholder="Enter Username or Email"
|
|
145
|
+
containerProps={{ my: "0" }}
|
|
146
|
+
onChange={field.onChange}
|
|
147
|
+
label="Username or Email"
|
|
148
|
+
error={!!errors.username}
|
|
149
|
+
required
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}}
|
|
153
|
+
name="username"
|
|
154
|
+
/>
|
|
155
|
+
<Controller
|
|
156
|
+
control={control}
|
|
157
|
+
render={({ field }: { field: any }) => {
|
|
158
|
+
return (
|
|
159
|
+
<TextField
|
|
160
|
+
size="medium"
|
|
161
|
+
name="password"
|
|
162
|
+
sx={{
|
|
163
|
+
width: "400px",
|
|
164
|
+
}}
|
|
165
|
+
placeholder="Enter password"
|
|
166
|
+
onChange={field.onChange}
|
|
167
|
+
containerProps={{ my: "0" }}
|
|
168
|
+
label="Password"
|
|
169
|
+
type="password"
|
|
170
|
+
error={!!errors.password}
|
|
171
|
+
required
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}}
|
|
175
|
+
name="password"
|
|
176
|
+
/>
|
|
177
|
+
<Button
|
|
178
|
+
type="submit"
|
|
179
|
+
color="primary"
|
|
180
|
+
variant="contained"
|
|
181
|
+
sx={{
|
|
182
|
+
width: "400px",
|
|
183
|
+
}}
|
|
184
|
+
onClick={handleSubmit(onSubmit)}
|
|
185
|
+
disabled={isLoading}
|
|
186
|
+
loading={isLoading}
|
|
187
|
+
>
|
|
188
|
+
Login
|
|
189
|
+
</Button>
|
|
190
|
+
</Stack>
|
|
191
|
+
</Stack>
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
};
|
package/src/config/axios.ts
CHANGED
|
@@ -63,7 +63,7 @@ axios.interceptors.response.use(
|
|
|
63
63
|
SnackbarStore.update((s) => {
|
|
64
64
|
s.open = true;
|
|
65
65
|
s.message = response.data.message;
|
|
66
|
-
s.
|
|
66
|
+
s.severity = "success";
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -74,7 +74,7 @@ axios.interceptors.response.use(
|
|
|
74
74
|
SnackbarStore.update((s) => {
|
|
75
75
|
s.open = true;
|
|
76
76
|
s.message = err.response.data.message || "Bad Request";
|
|
77
|
-
s.
|
|
77
|
+
s.severity = "error";
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { Snackbar } from "@campxdev/react-blueprint";
|
|
1
|
+
import { Severity, Snackbar } from "@campxdev/react-blueprint";
|
|
2
2
|
import { Store } from "pullstate";
|
|
3
3
|
import { createContext, ReactNode } from "react";
|
|
4
4
|
|
|
5
|
-
type Variant = "success" | "info" | "warning" | "alert";
|
|
6
|
-
|
|
7
5
|
interface SnackbarContextProps {
|
|
8
|
-
showSnackbar: (message: string,
|
|
6
|
+
showSnackbar: (message: string, severity?: Severity) => void;
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
const SnackbarContext = createContext<SnackbarContextProps | undefined>(
|
|
@@ -18,16 +16,16 @@ interface SnackbarProviderProps {
|
|
|
18
16
|
export const SnackbarStore = new Store({
|
|
19
17
|
open: false,
|
|
20
18
|
message: "",
|
|
21
|
-
|
|
19
|
+
severity: "success" as Severity,
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
export const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
|
|
25
23
|
const snackbar = SnackbarStore.useState((s) => s);
|
|
26
|
-
const showSnackbar = (message: string,
|
|
24
|
+
const showSnackbar = (message: string, severity: Severity = "info") => {
|
|
27
25
|
SnackbarStore.update((s) => {
|
|
28
26
|
s.open = true;
|
|
29
27
|
s.message = message;
|
|
30
|
-
s.
|
|
28
|
+
s.severity = severity;
|
|
31
29
|
});
|
|
32
30
|
};
|
|
33
31
|
return (
|
|
@@ -36,7 +34,7 @@ export const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
|
|
|
36
34
|
<Snackbar
|
|
37
35
|
open={snackbar.open}
|
|
38
36
|
message={snackbar.message}
|
|
39
|
-
|
|
37
|
+
severity={snackbar.severity}
|
|
40
38
|
autoHideDuration={1500}
|
|
41
39
|
onClose={() => {
|
|
42
40
|
SnackbarStore.update((s) => {
|