nexus-shared 1.1.19 → 1.1.21

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 (53) hide show
  1. package/package.json +1 -1
  2. package/src/api-services/authentication-service.tsx +23 -0
  3. package/src/api-services/system-service.tsx +24 -316
  4. package/src/client.ts +1 -2
  5. package/src/components/documents/button.tsx +9 -16
  6. package/src/components/documents/icon-box.tsx +1 -1
  7. package/src/components/documents/tab-button.tsx +2 -2
  8. package/src/components/documents/tag.tsx +30 -0
  9. package/src/components/inputs/input-element.tsx +1 -1
  10. package/src/components/inputs/input-form.tsx +2 -2
  11. package/src/components/layouts/global-dialogbox.tsx +5 -9
  12. package/src/components/layouts/global-layout.tsx +8 -0
  13. package/src/components/layouts/layout-helpers.tsx +1 -4
  14. package/src/components/layouts/panels/user-panel.tsx +112 -0
  15. package/src/components/layouts/utility-menu.tsx +9 -29
  16. package/src/components/panels/theme-panel.tsx +2 -2
  17. package/src/helpers/browser-helpers.tsx +48 -3
  18. package/src/helpers/element-helpers.tsx +66 -0
  19. package/src/interface.ts +1 -0
  20. package/src/interfaces/auth-token-interfaces.tsx +6 -0
  21. package/src/interfaces/browser-interfaces.tsx +13 -0
  22. package/src/interfaces/http-interfaces.tsx +0 -3
  23. package/src/interfaces/icon-interfaces.tsx +3 -0
  24. package/src/interfaces/menu-interfaces.tsx +18 -1
  25. package/src/interfaces/storage-interfaces.tsx +1 -0
  26. package/src/interfaces/theme-interfaces.tsx +43 -129
  27. package/src/interfaces/user-interfaces.tsx +30 -0
  28. package/src/nexus.environments.tsx +39 -27
  29. package/src/services/http-services.tsx +6 -6
  30. package/src/services/loader-service.tsx +33 -17
  31. package/src/services/localstorage-service.tsx +23 -6
  32. package/src/services/message-services.tsx +1 -1
  33. package/src/services/theme-service.tsx +32 -16
  34. package/src/services/user-services.tsx +10 -0
  35. package/src/sso-config/auth-token-validation.ts +4 -8
  36. package/src/sso-config/callback-route.tsx +3 -2
  37. package/src/sso-config/cookie-helpers.tsx +47 -22
  38. package/src/sso-config/oauth-callback-state.ts +5 -2
  39. package/src/sso-config/pkce-helpers.ts +2 -9
  40. package/src/sso-config/provider-complete-route.tsx +7 -4
  41. package/src/sso-config/redirect-context-actions.tsx +2 -3
  42. package/src/sso-config/redirect-context-helpers.tsx +20 -5
  43. package/src/sso-config/refresh-route.tsx +5 -4
  44. package/src/sso-config/sign-in-route.tsx +9 -4
  45. package/src/sso-config/sign-out-route.tsx +7 -3
  46. package/src/sso-config/sign-up-route.tsx +14 -3
  47. package/src/sso-config/sso-backend-flow.ts +10 -17
  48. package/src/sso-config/sso-complete-route.tsx +7 -3
  49. package/src/sso-config/sso-config.ts +7 -5
  50. package/src/sso-config/sso-interfaces.tsx +34 -3
  51. package/src/styles/nexus.grid-view.css +42 -0
  52. package/src/styles/nexus.icon.css +1 -0
  53. package/src/styles/nexus.logic.css +34 -5
@@ -1,4 +1,5 @@
1
1
  import { EModules } from "./system-interfaces";
2
+ import { UN } from "./type-interfaces";
2
3
 
3
4
  export enum EUserRoles {
4
5
  Guest = 0,
@@ -6,6 +7,12 @@ export enum EUserRoles {
6
7
  Admin = 2,
7
8
  SuperAdmin = 3,
8
9
  }
10
+ export const IsAdmin = (userRole: EUserRoles | string | UN) => userRole === EUserRoles.Admin || EUserRoles[EUserRoles.Admin] === userRole;
11
+ export const IsSuperAdmin = (userRole: EUserRoles | string | UN) => userRole === EUserRoles.SuperAdmin || EUserRoles[EUserRoles.SuperAdmin] === userRole;
12
+ export const IsAdmin_SuperAdmin = (userRole: EUserRoles | string | UN) => IsAdmin(userRole) || IsSuperAdmin(userRole);
13
+ export const IsUser = (userRole: EUserRoles | string | UN) => userRole === EUserRoles.User || EUserRoles[EUserRoles.User] === userRole;
14
+ export const IsGuest = (userRole: EUserRoles | string | UN) => userRole === EUserRoles.Guest || EUserRoles[EUserRoles.Guest] === userRole;
15
+ export const IsUser_Guest = (userRole: EUserRoles | string | UN) => IsUser(userRole) || IsGuest(userRole);
9
16
 
10
17
  export interface IUserValidationResponse {
11
18
  isValidUser: boolean;
@@ -15,3 +22,26 @@ export interface IUserValidationResponse {
15
22
  userRole: EUserRoles;
16
23
  assignedModules: EModules;
17
24
  }
25
+ export interface ICurrentUser {
26
+ id: string;
27
+ fullname: string;
28
+ username: string;
29
+ roleName: string;
30
+ profilePicture: string | UN;
31
+ emailAddress: string | UN;
32
+ contactNumber: string | UN;
33
+ isImpersonating: boolean | UN;
34
+ isMultiTenant: boolean | UN;
35
+ enableAuditHistory: boolean | UN;
36
+ rootAccess: boolean | UN;
37
+ branchId: string | UN;
38
+ companyId: string | UN;
39
+ fiscalYearId: string | UN;
40
+ modules: EModules | UN;
41
+ }
42
+ export enum ESigninRequestMode {
43
+ DEFAULT = "0",
44
+ SIGN_OUT = "1",
45
+ SWITCH_BRANCH = "2",
46
+ SWITCH_FISCAL_YEAR = "3",
47
+ }
@@ -1,54 +1,66 @@
1
1
  import { EModules } from "./interfaces/system-interfaces";
2
2
  import { EBackgrounds } from "./interfaces/theme-interfaces";
3
3
 
4
+ // @ts-ignore
5
+ const processEnv = process.env;
6
+
4
7
  export const APIS = {
5
- DRIVE: process.env.API_DRIVE as string,
6
- AUTHENTICATION: process.env.API_AUTHENTICATION as string,
7
- CONFIGURATION: process.env.API_CONFIGURATION as string,
8
- CURRENT: process.env.API_CURRENT as string,
9
- ENTERPRISE: process.env.API_ENTERPRISE as string,
10
- MESSAGE: process.env.API_MESSAGE as string,
11
- ACCOUNTING: process.env.API_ACCOUNTING as string,
12
- REPORT: process.env.API_REPORT as string,
8
+ DRIVE: processEnv.API_DRIVE as string,
9
+ AUTHENTICATION: processEnv.API_AUTHENTICATION as string,
10
+ CONFIGURATION: processEnv.API_CONFIGURATION as string,
11
+ CURRENT: processEnv.API_CURRENT as string,
12
+ ENTERPRISE: processEnv.API_ENTERPRISE as string,
13
+ MESSAGE: processEnv.API_MESSAGE as string,
14
+ ACCOUNTING: processEnv.API_ACCOUNTING as string,
15
+ REPORT: processEnv.API_REPORT as string,
13
16
  };
14
17
 
15
18
  export const URLS = {
16
- CURRENT: process.env.URL_CURRENT as string,
17
- AUTHENTICATION: process.env.URL_AUTHENTICATION as string,
18
- SIGN_IN: process.env.URL_AUTHENTICATION_SIGN_IN as string,
19
- ALLOWED_ORIGINS: (process.env.ALLOWED_ORIGINS ?? "")
19
+ GLOBAL_SIGNIN: `${processEnv.URL_AUTHENTICATION}/api/auth/redirect-context`,
20
+ CURRENT: processEnv.URL_CURRENT as string,
21
+ AUTHENTICATION: processEnv.URL_AUTHENTICATION as string,
22
+ SIGN_IN: processEnv.URL_AUTHENTICATION_SIGN_IN as string,
23
+ ALLOWED_ORIGINS: (processEnv.ALLOWED_ORIGINS ?? "")
20
24
  .split(",")
21
- .map((origin) => origin.trim())
25
+ .map((origin: string) => origin.trim())
22
26
  .filter(Boolean),
23
27
  };
24
- const FILE = (apiName: string, filePath: string, fileName: string) => `${process.env.API_RESOURCES_BASE_PATH ?? `/api/app/${EModules.Drive}/Resources`}/${apiName}/${encodeURIComponent(filePath)}/${encodeURIComponent(fileName)}`;
28
+ const FILE = (apiName: string, filePath: string, fileName: string) => `${processEnv.API_RESOURCES_BASE_PATH ?? `/api/app/${EModules.Drive}/Resources`}/${apiName}/${encodeURIComponent(filePath)}/${encodeURIComponent(fileName)}`;
25
29
  export const FILES = {
26
30
  LOGO: {
27
31
  ICON: FILE("Image", "logos", "icon-logo"),
28
32
  FULL: FILE("Image", "logos", "full-logo"),
29
33
  FULL_TEXT: FILE("Image", "logos", "full-text-logo"),
30
34
  },
35
+ ICONS: {
36
+ USER: FILE("Image", "icons", "user"),
37
+ NO_IMAGE: FILE("Image", "icons", "no-image"),
38
+ },
31
39
  SCRIPT: (fileName: string, isStyles: boolean = false) => FILE("Script", isStyles ? "css" : "js", fileName),
32
40
  };
33
41
  export const NEXUS_INFO = {
34
- IS_ADMIN_MODULE: process.env.IS_ADMIN_MODULE !== "false",
35
- CURRENT_MODULE: process.env.CURRENT_MODULE !== undefined ? (parseInt(process.env.CURRENT_MODULE) as EModules) : undefined,
36
- CURRENT_MODULE_NAME: process.env.CURRENT_MODULE_NAME as string,
37
- CURRENT_MODULE_SHORT_NAME: process.env.CURRENT_MODULE_SHORT_NAME as string,
38
- IS_PRODUCTION: process.env.NODE_ENV === "production",
39
- SOFTWARE_COMPANY_SHORT_NAME: process.env.SOFTWARE_COMPANY_SHORT_NAME as string,
40
- SOFTWARE_COMPANY_FULL_NAME: process.env.SOFTWARE_COMPANY_FULL_NAME as string,
41
- SOFTWARE_VERSION: process.env.SOFTWARE_VERSION as string,
42
- SOFTWARE_LAST_UPDATE_DATE: process.env.SOFTWARE_LAST_UPDATE_DATE as string,
43
- SOFTWARE_COMPANY_WEBSITE: process.env.SOFTWARE_COMPANY_WEBSITE as string,
44
- IS_PROXY_SERVER: process.env.IS_PROXY_SERVER !== "false",
42
+ IS_ADMIN_MODULE: processEnv.IS_ADMIN_MODULE !== "false",
43
+ CURRENT_MODULE: processEnv.CURRENT_MODULE !== undefined ? (parseInt(processEnv.CURRENT_MODULE) as EModules) : undefined,
44
+ CURRENT_MODULE_NAME: processEnv.CURRENT_MODULE_NAME as string,
45
+ CURRENT_MODULE_SHORT_NAME: processEnv.CURRENT_MODULE_SHORT_NAME as string,
46
+ IS_PRODUCTION: processEnv.NODE_ENV === "production",
47
+ SOFTWARE_COMPANY_SHORT_NAME: processEnv.SOFTWARE_COMPANY_SHORT_NAME as string,
48
+ SOFTWARE_COMPANY_FULL_NAME: processEnv.SOFTWARE_COMPANY_FULL_NAME as string,
49
+ SOFTWARE_VERSION: processEnv.SOFTWARE_VERSION as string,
50
+ SOFTWARE_LAST_UPDATE_DATE: processEnv.SOFTWARE_LAST_UPDATE_DATE as string,
51
+ SOFTWARE_COMPANY_WEBSITE: processEnv.SOFTWARE_COMPANY_WEBSITE as string,
52
+ IS_PROXY_SERVER: processEnv.IS_PROXY_SERVER !== "false",
45
53
  };
46
54
  export const NEXUS_CONFIG = {
47
55
  /** Default revalidate time for static generation (in minutes) */
48
56
  DEFAULT_REVALIDATE_TIME: NEXUS_INFO.IS_PRODUCTION ? 60 * 24 : 0,
49
-
57
+ DEFAULT_REVALIDATE_TIME_ONE_HOUR: NEXUS_INFO.IS_PRODUCTION ? 60 : 0,
58
+ DEFAULT_REVALIDATE_TIME_ONE_DAY: NEXUS_INFO.IS_PRODUCTION ? 60 * 24 : 0,
59
+ DEFAULT_REVALIDATE_TIME_ONE_WEEK: NEXUS_INFO.IS_PRODUCTION ? 60 * 24 * 7 : 0,
60
+ DEFAULT_REVALIDATE_TIME_ONE_MONTH: NEXUS_INFO.IS_PRODUCTION ? 60 * 24 * 30 : 0,
61
+ DEFAULT_REVALIDATE_TIME_ONE_YEAR: NEXUS_INFO.IS_PRODUCTION ? 60 * 24 * 365 : 0,
50
62
  /** Default timeout for HTTP requests (in milliseconds) */
51
63
  DEFAULT_HTTP_REQUEST_TIMEOUT: NEXUS_INFO.IS_PRODUCTION ? 60000 : 1000000,
52
64
 
53
- DEFAULT_BACKGROUND: (process.env.DEFAULT_BACKGROUND as EBackgrounds) ?? EBackgrounds.Background1,
65
+ DEFAULT_BACKGROUND: (processEnv.DEFAULT_BACKGROUND as EBackgrounds) ?? EBackgrounds.Background1,
54
66
  };
@@ -3,7 +3,7 @@
3
3
  * For browser/client components use `http-client-services` (`Post`, `Get`, …) via `nexus-shared/client`.
4
4
  */
5
5
  import { Delay } from "../helpers/utility-helpers";
6
- import { EActions, EBody, EModules, GetMethod, IErrorRespose, ISuccessResponse, UN } from "../interface";
6
+ import { EActions, EBody, EModules, GetMethod, GetResponse, IErrorRespose, ISuccessResponse, UN } from "../interface";
7
7
  import { EErrorCodes, EExceptionCategories } from "../interfaces/exception-interfaces";
8
8
  import { APIS, NEXUS_CONFIG, NEXUS_INFO } from "../nexus.environments";
9
9
 
@@ -15,7 +15,7 @@ export const BuildApiHeader = (formdata?: any, httpMethod?: EActions | string, b
15
15
  bodyType = bodyType ?? EBody.JSON;
16
16
 
17
17
  const headers = new Headers();
18
- if (token && token.length > 50) headers.append("authorization", `${token}`);
18
+ if (token && token.length > 50) headers.append("authorization", token.startsWith("Bearer ") ? token : `Bearer ${token}`);
19
19
  if (browserId) headers.append("bid", browserId);
20
20
  headers.append("Content-Type", bodyType);
21
21
 
@@ -115,8 +115,9 @@ const handleResponse = async (response: Response): Promise<IErrorRespose> => {
115
115
  const status = response.status;
116
116
  try {
117
117
  const errorData = await response.json();
118
- return handleError({ ...errorData, status });
119
- } catch {
118
+
119
+ return handleError({ ...errorData, code: status, status: status });
120
+ } catch (error: any) {
120
121
  let message = "Unexpected server error.";
121
122
  switch (status) {
122
123
  case 401:
@@ -131,7 +132,7 @@ const handleResponse = async (response: Response): Promise<IErrorRespose> => {
131
132
  message = "Method not allowed.";
132
133
  break;
133
134
  }
134
- return handleError({ status, message });
135
+ return handleError(GetResponse.ERROR(message, "GENERIC_ERROR", EExceptionCategories.Application, EErrorCodes.Unknown, undefined, undefined, status));
135
136
  }
136
137
  };
137
138
  export const handleError = (error: any): IErrorRespose => {
@@ -148,7 +149,6 @@ export const handleError = (error: any): IErrorRespose => {
148
149
  };
149
150
  };
150
151
  const handleNetworkError = (error: any): IErrorRespose => {
151
- console.error("\n\nNetwork error encountered:", error);
152
152
  return handleError({
153
153
  status: 503,
154
154
  message: "Unable to connect to the server. Please check your network or backend status.",
@@ -36,17 +36,17 @@ const _findExistingLoader = (parent: HTMLElement): HTMLElement | null => {
36
36
  const loaderElement = FindChildElement(parent, `#${LOADER_IDENTITY}`);
37
37
  return loaderElement as HTMLElement | null;
38
38
  };
39
- const _updateLoaderProperties = (loaderElement: HTMLElement, type: EAnimationClasses, size: ESizes) => {
39
+ const _updateLoaderProperties = (loaderElement: HTMLElement, type: EAnimationClasses, size: ESizes, isInputLoader: boolean) => {
40
40
  if (type === EAnimationClasses.SPIN) {
41
41
  if (size === ESizes.VeryTiny) {
42
- loaderElement.style.setProperty("--SPIN_BOX_SIZE", "2.5rem");
43
- loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", "0.34rem");
42
+ loaderElement.style.setProperty("--SPIN_BOX_SIZE", isInputLoader ? "1.5rem" : "2.5rem");
43
+ loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", isInputLoader ? "0.28rem" : "0.34rem");
44
44
  } else if (size === ESizes.Tiny) {
45
- loaderElement.style.setProperty("--SPIN_BOX_SIZE", "2.8rem");
46
- loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", "0.36rem");
45
+ loaderElement.style.setProperty("--SPIN_BOX_SIZE", isInputLoader ? "2.0rem" : "2.8rem");
46
+ loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", isInputLoader ? "0.31rem" : "0.36rem");
47
47
  } else if (size === ESizes.VerySmall) {
48
- loaderElement.style.setProperty("--SPIN_BOX_SIZE", "3rem");
49
- loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", "0.38rem");
48
+ loaderElement.style.setProperty("--SPIN_BOX_SIZE", isInputLoader ? "2.4rem" : "3rem");
49
+ loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", isInputLoader ? "0.34rem" : "0.38rem");
50
50
  } else if (size === ESizes.Small) {
51
51
  loaderElement.style.setProperty("--SPIN_BOX_SIZE", "3.5rem");
52
52
  loaderElement.style.setProperty("--SPIN_BOX_BORDER_SIZE", "0.45rem");
@@ -68,26 +68,30 @@ const _updateLoaderProperties = (loaderElement: HTMLElement, type: EAnimationCla
68
68
  }
69
69
  }
70
70
  };
71
- const _updateLoaderAttributes = (loaderElement: HTMLElement, type: EAnimationClasses, size: ESizes, disabledInputs: boolean, count: number) => {
71
+ const _updateLoaderAttributes = (loaderElement: HTMLElement, type: EAnimationClasses, size: ESizes, disabledInputs: boolean, count: number, isInputLoader: boolean) => {
72
72
  loaderElement.setAttribute(LOADER_DATA_COUNT_ATTRIBUTE, count.toString());
73
73
  loaderElement.setAttribute(LOADER_DATA_TYPE_ATTRIBUTE, type);
74
74
  loaderElement.setAttribute(LOADER_DATA_SIZE_ATTRIBUTE, size);
75
75
  loaderElement.setAttribute(LOADER_DATA_DISABLED_INPUTS_ATTRIBUTE, disabledInputs ? "true" : "false");
76
76
  };
77
- const _createLoaderElement = (parent: HTMLElement, type: EAnimationClasses, size: ESizes, showLogo: boolean, disabledInputs: boolean, background: EBackgrounds, defaultClass?: string | UN): HTMLElement | null => {
77
+ const _createLoaderElement = (parent: HTMLElement, type: EAnimationClasses, size: ESizes, showLogo: boolean, disabledInputs: boolean, background: EBackgrounds, defaultClass?: string | UN, isInputLoader?: boolean): HTMLElement | null => {
78
78
  const loaderElement = _findExistingLoader(parent);
79
79
  if (loaderElement && !loaderElement.classList.contains(DISPOSED_LOADER_CLASS)) {
80
80
  const count = parseInt(loaderElement.getAttribute(LOADER_DATA_COUNT_ATTRIBUTE) ?? "0") + 1;
81
- _updateLoaderAttributes(loaderElement, type, size, disabledInputs, count);
82
- _updateLoaderProperties(loaderElement, type, size);
81
+ _updateLoaderAttributes(loaderElement, type, size, disabledInputs, count, isInputLoader ?? false);
82
+ _updateLoaderProperties(loaderElement, type, size, isInputLoader ?? false);
83
83
  return loaderElement as HTMLElement;
84
84
  }
85
85
 
86
86
  const newLoaderElement = document.createElement("div");
87
87
  newLoaderElement.id = LOADER_IDENTITY;
88
88
  newLoaderElement.classList.add("nexus-container", "o0", EAnimationClasses.FADE_IN, background);
89
- if (defaultClass) newLoaderElement.classList.add(defaultClass);
90
- _updateLoaderAttributes(newLoaderElement, type, size, disabledInputs, 1);
89
+ if (defaultClass) newLoaderElement.classList.add(...defaultClass.split(" "));
90
+ _updateLoaderAttributes(newLoaderElement, type, size, disabledInputs, 1, isInputLoader ?? false);
91
+ _updateLoaderProperties(newLoaderElement, type, size, isInputLoader ?? false);
92
+
93
+ newLoaderElement.style.maxHeight = "100%!important";
94
+ newLoaderElement.style.maxWidth = "100%!important";
91
95
 
92
96
  const loader = document.createElement("div");
93
97
  newLoaderElement.appendChild(loader);
@@ -99,8 +103,6 @@ const _createLoaderElement = (parent: HTMLElement, type: EAnimationClasses, size
99
103
  loader.classList.add("tx52");
100
104
  loader.style.fontSize = "1.5rem";
101
105
  } else if (showLogo && type !== EAnimationClasses.SLIDE_BAR && type !== EAnimationClasses.BOUNCE_BAR) {
102
- _updateLoaderProperties(newLoaderElement, type, size);
103
-
104
106
  const logoElement = document.createElement("img");
105
107
  logoElement.src = FILES.LOGO.ICON;
106
108
  logoElement.alt = "Logo";
@@ -124,7 +126,7 @@ const _createLoaderElement = (parent: HTMLElement, type: EAnimationClasses, size
124
126
  return newLoaderElement;
125
127
  };
126
128
 
127
- export const ShowLoader = async (parent?: String | HTMLElement | UN, type?: EAnimationClasses | UN, disabledInputs?: boolean | UN, size?: ESizes | UN, delay?: number | UN, showLogo?: boolean | UN, background?: EBackgrounds | UN, defaultClass?: string | UN): Promise<HTMLElement | null> => {
129
+ export const ShowLoader = async (parent?: String | HTMLElement | UN, type?: EAnimationClasses | UN, disabledInputs?: boolean | UN, size?: ESizes | UN, delay?: number | UN, showLogo?: boolean | UN, background?: EBackgrounds | UN, defaultClass?: string | UN, isInputLoader?: boolean | UN): Promise<HTMLElement | null> => {
128
130
  if (!IsBrowser()) return Promise.resolve(null);
129
131
  type = type ?? DEFAULT_LOADER_TYPE;
130
132
  size = size ?? DEFAULT_LOADER_SIZE;
@@ -134,7 +136,7 @@ export const ShowLoader = async (parent?: String | HTMLElement | UN, type?: EAni
134
136
  const timeout = setTimeout(() => {
135
137
  clearTimeout(timeout);
136
138
  const parentElement = _findParentElement(parent);
137
- const loaderElement = _createLoaderElement(parentElement, type, size, showLogo ?? true, disabledInputs ?? false, background, defaultClass);
139
+ const loaderElement = _createLoaderElement(parentElement, type, size, showLogo ?? true, disabledInputs ?? false, background, defaultClass, isInputLoader ?? false);
138
140
  resolve(loaderElement);
139
141
  }, delay ?? DEFAULT_LOADER_DELAY);
140
142
  });
@@ -167,3 +169,17 @@ export const HideLoader = (parent?: String | HTMLElement | UN, force?: boolean |
167
169
  }, delay ?? DEFAULT_LOADER_DELAY);
168
170
  });
169
171
  };
172
+
173
+ export const InputLoader = async (parent: String | HTMLElement, isShow: boolean, defaultClass?: string | UN): Promise<HTMLElement | null> => {
174
+ if (isShow !== false) {
175
+ const loaderElement = await ShowLoader(parent, EAnimationClasses.SPIN, false, ESizes.VeryTiny, null, false, EBackgrounds.None, defaultClass, true);
176
+ if (loaderElement) {
177
+ loaderElement.style.width = "100%";
178
+ loaderElement.style.height = "100%";
179
+ }
180
+ return loaderElement;
181
+ }
182
+
183
+ HideLoader(parent);
184
+ return null;
185
+ };
@@ -1,7 +1,7 @@
1
1
  import { CreateUpdatePreference } from "../api-services/preference-service";
2
2
  import { IsBrowser } from "../helpers/browser-helpers";
3
3
  import { Debounce } from "../helpers/utility-helpers";
4
- import { EFlexPositions } from "../interface";
4
+ import { EFlexPositions, UN } from "../interface";
5
5
  import { LOCAL_STORAGE_KEYS } from "../interfaces/storage-interfaces";
6
6
  import { EPreferenceTargets } from "../interfaces/system-interfaces";
7
7
  import { IThemeInformation } from "../interfaces/theme-interfaces";
@@ -24,10 +24,10 @@ const _createUpdatePreferenceDebounce = Debounce(CreateUpdatePreference, 1000);
24
24
  * @param isSyncWithServer Whether to sync with the server
25
25
  * @param target The target of the local storage
26
26
  */
27
- const _syncStorageToServer = async (key: string, value: string | null, isSyncWithServer?: boolean, target?: EPreferenceTargets) => {
27
+ const _syncStorageToServer = async (key: string, value: string | UN, isSyncWithServer?: boolean | UN, target?: EPreferenceTargets | UN) => {
28
28
  if (!IsBrowser() || !isSyncWithServer || !target) return;
29
29
 
30
- await _createUpdatePreferenceDebounce(target, key, value);
30
+ await _createUpdatePreferenceDebounce(target, key, value ?? null);
31
31
  };
32
32
 
33
33
  /**
@@ -38,11 +38,28 @@ const _syncStorageToServer = async (key: string, value: string | null, isSyncWit
38
38
  * @param isSyncWithServer Whether to sync with the server
39
39
  * @param target The target of the local storage
40
40
  */
41
- export const SetLocalStorage = (key: string, value: string, isSyncWithServer?: boolean, target?: EPreferenceTargets) => {
41
+ export const SetLocalStorage = (key: string, value: string | UN, isSyncWithServer?: boolean | UN, target?: EPreferenceTargets | UN) => {
42
42
  if (!IsBrowser()) return;
43
43
 
44
44
  _syncStorageToServer(key, value, isSyncWithServer, target);
45
- localStorage.setItem(key, value);
45
+
46
+ if (value === null || value === undefined) RemoveLocalStorage(key, isSyncWithServer, target);
47
+ else localStorage.setItem(key, value);
48
+ };
49
+ export const SetSessionStorage = (key: string, value: string | UN) => {
50
+ if (!IsBrowser()) return;
51
+ if (value === null || value === undefined) RemoveSessionStorage(key);
52
+ else sessionStorage.setItem(key, value);
53
+ };
54
+ export const GetSessionStorage = (key: string): string | null => {
55
+ if (!IsBrowser()) return null;
56
+ const data = sessionStorage.getItem(key);
57
+ if (!data) return null;
58
+ return data;
59
+ };
60
+ export const RemoveSessionStorage = (key: string) => {
61
+ if (!IsBrowser()) return;
62
+ sessionStorage.removeItem(key);
46
63
  };
47
64
  /**
48
65
  * Remove the value of the local storage
@@ -51,7 +68,7 @@ export const SetLocalStorage = (key: string, value: string, isSyncWithServer?: b
51
68
  * @param isSyncWithServer Whether to sync with the server
52
69
  * @param target The target of the local storage
53
70
  */
54
- export const RemoveLocalStorage = (key: string, isSyncWithServer?: boolean, target?: EPreferenceTargets) => {
71
+ export const RemoveLocalStorage = (key: string, isSyncWithServer?: boolean | UN, target?: EPreferenceTargets | UN) => {
55
72
  _syncStorageToServer(key, null, isSyncWithServer, target);
56
73
  localStorage.removeItem(key);
57
74
  };
@@ -157,7 +157,7 @@ const _showMessage = (title: string, message: string, messageType: EMessageTypes
157
157
  messageContentElement.appendChild(messageElement);
158
158
 
159
159
  const dismiss = () => dismissMessage(messageRootElement, callback);
160
- CreateIconBox(EIcons.CLOSE, titleElement, null, null, EIconTypes.FILLED, EBackgrounds.Background1, ESizes.VeryTiny, ESizes.Tiny, null, null, null, null, EOpacity.O05, "Clear Message", null, null, null, dismiss);
160
+ CreateIconBox(EIcons.CLOSE, titleElement, null, null, EIconTypes.OUTLINED, EBackgrounds.Background1, ESizes.VeryTiny, ESizes.Tiny, null, null, null, null, EOpacity.O05, "Clear Message", null, null, null, dismiss);
161
161
 
162
162
  activeMessages.unshift(messageRootElement);
163
163
  scheduleReflow();
@@ -1,10 +1,21 @@
1
1
  import { DEFAULT_SMALL_ICON_BOX_SIZE } from "../interfaces/icon-interfaces";
2
2
  import { DEFAULT_FONT_WEIGHTS } from "../interfaces/layout-interfaces";
3
3
  import { O } from "../nexus-client";
4
- import { IThemeColors, IThemeInformation } from "./../interfaces/theme-interfaces";
4
+ import { EBackgroundColors, EColorTypes, ETextColors, IThemeInformation } from "./../interfaces/theme-interfaces";
5
5
  import { GetCurrentTheme, SetCurrentTheme } from "./localstorage-service";
6
6
 
7
7
  const I = "!important;";
8
+ export const ICON_BOX_SCALES = {
9
+ xxs: 1 / 1.7,
10
+ xs: 1 / 1.5,
11
+ sm: DEFAULT_SMALL_ICON_BOX_SIZE,
12
+ ssm: 1 / 1.35,
13
+ lg: 1.25,
14
+ med: 1.1,
15
+ xl: 1.5,
16
+ xxl: 1.8,
17
+ };
18
+ export const THEME_KEYS = ["c1", "c2", "c3", "c10", "c11", "c12", "c13", "c20", "c30", "c41", "c51", "c52", "c100", "c200", "c400", "c600", "c800", "c950"];
8
19
 
9
20
  const _setVariable = (key: string, value: string | null) => {
10
21
  if (value) document.documentElement.style.setProperty(key, value);
@@ -22,13 +33,12 @@ const _initializeTheme = (theme: IThemeInformation): boolean => {
22
33
  if (!theme) return false;
23
34
  SetCurrentTheme(theme.systemName);
24
35
 
25
- const THEME_KEYS = Object.keys(theme.themeColors);
26
36
  for (const key of THEME_KEYS) {
27
- const color = theme.themeColors[key as keyof IThemeColors];
37
+ const color = theme[key as keyof IThemeInformation] as string;
28
38
  const [background, hover, focus, text] = color.split("|");
29
39
 
30
40
  if (!background) continue;
31
- const keyNumber = key.replace("C", "");
41
+ const keyNumber = key.replace("c", "");
32
42
 
33
43
  if (background !== "NULL") _setVariable(`--B${keyNumber}`, background);
34
44
  if (hover !== "NULL") _setVariable(`--H${keyNumber}`, hover);
@@ -96,9 +106,8 @@ export const InitializeStyles = () => {
96
106
  _setRule(sheet, `.bat${s}`, `border: var(--BRD${s}) solid transparent`);
97
107
  });
98
108
 
99
- const THEME_KEYS = Object.keys(themeInformations[0].themeColors);
100
109
  for (const key of THEME_KEYS) {
101
- const keyNumber = key.replace("C", "");
110
+ const keyNumber = key.replace("c", "");
102
111
  _setRule(sheet, `.bg${keyNumber}`, `background-color: var(--B${keyNumber}) ${I}; color: var(--T${keyNumber});`);
103
112
  _setRule(sheet, `.h.bg${keyNumber}:hover`, `background-color: var(--H${keyNumber}) ${I}`);
104
113
  _setRule(sheet, `.h.bg${keyNumber}:focus`, `background-color: var(--F${keyNumber}) ${I}`);
@@ -118,16 +127,6 @@ export const InitializeStyles = () => {
118
127
  fs: 2.85,
119
128
  };
120
129
 
121
- const ICON_BOX_SCALES = {
122
- xxs: 1 / 1.7,
123
- xs: 1 / 1.5,
124
- sm: DEFAULT_SMALL_ICON_BOX_SIZE,
125
- ssm: 1 / 1.35,
126
- lg: 1.25,
127
- med: 1.1,
128
- xl: 1.5,
129
- xxl: 1.8,
130
- };
131
130
  Object.entries(ICON_SIZES).forEach(([k, v]) => _setRule(sheet, `.icon.${k}`, `font-size: ${v}rem;`));
132
131
 
133
132
  Object.entries(ICON_BOX_SCALES).forEach(([k, s]) => {
@@ -135,6 +134,8 @@ export const InitializeStyles = () => {
135
134
  const css = `min-width: ${size} ${I}; min-height: ${size} ${I}; max-width: ${size} ${I}; max-height: ${size} ${I};`;
136
135
  _setRule(sheet, `.ib.${k}`, css);
137
136
  });
137
+
138
+ _setVariable(`--TOP_HEIGHT`, `calc(var(--IH) * ${ICON_BOX_SCALES.sm})`);
138
139
  };
139
140
  export const SwitchTheme = (themeCode: string): boolean => {
140
141
  const themeInformations = O.systemThemes;
@@ -145,3 +146,18 @@ export const SwitchTheme = (themeCode: string): boolean => {
145
146
  SetCurrentTheme(theme.systemName);
146
147
  return _initializeTheme(theme);
147
148
  };
149
+ export const GetColorCode = (colorCode: string | EBackgroundColors | ETextColors): number | null => {
150
+ if (colorCode) {
151
+ const match = colorCode.match(/\d+/);
152
+ return match ? parseInt(match[0], 10) : 0;
153
+ }
154
+
155
+ return null;
156
+ };
157
+ export const GetDynamicColor = (colorCode: string | EBackgroundColors | ETextColors, colorType: EColorTypes): string | null => {
158
+ if (colorCode) {
159
+ const code = GetColorCode(colorCode);
160
+ if (code) return `var(${colorType}${code})`;
161
+ }
162
+ return null;
163
+ };
@@ -0,0 +1,10 @@
1
+ import { EUserRoles, ICurrentUser, UN } from "../interface";
2
+
3
+ let CURRENT_USER: ICurrentUser | null = null;
4
+ export const SetCurrentUser = (user: ICurrentUser | UN) => (CURRENT_USER = user ?? null);
5
+ export const GetCurrentUser = (): ICurrentUser | null => CURRENT_USER;
6
+ export const GetCurrentUserRole = (): EUserRoles | null => {
7
+ const roleName = CURRENT_USER?.roleName;
8
+ if (!roleName) return null;
9
+ return EUserRoles[roleName as keyof typeof EUserRoles];
10
+ };
@@ -9,12 +9,8 @@ import { SSO_CONFIG } from "./sso-config";
9
9
  */
10
10
  export async function VerifyAccessToken(token: string): Promise<boolean> {
11
11
  if (!token?.trim()) return false;
12
- try {
13
- const response = await HttpGet(SSO_CONFIG.BACKEND_ENDPOINTS.VERIFY, token);
14
- if (!response.isSuccess) return false;
15
- const validationResponse = response.result as IUserValidationResponse;
16
- return validationResponse.isValidUser;
17
- } catch {
18
- return false;
19
- }
12
+ const response = await HttpGet(SSO_CONFIG.BACKEND_ENDPOINTS.VERIFY, token, 0);
13
+ const validationResponse = response.result as IUserValidationResponse;
14
+ if (!response.isSuccess) return false;
15
+ return validationResponse.isValidUser;
20
16
  }
@@ -2,9 +2,9 @@ import { NextRequest, NextResponse } from "next/server";
2
2
  import { URLS } from "../nexus.environments";
3
3
  import { SetAuthCookies } from "./cookie-helpers";
4
4
  import { DecodeOAuthCallbackState } from "./oauth-callback-state";
5
- import { SanitizeReturnPath } from "./redirect-context-helpers";
6
- import { BuildAuthenticationSignInUrl, SSO_CONFIG } from "./sso-config";
5
+ import { ClearRedirectContextCookie, SanitizeReturnPath } from "./redirect-context-helpers";
7
6
  import { ExchangeAuthorizationCode } from "./sso-backend-flow";
7
+ import { BuildAuthenticationSignInUrl, SSO_CONFIG } from "./sso-config";
8
8
 
9
9
  export async function CallbackRoute(req: NextRequest) {
10
10
  const code = req.nextUrl.searchParams.get("code");
@@ -27,6 +27,7 @@ export async function CallbackRoute(req: NextRequest) {
27
27
  const redirectTarget = new URL(SanitizeReturnPath(tokenData.returnPath ?? callbackState.returnPath), URLS.CURRENT!);
28
28
  const response = NextResponse.redirect(redirectTarget);
29
29
  SetAuthCookies(response, tokenData);
30
+ ClearRedirectContextCookie(response);
30
31
  return response;
31
32
  } catch {
32
33
  return NextResponse.redirect(new URL(BuildAuthenticationSignInUrl(callbackState?.returnPath)));
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
3
3
  import { VerifyAccessToken } from "./auth-token-validation";
4
4
  import { DecryptCookieValue, EncryptCookieValue } from "./cookie-encryption";
5
5
  import { GetAuthCookieOptions } from "./redirect-context-helpers";
6
+ import { SSO_CONFIG } from "./sso-config";
6
7
  import { CookieInfo, SSOAuthResponse } from "./sso-interfaces";
7
8
 
8
9
  export const AUTH_COOKIES = {
@@ -24,11 +25,7 @@ export function SetSsoSessionCookie(response: NextResponse, ssoSession: string,
24
25
  export function ClearAuthCookies(response: NextResponse) {
25
26
  response.cookies.set(AUTH_COOKIES.accessToken, "", GetAuthCookieOptions(0));
26
27
  response.cookies.set(AUTH_COOKIES.refreshToken, "", GetAuthCookieOptions(0));
27
- }
28
-
29
- export function ClearAllAuthCookies(response: NextResponse) {
30
28
  response.cookies.set(AUTH_COOKIES.ssoSession, "", GetAuthCookieOptions(0));
31
- ClearAuthCookies(response);
32
29
  }
33
30
 
34
31
  /**
@@ -44,34 +41,62 @@ export function GetDecryptedRequestCookie(req: NextRequest, name: string): strin
44
41
  export async function GetCookieInfo(): Promise<CookieInfo | null> {
45
42
  const cookieStore = await cookies();
46
43
 
44
+ const rawRefreshToken = cookieStore.get(AUTH_COOKIES.refreshToken)?.value;
45
+ const refreshToken = rawRefreshToken ? (DecryptCookieValue(rawRefreshToken) ?? undefined) : undefined;
46
+
47
47
  const rawSsoSession = cookieStore.get(AUTH_COOKIES.ssoSession)?.value;
48
- if (!rawSsoSession) return null;
49
- const ssoSession = DecryptCookieValue(rawSsoSession);
50
- if (!ssoSession) return null;
48
+ const ssoSession = rawSsoSession ? (DecryptCookieValue(rawSsoSession) ?? undefined) : undefined;
51
49
 
52
50
  const rawAccessToken = cookieStore.get(AUTH_COOKIES.accessToken)?.value;
53
- const rawRefreshToken = cookieStore.get(AUTH_COOKIES.refreshToken)?.value;
51
+ const accessToken = rawAccessToken ? (DecryptCookieValue(rawAccessToken) ?? undefined) : undefined;
52
+
53
+ if (!refreshToken && !ssoSession) return null;
54
54
 
55
55
  return {
56
56
  ssoSession,
57
- accessToken: rawAccessToken ? (DecryptCookieValue(rawAccessToken) ?? undefined) : undefined,
58
- refreshToken: rawRefreshToken ? (DecryptCookieValue(rawRefreshToken) ?? undefined) : undefined,
57
+ accessToken,
58
+ refreshToken,
59
59
  };
60
60
  }
61
61
 
62
- /** True when this client app has a valid access token (use on all non-auth client sites). */
63
- export async function HasAccessToken(): Promise<boolean> {
64
- const cookieStore = await cookies();
65
- const raw = cookieStore.get(AUTH_COOKIES.accessToken)?.value;
66
- if (!raw) return false;
67
- const token = DecryptCookieValue(raw);
68
- if (!token) return false;
69
- return VerifyAccessToken(token);
62
+ /** Where to send the browser after credentials or provider sign-in succeeds. */
63
+ export function ResolvePostLoginRedirectUrl(hasRedirectContext: boolean): string {
64
+ return hasRedirectContext ? SSO_CONFIG.FRONTEND_ENDPOINTS.SSO_COMPLETE : SSO_CONFIG.FRONTEND_ENDPOINTS.SIGN_IN;
70
65
  }
71
66
 
72
- /** True on the central auth app when user has SSO session or a valid local access token. */
73
- export async function HasAuthSession(): Promise<boolean> {
67
+ /**
68
+ * When the user already has auth cookies, returns the next URL (or null to show sign-in).
69
+ * - refresh token → home or SSO complete for an external client
70
+ * - sso session only → token exchange (GET sign-in) or SSO complete
71
+ */
72
+ export async function ResolveAuthenticatedRedirect(redirectContextUri?: string | null): Promise<string | null> {
73
+ const cookieInfo = await GetCookieInfo();
74
+ if (!cookieInfo) return null;
75
+
76
+ if (cookieInfo.refreshToken) {
77
+ return redirectContextUri ? SSO_CONFIG.FRONTEND_ENDPOINTS.SSO_COMPLETE : "/home";
78
+ }
79
+
80
+ if (cookieInfo.ssoSession) {
81
+ return redirectContextUri ? SSO_CONFIG.FRONTEND_ENDPOINTS.SSO_COMPLETE : SSO_CONFIG.FRONTEND_ENDPOINTS.SIGN_IN;
82
+ }
83
+
84
+ return null;
85
+ }
86
+
87
+ /** True when this client app has a valid access token (use on all non-auth client sites). */
88
+ export async function HasAccessToken(backendValidation: boolean = false): Promise<boolean> {
74
89
  const cookieStore = await cookies();
75
- const rawSsoSession = cookieStore.get(AUTH_COOKIES.ssoSession)?.value;
76
- return !!(rawSsoSession && DecryptCookieValue(rawSsoSession));
90
+ const rawRefreshToken = cookieStore.get(AUTH_COOKIES.refreshToken)?.value;
91
+ const refreshToken = rawRefreshToken ? DecryptCookieValue(rawRefreshToken) : null;
92
+ if (!refreshToken) return false;
93
+
94
+ const rawAccessToken = cookieStore.get(AUTH_COOKIES.accessToken)?.value;
95
+ const accessToken = rawAccessToken ? DecryptCookieValue(rawAccessToken) : null;
96
+
97
+ if (!accessToken && !backendValidation) return true;
98
+
99
+ if (!accessToken) return false;
100
+
101
+ return VerifyAccessToken(accessToken);
77
102
  }
@@ -8,11 +8,14 @@ type EncodedCallbackState = {
8
8
  t: number;
9
9
  };
10
10
 
11
+ // @ts-ignore
12
+ const processEnv = process.env;
13
+
11
14
  function getCookieSecret(): string {
12
- const ssoSecret = process.env.SSO_COOKIE_SECRET?.trim();
15
+ const ssoSecret = processEnv.SSO_COOKIE_SECRET?.trim();
13
16
  const secret = ssoSecret && !ssoSecret.startsWith("$") ? ssoSecret : undefined;
14
17
  if (secret) return secret;
15
- if (process.env.NODE_ENV === "development") return "NexusDevAuthSecretKey2026MustBe32CharsLong!";
18
+ if (processEnv.NODE_ENV === "development") return "NexusDevAuthSecretKey2026MustBe32CharsLong!";
16
19
  throw new Error("SSO_COOKIE_SECRET must be configured.");
17
20
  }
18
21
 
@@ -1,12 +1,5 @@
1
- import { randomBytes, createHash } from "crypto";
2
-
3
- export const PKCE_METHOD_S256 = "S256";
4
-
5
- export type PkcePair = {
6
- verifier: string;
7
- challenge: string;
8
- method: typeof PKCE_METHOD_S256;
9
- };
1
+ import { createHash, randomBytes } from "crypto";
2
+ import { PKCE_METHOD_S256, PkcePair } from "./sso-interfaces";
10
3
 
11
4
  /** Creates an OAuth PKCE pair (RFC 7636) using S256. */
12
5
  export function CreatePkcePair(): PkcePair {