@asaleh37/ui-base 25.8.31 → 25.9.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaleh37/ui-base",
3
- "version": "25.8.31",
3
+ "version": "25.9.3",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Ahmed Saleh Mohamed",
@@ -113,7 +113,9 @@
113
113
  "stylis-plugin-rtl": "^2.1.1",
114
114
  "zod": "^3.24.2"
115
115
  },
116
- "dependencies": {
116
+ "dependencies": {
117
+ "@azure/msal-browser": "^4.21.1",
118
+ "@azure/msal-react": "^3.0.19",
117
119
  "@rollup/plugin-json": "^6.1.0",
118
120
  "rollup-plugin-terser": "^7.0.2"
119
121
  }
@@ -13,9 +13,18 @@ import {
13
13
  } from "../util";
14
14
  import { setStoresMetaData } from "../redux/features/common/CommonStoreSlice";
15
15
  import { ADMINISTRATION_STORES } from "../redux/features/administration/AdministrationStoresMetaData";
16
+ import { PublicClientApplication } from "@azure/msal-browser";
17
+ import { MsalProvider } from "@azure/msal-react";
16
18
 
17
19
  const App: React.FC<AppInfo> = (props: AppInfo) => {
18
20
  const dispatch = useDispatch();
21
+ const msalInstance = new PublicClientApplication({
22
+ auth: {
23
+ clientId: props?.azureConfiguration?.frontEndClientId,
24
+ authority: `https://login.microsoftonline.com/${props?.azureConfiguration?.tenantId}`,
25
+ redirectUri: props?.azureConfiguration?.redirectURL,
26
+ },
27
+ });
19
28
  LicenseInfo.setLicenseKey(props.muiPremiumKey);
20
29
  const LightThemeOptions: ThemeOptions = {
21
30
  components: {
@@ -115,7 +124,13 @@ const App: React.FC<AppInfo> = (props: AppInfo) => {
115
124
  }, [props]);
116
125
  return (
117
126
  <ThemeProvider theme={theme}>
118
- <Layout />
127
+ {props?.authenticationMethod === "AZURE" ? (
128
+ <MsalProvider instance={msalInstance}>
129
+ <Layout msalInstance={msalInstance} />
130
+ </MsalProvider>
131
+ ) : (
132
+ <Layout msalInstance={msalInstance} />
133
+ )}
119
134
  </ThemeProvider>
120
135
  );
121
136
  };
@@ -0,0 +1,70 @@
1
+ import { PublicClientApplication } from "@azure/msal-browser";
2
+ import { Box, Button } from "@mui/material";
3
+ import { useDispatch, useSelector } from "react-redux";
4
+ import { AppInfo } from "../../redux/features/common/AppInfoSlice";
5
+ import { useAxios } from "../../hooks";
6
+ import { UserSessionActions } from "../../redux/features/common/UserSessionSlice";
7
+ import { useEffect } from "react";
8
+
9
+ interface AzureLoginProps {
10
+ msalInstance: PublicClientApplication;
11
+ }
12
+
13
+ const AzureLogin: React.FC<AzureLoginProps> = ({ msalInstance }) => {
14
+ const appInfo: AppInfo = useSelector((state: any) => state.AppInfo.value);
15
+ const userSession = useSelector((state: any) => state.UserSession.value);
16
+ const { handleGetRequest } = useAxios();
17
+ const dispatch = useDispatch();
18
+ const checkUserSession = async () => {
19
+ if (appInfo?.apiBaseUrl) {
20
+ if (userSession.isAuthenticated == null) {
21
+ const token = localStorage.getItem("TOKEN");
22
+ handleGetRequest({
23
+ endPointURI: "api/auth/userInfo",
24
+ showMask: true,
25
+ successCallBkFn: (response) => {
26
+ if (response != null && response.data != null) {
27
+ const UserSession = {
28
+ ...response.data,
29
+ isAuthenticated: true,
30
+ };
31
+ dispatch(UserSessionActions.setAuthenticated(UserSession));
32
+ } else {
33
+ dispatch(UserSessionActions.setUnAuthenticated());
34
+ localStorage.removeItem("TOKEN");
35
+ }
36
+ },
37
+ });
38
+ }
39
+ }
40
+ };
41
+ const handleLogin = async () => {
42
+ const response = await msalInstance.acquireTokenPopup({
43
+ scopes: appInfo?.azureConfiguration?.scopes,
44
+ });
45
+ if (response?.accessToken) {
46
+ localStorage.setItem("TOKEN", response.accessToken);
47
+ checkUserSession();
48
+ }
49
+ };
50
+ const getUserInfo = async () => {
51
+ const response = await handleGetRequest({
52
+ endPointURI: "api/auth/userInfo",
53
+ showMask: true,
54
+ successCallBkFn: (response) => {
55
+ console.log(response);
56
+ },
57
+ });
58
+ };
59
+
60
+ // useEffect(()=>{
61
+ // handleLogin()
62
+ // },[])
63
+ return (
64
+ <Box>
65
+ <Button onClick={handleLogin}>Azure Login </Button>
66
+ </Box>
67
+ );
68
+ };
69
+
70
+ export default AzureLogin;
@@ -16,10 +16,13 @@ import { UserSessionActions } from "../../redux/features/common/UserSessionSlice
16
16
  import {
17
17
  DARK_THEME_INITIAL_MAIN_COLOR,
18
18
  DARK_THEME_INITIAL_SECANDARY_COLOR,
19
+ LIGHT_THEME_INITIAL_MAIN_COLOR,
20
+ LIGHT_THEME_INITIAL_SECANDARY_COLOR,
19
21
  } from "../../util";
22
+ import { AppInfo } from "../../redux/features/common/AppInfoSlice";
20
23
 
21
24
  const Login: React.FC = () => {
22
- const appInfo = useSelector((state: any) => state.AppInfo.value);
25
+ const appInfo: AppInfo = useSelector((state: any) => state.AppInfo.value);
23
26
  const [username, setUsername] = useState("");
24
27
  const [password, setPassword] = useState("");
25
28
  const [isLoginInProcess, setIsLoginInProcess] = useState(false);
@@ -89,15 +92,22 @@ const Login: React.FC = () => {
89
92
  },
90
93
  },
91
94
  palette: {
92
- mode: "dark",
95
+ mode: appInfo?.loginScreenStyle?.themeMode || "dark",
93
96
  primary: {
94
97
  main:
95
- appInfo.appTheme?.dark?.primaryColor || DARK_THEME_INITIAL_MAIN_COLOR,
98
+ appInfo?.loginScreenStyle?.themeMode === "light"
99
+ ? appInfo.appTheme?.light?.primaryColor ||
100
+ LIGHT_THEME_INITIAL_MAIN_COLOR
101
+ : appInfo.appTheme?.dark?.primaryColor ||
102
+ DARK_THEME_INITIAL_MAIN_COLOR,
96
103
  },
97
104
  secondary: {
98
105
  main:
99
- appInfo.appTheme?.dark?.secondaryColor ||
100
- DARK_THEME_INITIAL_SECANDARY_COLOR,
106
+ appInfo?.loginScreenStyle?.themeMode === "light"
107
+ ? appInfo.appTheme?.light?.secondaryColor ||
108
+ LIGHT_THEME_INITIAL_SECANDARY_COLOR
109
+ : appInfo.appTheme?.dark?.secondaryColor ||
110
+ DARK_THEME_INITIAL_SECANDARY_COLOR,
101
111
  },
102
112
  },
103
113
  });
@@ -121,6 +131,7 @@ const Login: React.FC = () => {
121
131
  dispatch(UserSessionActions.setAuthenticated(UserSession));
122
132
  } else {
123
133
  dispatch(UserSessionActions.setUnAuthenticated());
134
+ localStorage.removeItem("TOKEN");
124
135
  }
125
136
  } catch (error) {
126
137
  dispatch(UserSessionActions.setUnAuthenticated());
@@ -136,85 +147,101 @@ const Login: React.FC = () => {
136
147
 
137
148
  return (
138
149
  <ThemeProvider theme={loginTheme}>
139
- <Paper
150
+ <Box
140
151
  sx={{
141
- display: "flex",
142
- height: "100vh",
152
+ minHeight: "100vh", // full page height
143
153
  width: "100%",
144
- borderRadius: 0,
154
+ display: "flex",
145
155
  alignItems: "center",
146
156
  justifyContent: "center",
157
+ background: appInfo?.loginScreenStyle?.backgroundColor,
158
+ backgroundImage: `url('/${appInfo?.loginScreenStyle?.backgroundImageNameInPublicFolder}')`,
159
+ backgroundRepeat: "no-repeat",
160
+ backgroundSize: "cover", // 🔥 makes it responsive
161
+ backgroundPosition: "center", // keeps it centered
147
162
  }}
148
163
  >
149
- {UserSessionState.isAuthenticated == false ? (
150
- <Box
151
- sx={{
152
- display: "flex",
153
- flexDirection: "column",
154
- alignItems: "center",
155
- justifyContent: "center",
156
- }}
157
- >
158
- <img src={appInfo?.appLogo} width={150} height={150} />
159
- <Typography sx={{ m: 1 }} variant="h4" color="textSecondary">
160
- {appInfo?.appName}
161
- </Typography>
162
- <Typography
164
+ <Paper
165
+ sx={{
166
+ display: "flex",
167
+ width: "fit-content",
168
+ padding: 2,
169
+ height: "fit-content",
170
+ borderRadius: 5,
171
+ alignItems: "center",
172
+ justifyContent: "center",
173
+ }}
174
+ >
175
+ {UserSessionState.isAuthenticated == false ? (
176
+ <Box
163
177
  sx={{
164
- paddingRight: 1,
165
- width: "100%",
166
- textAlign: "right",
167
- fontSize: 10,
168
- }}
169
- variant="caption"
170
- color="textSecondary"
171
- >
172
- V.{appInfo.appVersion}
173
- </Typography>
174
- <TextField
175
- label="username"
176
- sx={{ width: 300, m: 1 }}
177
- value={username}
178
- onChange={(event) => {
179
- setUsername(event.target.value);
180
- }}
181
- onKeyDown={(event) => {
182
- if (event.key === "Enter") {
183
- handleLogin();
184
- }
185
- }}
186
- />
187
- <TextField
188
- label="password"
189
- sx={{ width: 300, m: 1 }}
190
- value={password}
191
- type="password"
192
- onChange={(event) => {
193
- setPassword(event.target.value);
194
- }}
195
- onKeyDown={(event) => {
196
- if (event.key === "Enter") {
197
- handleLogin();
198
- }
178
+ display: "flex",
179
+ flexDirection: "column",
180
+ alignItems: "center",
181
+ justifyContent: "center",
199
182
  }}
200
- />
201
- <Button
202
- loading={isLoginInProcess}
203
- onClick={handleLogin}
204
- variant="contained"
205
- color="primary"
206
- sx={{ m: 1 }}
207
183
  >
208
- login
209
- </Button>
210
- </Box>
211
- ) : (
212
- <>
213
- <CircularProgress sx={{ marginRight: 1 }} />
214
- <div>You will be redirected shortly ... please wait</div>
215
- </>
216
- )}
217
- </Paper>
184
+ <img src={appInfo?.appLogo} width={150} height={150} />
185
+ <Typography sx={{ m: 1 }} variant="h4" color="textSecondary">
186
+ {appInfo?.appName}
187
+ </Typography>
188
+ <Typography
189
+ sx={{
190
+ paddingRight: 1,
191
+ width: "100%",
192
+ textAlign: "right",
193
+ fontSize: 10,
194
+ }}
195
+ variant="caption"
196
+ color="textSecondary"
197
+ >
198
+ V.{appInfo.appVersion}
199
+ </Typography>
200
+ <TextField
201
+ label="username"
202
+ sx={{ width: 300, m: 1 }}
203
+ value={username}
204
+ onChange={(event) => {
205
+ setUsername(event.target.value);
206
+ }}
207
+ onKeyDown={(event) => {
208
+ if (event.key === "Enter") {
209
+ handleLogin();
210
+ }
211
+ }}
212
+ />
213
+ <TextField
214
+ label="password"
215
+ sx={{ width: 300, m: 1 }}
216
+ value={password}
217
+ type="password"
218
+ onChange={(event) => {
219
+ setPassword(event.target.value);
220
+ }}
221
+ onKeyDown={(event) => {
222
+ if (event.key === "Enter") {
223
+ handleLogin();
224
+ }
225
+ }}
226
+ />
227
+ <Button
228
+ loading={isLoginInProcess}
229
+ onClick={handleLogin}
230
+ variant="contained"
231
+ color="primary"
232
+ sx={{ m: 1 }}
233
+ >
234
+ login
235
+ </Button>
236
+ </Box>
237
+ ) : (
238
+ <>
239
+ <CircularProgress sx={{ marginRight: 1 }} />
240
+ <div>You will be redirected shortly ... please wait</div>
241
+ </>
242
+ )}
243
+ </Paper>
244
+ </Box>
218
245
  </ThemeProvider>
219
246
  );
220
247
  };
@@ -0,0 +1,11 @@
1
+ import { PublicClientApplication } from "@azure/msal-browser";
2
+
3
+ export const msalConfig = {
4
+ auth: {
5
+ clientId: "your-frontend-client-id",
6
+ authority: "https://login.microsoftonline.com/<tenant-id>",
7
+ redirectUri: "http://localhost:3000"
8
+ }
9
+ };
10
+
11
+ export const msalInstance = new PublicClientApplication(msalConfig);
@@ -64,6 +64,7 @@ const useAxios = () => {
64
64
  : false
65
65
  ) {
66
66
  dispatch(UserSessionActions.setUnAuthenticated());
67
+ localStorage.removeItem("TOKEN");
67
68
  toast.error("your session is now expired, you need to login again", {
68
69
  autoClose: false,
69
70
  });
@@ -14,6 +14,9 @@ import { UserSessionProps } from "../redux/features/common/UserSessionSlice";
14
14
  import LoadingMask from "../components/common/LoadingMask";
15
15
  import Login from "../components/common/Login";
16
16
  import NoLicenseComponent from "../components/common/NoLicenseComponent";
17
+ import { msalInstance } from "../components/msalConfig";
18
+ import { PublicClientApplication } from "@azure/msal-browser";
19
+ import AzureLogin from "../components/common/AzureLogin";
17
20
 
18
21
  const Main = styled("main", {
19
22
  shouldForwardProp: (prop) => prop !== "open",
@@ -53,9 +56,13 @@ const Main = styled("main", {
53
56
  };
54
57
  });
55
58
 
56
- export default function Layout() {
57
- const SideBarState = useSelector((state: any) => state.SideBar);
59
+ interface LayoutProps {
60
+ msalInstance: PublicClientApplication;
61
+ }
58
62
 
63
+ const Layout: React.FC<LayoutProps> = ({ msalInstance }) => {
64
+ const SideBarState = useSelector((state: any) => state.SideBar);
65
+ const appInfo = useSelector((state: any) => state.AppInfo.value);
59
66
  const isMobile = useIsMobile();
60
67
  const UserSession: UserSessionProps = useSelector(
61
68
  (state: any) => state.UserSession
@@ -80,9 +87,13 @@ export default function Layout() {
80
87
  <MainContent />
81
88
  <NoLicenseComponent />
82
89
  </Main>
90
+ ) : appInfo.authenticationMethod === "AZURE" ? (
91
+ <AzureLogin msalInstance={msalInstance} />
83
92
  ) : (
84
93
  <Login />
85
94
  )}
86
95
  </BrowserRouter>
87
96
  );
88
- }
97
+ };
98
+
99
+ export default Layout;
@@ -107,11 +107,6 @@ const MainContent: React.FC = () => {
107
107
  })}
108
108
  </Routes>
109
109
  </Box>
110
- )
111
- {/* : (
112
- <></>
113
- )
114
- } */}
115
110
  </CacheProvider>
116
111
  );
117
112
  };
@@ -102,6 +102,7 @@ const TopBar: React.FC = () => {
102
102
  await handleGetRequest({ endPointURI: "api/auth/logout" });
103
103
  } catch (error) {}
104
104
  dispatch(UserSessionActions.setUnAuthenticated());
105
+ localStorage.removeItem("TOKEN");
105
106
  };
106
107
  const toggleSideBar = () => {
107
108
  dispatch(toggleSideBarState());
@@ -169,7 +170,7 @@ const TopBar: React.FC = () => {
169
170
  <AttachmentImageViewer
170
171
  showAsAvatar={true}
171
172
  attachmentCode="ORGANIZATION_LOGOS"
172
- refKey={UserSession.value.currentOrganization.id + ""}
173
+ refKey={UserSession?.value?.currentOrganization?.id + ""}
173
174
  onErrorImage="/logo.png"
174
175
  style={{
175
176
  marginRight: 1,
package/src/main.tsx CHANGED
@@ -7,7 +7,18 @@ createRoot(document.getElementById("root")!).render(
7
7
  enableUINotifications={false}
8
8
  appLogo={"/logo.png"}
9
9
  appName="UI Base Library"
10
+ loginScreenStyle={{
11
+ themeMode: "light",
12
+ backgroundImageNameInPublicFolder: "bg.jpg",
13
+ }}
10
14
  appVersion="0.0"
15
+ authenticationMethod="APP"
16
+ azureConfiguration={{
17
+ frontEndClientId: "c3bbbdbd-f392-4459-b3dd-2351cb07f924",
18
+ tenantId: "9f136fef-4529-475f-98e6-d271eb04eb00",
19
+ redirectURL: "http://localhost:3000",
20
+ scopes: ["api://6008a934-2a32-4776-a3df-1842ac9371da/access_as_user"],
21
+ }}
11
22
  businessCommonStoresMetaData={{}}
12
23
  businessNavigationItems={[]}
13
24
  businessReduxReducers={{}}
@@ -31,12 +31,5 @@ export const findNavigationItemById = (
31
31
  };
32
32
 
33
33
  export const NavigationItems: TreeViewBaseItem<ExtendedTreeItemProps>[] = [
34
- ...AdministrationItems,
35
- {
36
- icon: "user",
37
- label: "Example Grid",
38
- id: "example",
39
- action: "NAVIGATION",
40
- actionPayload: { path: "example" },
41
- },
34
+ ...AdministrationItems
42
35
  ];
@@ -19,6 +19,18 @@ export type AppInfo = {
19
19
  ar: { [key: string]: string };
20
20
  en: { [key: string]: string };
21
21
  };
22
+ loginScreenStyle?: {
23
+ themeMode: "dark" | "light";
24
+ backgroundImageNameInPublicFolder?: string;
25
+ backgroundColor?: string;
26
+ };
27
+ authenticationMethod: "AZURE" | "APP";
28
+ azureConfiguration?: {
29
+ frontEndClientId: string;
30
+ tenantId: string;
31
+ redirectURL: string;
32
+ scopes: Array<string>;
33
+ };
22
34
  businessRoutes?: Array<SystemRoute>;
23
35
  businessNavigationItems?: Array<ExtendedTreeItemProps>;
24
36
  businessReduxReducers?: { [key: string]: Reducer<any> };
@@ -41,6 +53,7 @@ const initialState: AppInfoProp = {
41
53
  value: {
42
54
  documentTitle: null,
43
55
  apiBaseUrl: null,
56
+ authenticationMethod: "APP",
44
57
  appName: null,
45
58
  appVersion: null,
46
59
  appLogo: null,
@@ -1,9 +1,5 @@
1
- import Home from "../components/common/Home";
2
- import ExampleGrid from "../examples/ExampleGrid";
1
+
3
2
  import { ADMINISTRATION_ROUTES } from "./administration";
4
3
  import { SystemRoute } from "./types";
5
4
 
6
- export const SYSTEM_ROUTES: Array<SystemRoute> = [
7
- ...ADMINISTRATION_ROUTES,
8
- { path: "example", component: ExampleGrid },
9
- ];
5
+ export const SYSTEM_ROUTES: Array<SystemRoute> = [...ADMINISTRATION_ROUTES];
@@ -1,4 +1,6 @@
1
+ import moment from "moment";
1
2
  import { FormElementProps } from "../components";
3
+ import { DATE_FORMAT } from "./constants";
2
4
 
3
5
  export function hasDigitsOnly(str: string) {
4
6
  return /^\d+$/.test(str);
@@ -53,3 +55,19 @@ export function timeAgo(dateInput, appDirection: "ltr" | "rtl") {
53
55
 
54
56
  return locale === "ar" ? "الآن" : "just now";
55
57
  }
58
+
59
+ export const formatDate = (date, format) => {
60
+ if (format) {
61
+ return moment(date).format(format);
62
+ } else {
63
+ return moment(date).format(DATE_FORMAT);
64
+ }
65
+ };
66
+
67
+ export const parseStringToDate = (dateString, format) => {
68
+ if (format) {
69
+ return moment(dateString, format).toDate();
70
+ } else {
71
+ return moment(dateString, DATE_FORMAT).toDate();
72
+ }
73
+ };