diff --git a/cypress/e2e/Auth.cy.ts b/cypress/e2e/Auth.cy.ts
index 1c7fec13..a3f8be9f 100644
--- a/cypress/e2e/Auth.cy.ts
+++ b/cypress/e2e/Auth.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import { getTestSelectorByModule } from 'support/helpers';
import {
interceptFetchClientFailedRequest,
diff --git a/cypress/e2e/Branch/BranchCatalog.cy.ts b/cypress/e2e/Branch/BranchCatalog.cy.ts
index 771d58e3..6e8f4ca3 100644
--- a/cypress/e2e/Branch/BranchCatalog.cy.ts
+++ b/cypress/e2e/Branch/BranchCatalog.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearInputField,
clickActionButton,
diff --git a/cypress/e2e/Branch/BranchManagement.cy.ts b/cypress/e2e/Branch/BranchManagement.cy.ts
index e8242edf..52de5435 100644
--- a/cypress/e2e/Branch/BranchManagement.cy.ts
+++ b/cypress/e2e/Branch/BranchManagement.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearBranchDetailsForm,
fillBranchDetailsForm,
diff --git a/cypress/e2e/Branch/BranchView.cy.ts b/cypress/e2e/Branch/BranchView.cy.ts
index 9ecd8ddb..a00b743a 100644
--- a/cypress/e2e/Branch/BranchView.cy.ts
+++ b/cypress/e2e/Branch/BranchView.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import { getLinearLoader, getTestSelectorByModule, selectAction, verifyInputFields, verifyTextFields } from 'support/helpers';
import {
interceptEditBranchRequest,
diff --git a/cypress/e2e/Company/CompanyManagement.cy.ts b/cypress/e2e/Company/CompanyManagement.cy.ts
index 162979a8..028d86b4 100644
--- a/cypress/e2e/Company/CompanyManagement.cy.ts
+++ b/cypress/e2e/Company/CompanyManagement.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearInputField,
clickActionButton,
diff --git a/cypress/e2e/Company/CompanySettings.cy.ts b/cypress/e2e/Company/CompanySettings.cy.ts
index 4960d5df..3bcb43c0 100644
--- a/cypress/e2e/Company/CompanySettings.cy.ts
+++ b/cypress/e2e/Company/CompanySettings.cy.ts
@@ -1,5 +1,6 @@
-import { COMPANY_SETTINGS_KEY, ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { COMPANY_SETTINGS_KEY } from 'shared/constants/configs';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clickActionButton,
clickFlowsIcon,
diff --git a/cypress/e2e/Department/DepartmentCatalog.cy.ts b/cypress/e2e/Department/DepartmentCatalog.cy.ts
index 42841c70..796df94c 100644
--- a/cypress/e2e/Department/DepartmentCatalog.cy.ts
+++ b/cypress/e2e/Department/DepartmentCatalog.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearInputField,
clickActionButton,
diff --git a/cypress/e2e/Department/DepartmentManagement.cy.ts b/cypress/e2e/Department/DepartmentManagement.cy.ts
index fd4ef4fc..21e890fc 100644
--- a/cypress/e2e/Department/DepartmentManagement.cy.ts
+++ b/cypress/e2e/Department/DepartmentManagement.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
getLinearLoader,
getLoadingButtonLoadingIcon,
diff --git a/cypress/e2e/Department/DepartmentView.cy.ts b/cypress/e2e/Department/DepartmentView.cy.ts
index 254a98a6..06c7c6d9 100644
--- a/cypress/e2e/Department/DepartmentView.cy.ts
+++ b/cypress/e2e/Department/DepartmentView.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import { getLinearLoader, getTestSelectorByModule, selectAction, verifyInputFields, verifyTextFields } from 'support/helpers';
import {
interceptFetchBranchesRequest,
diff --git a/cypress/e2e/Employee/EmployeeCatalog.cy.ts b/cypress/e2e/Employee/EmployeeCatalog.cy.ts
index 1dc741e3..da8e9ced 100644
--- a/cypress/e2e/Employee/EmployeeCatalog.cy.ts
+++ b/cypress/e2e/Employee/EmployeeCatalog.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearInputField,
getLinearLoader,
diff --git a/cypress/e2e/Employee/EmployeeManagement.cy.ts b/cypress/e2e/Employee/EmployeeManagement.cy.ts
index 60a6d63d..d4756b86 100644
--- a/cypress/e2e/Employee/EmployeeManagement.cy.ts
+++ b/cypress/e2e/Employee/EmployeeManagement.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clickActionButton,
getLinearLoader,
diff --git a/cypress/e2e/Employee/EmployeeOrgChart.cy.ts b/cypress/e2e/Employee/EmployeeOrgChart.cy.ts
index bbdde83e..51b074b7 100644
--- a/cypress/e2e/Employee/EmployeeOrgChart.cy.ts
+++ b/cypress/e2e/Employee/EmployeeOrgChart.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
checkEmployeeReportsTo,
clickActionButton,
diff --git a/cypress/e2e/Employee/Profile.cy.ts b/cypress/e2e/Employee/Profile.cy.ts
index 235aa08f..00eef20d 100644
--- a/cypress/e2e/Employee/Profile.cy.ts
+++ b/cypress/e2e/Employee/Profile.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clearInputField,
clickActionButton,
diff --git a/cypress/e2e/Flows.cy.ts b/cypress/e2e/Flows.cy.ts
index 4aa95a18..829570ea 100644
--- a/cypress/e2e/Flows.cy.ts
+++ b/cypress/e2e/Flows.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
checkIsSubFlowDisabled,
checkIsSubFlowHasDisabledAttribute,
diff --git a/cypress/e2e/License.cy.ts b/cypress/e2e/License.cy.ts
index 28152f31..3d134f5b 100644
--- a/cypress/e2e/License.cy.ts
+++ b/cypress/e2e/License.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import { getTestSelectorByModule } from 'support/helpers';
import {
interceptFetchBranchesRequest,
diff --git a/cypress/e2e/Offboarding.cy.ts b/cypress/e2e/Offboarding.cy.ts
index 39c06968..49885327 100644
--- a/cypress/e2e/Offboarding.cy.ts
+++ b/cypress/e2e/Offboarding.cy.ts
@@ -1,5 +1,6 @@
-import { COMPANY_SETTINGS_KEY, ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { COMPANY_SETTINGS_KEY } from 'shared/constants/configs';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clickActionButton,
getTestSelectorByModule,
diff --git a/cypress/e2e/Onboarding.cy.ts b/cypress/e2e/Onboarding.cy.ts
index a116e205..74298eb3 100644
--- a/cypress/e2e/Onboarding.cy.ts
+++ b/cypress/e2e/Onboarding.cy.ts
@@ -1,5 +1,5 @@
-import { ROUTES } from 'shared/constants';
-import { Module, SubModule } from 'shared/types';
+import { ROUTES } from 'shared/constants/routes';
+import { Module, SubModule } from 'shared/types/module';
import {
clickActionButton,
clickField,
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index fbdbc4c2..44c92f3e 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -1,7 +1,7 @@
///
import { SinonStub } from 'cypress/types/sinon';
-import { ThemeMode } from 'shared/types';
+import { ThemeMode } from 'shared/types/theme';
// ***********************************************
// This example commands.ts shows you how to
diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts
index 2276bcba..17152c78 100644
--- a/cypress/support/helpers.ts
+++ b/cypress/support/helpers.ts
@@ -1,4 +1,8 @@
-import { Branch, ColorSchemeId, Department, EntityInput, Module, SubModule, ThemeMode } from 'shared/types';
+import { Branch } from 'shared/types/branch';
+import { EntityInput } from 'shared/types/common';
+import { Department } from 'shared/types/department';
+import { Module, SubModule } from 'shared/types/module';
+import { ColorSchemeId, ThemeMode } from 'shared/types/theme';
const colorSchemeValues: ColorSchemeId[] = [
'midnight',
diff --git a/cypress/support/interceptors.ts b/cypress/support/interceptors.ts
index d294dc27..a2b50d08 100644
--- a/cypress/support/interceptors.ts
+++ b/cypress/support/interceptors.ts
@@ -1,4 +1,10 @@
-import { Branch, Department, Employee, PaginatedResponse } from '../../src/shared/types';
+import { Branch } from '../../src/shared/types/branch';
+import { Department } from '../../src/shared/types/department';
+import { Employee } from '../../src/shared/types/employee';
+
+type PaginatedResponse = {
+ items: T[];
+};
const baseUrl = Cypress.config('baseUrl');
const serverBaseUrl = Cypress.env('serverBaseUrl');
@@ -12,18 +18,12 @@ export const interceptFetchSystemLicenseRequest = () => {
export const interceptFetchClientRequest = () => {
cy.fixture('client').then((client) => {
- cy.intercept('GET', `${serverBaseUrl}/Identity/Client?origin=${Cypress.config('baseUrl')}`, { body: client }).as('fetchClientRequest');
+ cy.intercept('GET', `${serverBaseUrl}/Identity/Client*`, { body: client }).as('fetchClientRequest');
});
};
export const interceptFetchClientFailedRequest = () => {
- cy.interceptWithAuth(
- 'GET',
- `${serverBaseUrl}/Identity/Client?origin=${Cypress.config('baseUrl')}`,
- null,
- 'fetchClientFailedRequest',
- 404
- );
+ cy.interceptWithAuth('GET', `${serverBaseUrl}/Identity/Client*`, null, 'fetchClientFailedRequest', 404);
};
export const interceptLoginRequest = () => {
diff --git a/jest.config.ts b/jest.config.ts
index 08792375..3461fbfc 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -171,12 +171,11 @@ const config: Config = {
// A map from regular expressions to paths to transformers
transform: {
- '^.+\\.tsx?$': 'ts-jest',
- '^.+\\.ts?$': 'ts-jest',
+ '^.+\\.[tj]sx?$': 'ts-jest',
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
- transformIgnorePatterns: ['node_modules/(?!(\\oidc-client-ts)/)'],
+ transformIgnorePatterns: ['node_modules/(?!(@fossa-app|@fable-org|oidc-client-ts)/)'],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
diff --git a/package-lock.json b/package-lock.json
index b872c749..463b93a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.13.0",
+ "@fossa-app/bridge": "^0.1.34",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.4.8",
"@reduxjs/toolkit": "^2.11.2",
@@ -1758,6 +1759,21 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@fable-org/fable-library-ts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@fable-org/fable-library-ts/-/fable-library-ts-2.0.0.tgz",
+ "integrity": "sha512-jT0//WjcvRqC8x+9t8nrsKUmuCJdKsl/dXDfMCtk4g8qh9CAX/g9xzdbhTTtUV4Dxp3lkHkylKV/5gel4P05Ew==",
+ "license": "MIT"
+ },
+ "node_modules/@fossa-app/bridge": {
+ "version": "0.1.34",
+ "resolved": "https://registry.npmjs.org/@fossa-app/bridge/-/bridge-0.1.34.tgz",
+ "integrity": "sha512-zRVgAseXtagw29dD2d30smsUl6i9odWjl0SOHQVf5izLrJ9K2CXRvoogyiIXSCZD9kiHset9YahmQomjnBYF+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fable-org/fable-library-ts": "^2.0.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
diff --git a/package.json b/package.json
index e81cad6d..12be4e01 100644
--- a/package.json
+++ b/package.json
@@ -6,10 +6,10 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.13.0",
+ "@fossa-app/bridge": "^0.1.34",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.4.8",
"@reduxjs/toolkit": "^2.11.2",
- "axios": "^1.16.1",
"leaflet": "^1.9.4",
"oidc-client-ts": "^3.5.0",
"react": "^19.2.0",
diff --git a/src/AxiosInterceptor.tsx b/src/AxiosInterceptor.tsx
deleted file mode 100644
index d2782771..00000000
--- a/src/AxiosInterceptor.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useAppDispatch, useAppSelector } from 'store';
-import { removeUser, selectAuthSettings, setError } from 'store/features';
-import axios, { AxiosError, AxiosRequestConfig } from 'shared/configs/axios';
-import { getUserFromLocalStorage, getUserManager, parseResponse } from 'shared/helpers';
-import { MESSAGES, ROUTES } from 'shared/constants';
-import { ErrorResponseDTO } from 'shared/types';
-
-// TODO: debug, a weird issue takes place when authenticated, the token is not being set and receiving 401, fixes after refresh
-const AxiosInterceptor: React.FC = ({ children }) => {
- const navigate = useNavigate();
- const userManager = getUserManager();
- const dispatch = useAppDispatch();
- const { item: authSettings } = useAppSelector(selectAuthSettings);
- const [shouldNavigate, setShouldNavigate] = React.useState(false);
-
- const refreshToken = React.useCallback(
- async (errorConfig: AxiosRequestConfig): Promise => {
- try {
- const user = await userManager.signinSilent();
-
- if (user?.access_token && errorConfig.headers) {
- errorConfig.headers.Authorization = `${user.token_type} ${user.access_token}`;
-
- return axios(errorConfig);
- }
- } catch {
- await userManager.removeUser();
-
- setShouldNavigate(true);
- dispatch(removeUser());
-
- dispatch(
- setError({
- title: MESSAGES.error.general.unAuthorized,
- })
- );
-
- return Promise.reject({
- title: MESSAGES.error.general.unAuthorized,
- status: 401,
- });
- }
-
- return null;
- },
- [dispatch, userManager]
- );
-
- React.useEffect(() => {
- if (shouldNavigate) {
- navigate(ROUTES.login.path);
- }
- }, [shouldNavigate, navigate]);
-
- React.useEffect(() => {
- const requestInterceptor = axios.interceptors.request.use((config) => {
- const { access_token, token_type = 'Bearer' } = getUserFromLocalStorage(authSettings.client_id) || {};
-
- if (access_token) {
- config.headers.Authorization = `${token_type} ${access_token}`;
- }
-
- return config;
- });
-
- const responseInterceptor = axios.interceptors.response.use(
- (response) => {
- return parseResponse(response);
- },
- async (error: AxiosError) => {
- if (!error.isAxiosError) {
- return Promise.reject(error);
- }
-
- if (error.response) {
- error.response = parseResponse(error.response);
- }
-
- if (error.code === 'ERR_NETWORK') {
- dispatch(
- setError({
- title: MESSAGES.error.general.network,
- })
- );
-
- return Promise.reject({
- status: 599,
- title: MESSAGES.error.general.network,
- });
- }
-
- if (error.config && error.response?.status === 401) {
- return refreshToken(error.config);
- }
-
- if (error.response && error.response.status >= 500) {
- dispatch(
- setError({
- title: MESSAGES.error.general.common,
- })
- );
-
- return Promise.reject({
- ...(error.response.data as ErrorResponseDTO),
- title: MESSAGES.error.general.common,
- });
- }
-
- return Promise.reject(error.response?.data);
- }
- );
-
- return () => {
- axios.interceptors.request.eject(requestInterceptor);
- axios.interceptors.response.eject(responseInterceptor);
- };
- }, [authSettings, dispatch, refreshToken]);
-
- return <>{children}>;
-};
-
-export default AxiosInterceptor;
diff --git a/src/components/Catalog.tsx b/src/components/Catalog.tsx
index 3f94bf00..afbb631f 100644
--- a/src/components/Catalog.tsx
+++ b/src/components/Catalog.tsx
@@ -3,7 +3,7 @@ import { generatePath, useNavigate } from 'react-router-dom';
import { AsyncThunkAction } from '@reduxjs/toolkit';
import { AppDispatch, PaginatedStateEntity, RootState, Status, useAppDispatch, useAppSelector } from 'store';
import { selectUserRoles } from 'store/features';
-import { Module, SubModule, Entity, PaginationParams, UserRole, ErrorResponseDTO, PaginatedResponse } from 'shared/types';
+import { Module, SubModule, Entity, PaginationParams, UserRole, ProblemDetailsModel, PaginatedResponse } from 'shared/types';
import { APP_CONFIG } from 'shared/constants';
import { getTestSelectorByModule } from 'shared/helpers';
import { useUnmount } from 'shared/hooks';
@@ -15,7 +15,7 @@ import { renderPrimaryLinkText } from 'components/UI/helpers/renderPrimaryLinkTe
interface StateAction {
state: RootState;
- rejectValue: ErrorResponseDTO;
+ rejectValue: ProblemDetailsModel;
dispatch?: AppDispatch;
}
diff --git a/src/components/Entity/EntityManager.tsx b/src/components/Entity/EntityManager.tsx
index be00e561..3af6f95c 100644
--- a/src/components/Entity/EntityManager.tsx
+++ b/src/components/Entity/EntityManager.tsx
@@ -4,12 +4,12 @@ import { DefaultValues, FieldErrors, FieldValues } from 'react-hook-form';
import { AsyncThunkAction } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector, RootState, AppDispatch, StateEntity } from 'store';
import { useOnFormSubmitEffect, useSafeNavigateBack, useUnmount } from 'shared/hooks';
-import { BaseEntity, EntityInput, ErrorResponse, ErrorResponseDTO, Module, SubModule } from 'shared/types';
+import { BaseEntity, EntityInput, ErrorResponse, ProblemDetailsModel, Module, SubModule } from 'shared/types';
import { areEqualBigIds } from 'shared/helpers';
import PageLayout from 'components/layouts/PageLayout';
import Form, { FormActionName, FormFieldProps, FormProps } from 'components/UI/Form';
-type EntityManagerProps = {
+type EntityManagerProps = {
module: Module;
subModule: SubModule;
pageTitle: { create: string; edit: string };
@@ -24,15 +24,17 @@ type EntityManagerProps = {
resetEntity: () => ReturnType;
resetErrors: () => ReturnType;
resetCatalogFetchStatus: () => ReturnType;
- mapDTO: (value: T) => EntityInput;
- createEntityAction: (args: EntityInput) => AsyncThunkAction, { rejectValue: ErrorResponse }>;
+ mapInput: (value: T) => EntityInput;
+ createEntityAction: (
+ args: EntityInput
+ ) => AsyncThunkAction, { rejectValue: ErrorResponse }>;
editEntityAction: (
- args: [string, EntityInput]
- ) => AsyncThunkAction], { rejectValue: ErrorResponse }>;
- fetchEntityAction: (id: string) => AsyncThunkAction;
+ args: [string, EntityInput]
+ ) => AsyncThunkAction], { rejectValue: ErrorResponse }>;
+ fetchEntityAction: (id: string) => AsyncThunkAction;
};
-const EntityManager = ({
+const EntityManager = ({
module,
subModule,
pageTitle,
@@ -50,8 +52,8 @@ const EntityManager = ({
fetchEntityAction,
createEntityAction,
editEntityAction,
- mapDTO,
-}: EntityManagerProps) => {
+ mapInput,
+}: EntityManagerProps) => {
const dispatch = useAppDispatch();
const { id } = useParams<{ id: string }>();
const { item: values, fetchStatus, updateStatus = 'idle' } = useAppSelector(selectEntity);
@@ -71,12 +73,12 @@ const EntityManager = ({
});
const handleSubmit = (formValue: T) => {
- const dto = mapDTO(formValue);
+ const input = mapInput(formValue);
if (id) {
- dispatch(editEntityAction([id, dto]));
+ dispatch(editEntityAction([id, input]));
} else {
- dispatch(createEntityAction(dto));
+ dispatch(createEntityAction(input));
}
setFormSubmitted(true);
diff --git a/src/components/Entity/EntityViewer.tsx b/src/components/Entity/EntityViewer.tsx
index c3c3e2a6..375c2fe4 100644
--- a/src/components/Entity/EntityViewer.tsx
+++ b/src/components/Entity/EntityViewer.tsx
@@ -4,7 +4,7 @@ import { AsyncThunkAction } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector, RootState, AppDispatch, StateEntity } from 'store';
import { selectUserRoles } from 'store/features';
import { areEqualBigIds, hasAllowedRole } from 'shared/helpers';
-import { Module, SubModule, ErrorResponseDTO, BaseEntity } from 'shared/types';
+import { Module, SubModule, ProblemDetailsModel, BaseEntity } from 'shared/types';
import PageLayout from 'components/layouts/PageLayout';
import ViewDetails, { ViewDetailActionName, ViewDetailProps } from 'components/UI/ViewDetails';
@@ -17,7 +17,7 @@ type EntityViewerProps = {
editRoute?: string;
selectEntity: (state: RootState) => StateEntity;
resetEntity: () => ReturnType;
- fetchEntityAction: (id: string) => AsyncThunkAction;
+ fetchEntityAction: (id: string) => AsyncThunkAction;
};
const EntityViewer = ({
diff --git a/src/layout/MessageLayout.tsx b/src/layout/MessageLayout.tsx
index 053d800e..802a2cae 100644
--- a/src/layout/MessageLayout.tsx
+++ b/src/layout/MessageLayout.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { useAppDispatch, useAppSelector } from 'store';
import { clearMessages, selectMessage } from 'store/features';
import { Module, SubModule } from 'shared/types';
-import { getTestSelectorByModule } from 'shared/helpers';
+import { getProblemTitle, getTestSelectorByModule } from 'shared/helpers';
import Snackbar from 'components/UI/Snackbar';
const MessageLayout: React.FC = () => {
@@ -24,7 +24,7 @@ const MessageLayout: React.FC = () => {
data-cy={testSelector}
type={type}
open={!!error || !!success}
- message={error ? error.title : success}
+ message={error ? getProblemTitle(error) : success}
onClose={handleSnackbarClose}
/>
);
diff --git a/src/pages/Manage/Branch/pages/BranchManagement.tsx b/src/pages/Manage/Branch/pages/BranchManagement.tsx
index e7372708..9391725f 100644
--- a/src/pages/Manage/Branch/pages/BranchManagement.tsx
+++ b/src/pages/Manage/Branch/pages/BranchManagement.tsx
@@ -19,13 +19,14 @@ import {
ROUTES,
USER_PERMISSION_GENERAL_ERROR,
} from 'shared/constants';
-import { Branch, BranchDTO, TimeZone } from 'shared/types';
+import { Branch, TimeZone } from 'shared/types';
import {
getBranchManagementDetailsByAddressFormSchema,
- mapBranchDTO,
+ mapBranchInput,
mapDisabledFields,
mapBranchFieldOptionsToFieldOptions,
deepCopyObject,
+ getProblemErrors,
} from 'shared/helpers';
import { FormFieldProps } from 'components/UI/Form';
import EntityManager from 'components/Entity/EntityManager';
@@ -44,7 +45,7 @@ const BranchManagementPage: React.FC = () => {
const [noPhysicalAddress, setNoPhysicalAddress] = React.useState(undefined);
const [fields, setFields] = React.useState[]>([]);
const formLoading = fetchStatus === 'loading' || (!branch && !!id) || fields.length === 0;
- const errors = isUserAdmin ? deepCopyObject(updateError?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(updateError)) : USER_PERMISSION_GENERAL_ERROR;
React.useEffect(() => {
if (!id || (id && branch && noPhysicalAddress !== undefined)) {
@@ -70,7 +71,7 @@ const BranchManagementPage: React.FC = () => {
};
return (
-
+
module={testModule}
subModule={testSubModule}
pageTitle={{ create: 'Create Branch', edit: 'Edit Branch' }}
@@ -88,7 +89,7 @@ const BranchManagementPage: React.FC = () => {
fetchEntityAction={(id) => fetchBranchById({ id, skipState: false })}
createEntityAction={createBranch}
editEntityAction={editBranch}
- mapDTO={mapBranchDTO}
+ mapInput={mapBranchInput}
/>
);
};
diff --git a/src/pages/Manage/Company/pages/CompanyEdit.tsx b/src/pages/Manage/Company/pages/CompanyEdit.tsx
index 785ca475..a7e8a497 100644
--- a/src/pages/Manage/Company/pages/CompanyEdit.tsx
+++ b/src/pages/Manage/Company/pages/CompanyEdit.tsx
@@ -16,8 +16,8 @@ import {
ROUTES,
USER_PERMISSION_GENERAL_ERROR,
} from 'shared/constants';
-import { Company, CompanyDTO, EntityInput } from 'shared/types';
-import { deepCopyObject, mapCountriesToFieldOptions, mapDisabledFields } from 'shared/helpers';
+import { Company, EntityInput } from 'shared/types';
+import { deepCopyObject, getProblemErrors, mapCountriesToFieldOptions, mapDisabledFields } from 'shared/helpers';
import { useOnFormSubmitEffect } from 'shared/hooks';
import PageLayout from 'components/layouts/PageLayout';
import Form, { FormActionName } from 'components/UI/Form';
@@ -34,10 +34,10 @@ const CompanyEditPage: React.FC = () => {
const { item: company, updateError: error, fetchStatus, updateStatus = 'idle' } = useAppSelector(selectCompany);
const [formSubmitted, setFormSubmitted] = React.useState(false);
const fields = mapCountriesToFieldOptions(mapDisabledFields(COMPANY_MANAGEMENT_DETAILS_FORM_SCHEMA.fields, userRoles), countries);
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
const navigateToViewCompany = () => navigate(ROUTES.viewCompany.path);
- const handleSubmit = (data: EntityInput) => {
+ const handleSubmit = (data: EntityInput) => {
dispatch(editCompany(data));
setFormSubmitted(true);
};
diff --git a/src/pages/Manage/Company/pages/CompanySettings.tsx b/src/pages/Manage/Company/pages/CompanySettings.tsx
index 73d686ca..930869fe 100644
--- a/src/pages/Manage/Company/pages/CompanySettings.tsx
+++ b/src/pages/Manage/Company/pages/CompanySettings.tsx
@@ -10,7 +10,7 @@ import {
} from 'store/features';
import { editCompanySettings } from 'store/thunks';
import { COMPANY_SETTINGS_FIELDS, COMPANY_SETTINGS_MANAGEMENT_DETAILS_FORM_SCHEMA, ROUTES } from 'shared/constants';
-import { CompanySettings, CompanySettingsDTO, EntityInput, ThemeMode } from 'shared/types';
+import { CompanySettings, EntityInput, ThemeMode } from 'shared/types';
import { mapDisabledFields } from 'shared/helpers';
import { COLOR_SCHEMES } from 'shared/themes';
import { useUnmount } from 'shared/hooks';
@@ -29,11 +29,11 @@ const CompanySettingsPage: React.FC = () => {
const { isDarkTheme } = useAppSelector(selectAppConfig);
const mode: ThemeMode = isDarkTheme ? 'dark' : 'light';
- const handleSubmit = (data: EntityInput) => {
+ const handleSubmit = (data: EntityInput) => {
dispatch(editCompanySettings({ ...companySettings, ...data }));
};
- const handleChange = (data: CompanySettingsDTO) => {
+ const handleChange = (data: CompanySettings) => {
if (data.colorSchemeId) {
dispatch(setPreviewCompanyColorSchemeSettings(data.colorSchemeId));
}
diff --git a/src/pages/Manage/Department/pages/DepartmentManagement.tsx b/src/pages/Manage/Department/pages/DepartmentManagement.tsx
index 4b12137b..35065a37 100644
--- a/src/pages/Manage/Department/pages/DepartmentManagement.tsx
+++ b/src/pages/Manage/Department/pages/DepartmentManagement.tsx
@@ -23,8 +23,14 @@ import {
ROUTES,
USER_PERMISSION_GENERAL_ERROR,
} from 'shared/constants';
-import { Department, DepartmentDTO, EmployeeDTO } from 'shared/types';
-import { mapDisabledFields, deepCopyObject, mapDepartmentDTO, mapDepartmentFieldOptionsToFieldOptions } from 'shared/helpers';
+import { Department } from 'shared/types';
+import {
+ mapDisabledFields,
+ deepCopyObject,
+ mapDepartmentInput,
+ mapDepartmentFieldOptionsToFieldOptions,
+ getProblemErrors,
+} from 'shared/helpers';
import EntityManager from 'components/Entity/EntityManager';
const testModule = DEPARTMENT_MANAGEMENT_DETAILS_FORM_SCHEMA.module;
@@ -47,7 +53,7 @@ const DepartmentManagementPage: React.FC = () => {
} = useAppSelector(selectManagers);
const parentDepartmentsLoading = parentDepartmentsFetchStatus === 'loading';
const managersLoading = managersFetchStatus === 'loading';
- const errors = isUserAdmin ? deepCopyObject(updateError?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(updateError)) : USER_PERMISSION_GENERAL_ERROR;
const handleParentDepartmentsScrollEnd = () => {
if (parentDepartmentsPage.pageNumber! < parentDepartmentsPage.totalPages!) {
@@ -68,12 +74,12 @@ const DepartmentManagementPage: React.FC = () => {
);
const parentDepartmentItems =
department?.parentDepartmentId && !isParentDepartmentOptionAvailable
- ? [{ id: department.parentDepartmentId, name: department.parentDepartmentName } as DepartmentDTO, ...parentDepartments]
+ ? [{ id: department.parentDepartmentId, name: department.parentDepartmentName ?? '' }, ...parentDepartments]
: parentDepartments;
const isManagerOptionAvailable = managers.some((managertItem) => String(managertItem.id) === String(department?.managerId));
const managerItems =
department?.managerId && !isManagerOptionAvailable
- ? [{ id: department.managerId, name: department.managerName } as unknown as EmployeeDTO, ...managers]
+ ? [{ id: department.managerId, fullName: department.managerName ?? '' }, ...managers]
: managers;
const disabledFields = mapDisabledFields(DEPARTMENT_MANAGEMENT_DETAILS_FORM_SCHEMA.fields, userRoles);
const mappedFields = mapDepartmentFieldOptionsToFieldOptions(disabledFields, parentDepartmentItems, managerItems);
@@ -110,7 +116,7 @@ const DepartmentManagementPage: React.FC = () => {
}, [parentDepartmentsFetchStatus, parentDepartmentsPage, dispatch]);
return (
-
+
module={testModule}
subModule={testSubModule}
pageTitle={{ create: 'Create Department', edit: 'Edit Department' }}
@@ -127,7 +133,7 @@ const DepartmentManagementPage: React.FC = () => {
fetchEntityAction={(id) => fetchDepartmentById({ id, skipState: false })}
createEntityAction={createDepartment}
editEntityAction={editDepartment}
- mapDTO={mapDepartmentDTO}
+ mapInput={mapDepartmentInput}
/>
);
};
diff --git a/src/pages/Manage/Employee/pages/EmployeeEdit.tsx b/src/pages/Manage/Employee/pages/EmployeeEdit.tsx
index 4bcdf784..cda1b5b4 100644
--- a/src/pages/Manage/Employee/pages/EmployeeEdit.tsx
+++ b/src/pages/Manage/Employee/pages/EmployeeEdit.tsx
@@ -20,14 +20,15 @@ import {
} from 'store/features';
import { fetchAssignedBranches, fetchAssignedDepartments, editEmployee, fetchEmployeeById, fetchManagers } from 'store/thunks';
import { APP_CONFIG, EMPLOYEE_DETAILS_FORM_DEFAULT_VALUES, EMPLOYEE_DETAILS_FORM_SCHEMA, EMPLOYEE_FIELDS, ROUTES } from 'shared/constants';
-import { Branch, Department, Employee, EmployeeDTO, EntityInput } from 'shared/types';
+import { Employee, EntityInput } from 'shared/types';
import {
areEqualBigIds,
deepCopyObject,
mapBranchToFieldOption,
mapDepartmentToFieldOption,
- mapEmployeeDTO,
+ mapEmployeeInput,
mapEmployeeToFieldOption,
+ getProblemErrors,
} from 'shared/helpers';
import { useOnFormSubmitEffect, useSafeNavigateBack } from 'shared/hooks';
import PageLayout from 'components/layouts/PageLayout';
@@ -59,7 +60,7 @@ const EmployeeEditPage: React.FC = () => {
const safeNavigateBack = useSafeNavigateBack(ROUTES.employees.path);
const [formSubmitted, setFormSubmitted] = React.useState(false);
const defaultValues: EntityInput = employee || EMPLOYEE_DETAILS_FORM_DEFAULT_VALUES;
- const errors = deepCopyObject(updateError?.errors);
+ const errors = deepCopyObject(getProblemErrors(updateError));
const handleAssignedBranchesScrollEnd = () => {
if (assignedBranchesPage.pageNumber! < assignedBranchesPage.totalPages!) {
@@ -93,7 +94,7 @@ const EmployeeEditPage: React.FC = () => {
};
const handleSubmit = (formValue: Employee) => {
- const submitData = mapEmployeeDTO(formValue);
+ const submitData = mapEmployeeInput(formValue);
dispatch(editEmployee([id!, submitData]));
setFormSubmitted(true);
@@ -102,19 +103,19 @@ const EmployeeEditPage: React.FC = () => {
const isBranchOptionAvailable = assignedBranches.some((branchItem) => String(branchItem.id) === String(employee?.assignedBranchId));
const branchItems =
employee?.assignedBranchId && !isBranchOptionAvailable
- ? [{ id: employee.assignedBranchId, name: employee.assignedBranchName } as Branch, ...assignedBranches]
+ ? [{ id: employee.assignedBranchId, name: employee.assignedBranchName ?? '' }, ...assignedBranches]
: assignedBranches;
const isDepartmentOptionAvailable = assignedDepartments.some(
(departmentItem) => String(departmentItem.id) === String(employee?.assignedDepartmentId)
);
const departmentItems =
employee?.assignedDepartmentId && !isDepartmentOptionAvailable
- ? [{ id: employee.assignedDepartmentId, name: employee.assignedDepartmentName } as Department, ...assignedDepartments]
+ ? [{ id: employee.assignedDepartmentId, name: employee.assignedDepartmentName ?? '' }, ...assignedDepartments]
: assignedDepartments;
const isManagerOptionAvailable = managers.some((managerItem) => String(managerItem.id) === String(employee?.reportsToId));
const managerItems =
employee?.reportsToId && !isManagerOptionAvailable
- ? [{ id: employee.reportsToId, name: employee.reportsToName } as unknown as EmployeeDTO, ...managers]
+ ? [{ id: employee.reportsToId, fullName: employee.reportsToName ?? '' }, ...managers]
: managers;
const fields = EMPLOYEE_DETAILS_FORM_SCHEMA.fields.map((field) => {
diff --git a/src/pages/Manage/Offboarding/pages/Company/CompanyDelete.tsx b/src/pages/Manage/Offboarding/pages/Company/CompanyDelete.tsx
index fb7e25f7..e0a642df 100644
--- a/src/pages/Manage/Offboarding/pages/Company/CompanyDelete.tsx
+++ b/src/pages/Manage/Offboarding/pages/Company/CompanyDelete.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { useAppDispatch, useAppSelector } from 'store';
import { selectCompany, selectIsUserAdmin, selectUserRoles } from 'store/features';
import { deleteCompany } from 'store/thunks';
-import { deepCopyObject, hasAllowedRole } from 'shared/helpers';
+import { deepCopyObject, getProblemErrors, hasAllowedRole } from 'shared/helpers';
import { DELETE_COMPANY_DETAILS_FORM_SCHEMA, USER_PERMISSION_GENERAL_ERROR } from 'shared/constants';
import { Company } from 'shared/types';
import Form, { FormActionName } from 'components/UI/Form';
@@ -16,7 +16,7 @@ const CompanyDeletePage: React.FC = () => {
const isUserAdmin = useAppSelector(selectIsUserAdmin);
const { deleteError: error, deleteStatus } = useAppSelector(selectCompany);
const fields = DELETE_COMPANY_DETAILS_FORM_SCHEMA.fields;
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
const actions = DELETE_COMPANY_DETAILS_FORM_SCHEMA.actions.map((action) => {
switch (action.name) {
diff --git a/src/pages/Manage/Offboarding/pages/Company/CompanySettingsDelete.tsx b/src/pages/Manage/Offboarding/pages/Company/CompanySettingsDelete.tsx
index f28f46ef..a0449fc5 100644
--- a/src/pages/Manage/Offboarding/pages/Company/CompanySettingsDelete.tsx
+++ b/src/pages/Manage/Offboarding/pages/Company/CompanySettingsDelete.tsx
@@ -1,9 +1,8 @@
import React from 'react';
-import { FieldErrors, FieldValues } from 'react-hook-form';
import { useAppDispatch, useAppSelector } from 'store';
import { selectCompanySettings, selectIsUserAdmin, selectUserRoles } from 'store/features';
import { deleteCompanySettings } from 'store/thunks';
-import { deepCopyObject, hasAllowedRole } from 'shared/helpers';
+import { createProblemDetails, deepCopyObject, hasAllowedRole, mapError } from 'shared/helpers';
import { DELETE_COMPANY_SETTINGS_DETAILS_FORM_SCHEMA, USER_PERMISSION_GENERAL_ERROR } from 'shared/constants';
import Form, { FormActionName } from 'components/UI/Form';
@@ -16,7 +15,9 @@ const CompanySettingsDeletePage: React.FC = () => {
const isUserAdmin = useAppSelector(selectIsUserAdmin);
const { fetchError: error, deleteStatus } = useAppSelector(selectCompanySettings);
const fields = DELETE_COMPANY_SETTINGS_DETAILS_FORM_SCHEMA.fields;
- const errors = isUserAdmin ? deepCopyObject(error?.errors as FieldErrors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin
+ ? deepCopyObject(error ? mapError(createProblemDetails(error)).errors : undefined)
+ : USER_PERMISSION_GENERAL_ERROR;
const actions = DELETE_COMPANY_SETTINGS_DETAILS_FORM_SCHEMA.actions.map((action) => {
switch (action.name) {
diff --git a/src/pages/Manage/Onboarding/pages/Company/BranchCreate.tsx b/src/pages/Manage/Onboarding/pages/Company/BranchCreate.tsx
index 865b4e37..73a38f5d 100644
--- a/src/pages/Manage/Onboarding/pages/Company/BranchCreate.tsx
+++ b/src/pages/Manage/Onboarding/pages/Company/BranchCreate.tsx
@@ -12,10 +12,11 @@ import { createOnboardingBranch } from 'store/thunks';
import { Branch } from 'shared/types';
import {
getBranchManagementDetailsByAddressFormSchema,
- mapBranchDTO,
+ mapBranchInput,
mapDisabledFields,
mapBranchFieldOptionsToFieldOptions,
deepCopyObject,
+ getProblemErrors,
hasAllowedRole,
} from 'shared/helpers';
import { CREATE_BRANCH_DETAILS_FORM_SCHEMA, BRANCH_DETAILS_FORM_DEFAULT_VALUES, USER_PERMISSION_GENERAL_ERROR } from 'shared/constants';
@@ -37,7 +38,7 @@ const BranchCreatePage: React.FC = () => {
const schema = getBranchManagementDetailsByAddressFormSchema(CREATE_BRANCH_DETAILS_FORM_SCHEMA.fields, !!noPhysicalAddress);
const disabledFields = mapDisabledFields(schema, userRoles);
const fields = mapBranchFieldOptionsToFieldOptions(disabledFields, companyTimeZones, availableCountries);
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
const actions = CREATE_BRANCH_DETAILS_FORM_SCHEMA.actions.map((action) =>
action.name === FormActionName.submit
@@ -46,7 +47,7 @@ const BranchCreatePage: React.FC = () => {
);
const handleSubmit = (formValue: Branch) => {
- const submitData = mapBranchDTO(formValue);
+ const submitData = mapBranchInput(formValue);
dispatch(createOnboardingBranch(submitData));
};
diff --git a/src/pages/Manage/Onboarding/pages/Company/CompanyCreate.tsx b/src/pages/Manage/Onboarding/pages/Company/CompanyCreate.tsx
index 1b77138c..585f79e4 100644
--- a/src/pages/Manage/Onboarding/pages/Company/CompanyCreate.tsx
+++ b/src/pages/Manage/Onboarding/pages/Company/CompanyCreate.tsx
@@ -2,8 +2,8 @@ import React from 'react';
import { useAppDispatch, useAppSelector } from 'store';
import { selectCompany, selectIsUserAdmin, selectUserRoles, selectSystemCountries } from 'store/features';
import { createCompany } from 'store/thunks';
-import { Company, CompanyDTO } from 'shared/types';
-import { deepCopyObject, hasAllowedRole, mapCountriesToFieldOptions, mapDisabledFields } from 'shared/helpers';
+import { Company } from 'shared/types';
+import { deepCopyObject, getProblemErrors, hasAllowedRole, mapCountriesToFieldOptions, mapDisabledFields } from 'shared/helpers';
import { COMPANY_DETAILS_FORM_DEFAULT_VALUES, CREATE_COMPANY_DETAILS_FORM_SCHEMA, USER_PERMISSION_GENERAL_ERROR } from 'shared/constants';
import Form, { FormActionName } from 'components/UI/Form';
@@ -22,9 +22,9 @@ const CompanyCreatePage: React.FC = () => {
? { ...action, disabled: !hasAllowedRole(action.roles, userRoles), loading: updateStatus === 'loading' }
: action
);
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
- const handleSubmit = (data: CompanyDTO) => {
+ const handleSubmit = (data: Company) => {
dispatch(createCompany(data));
};
diff --git a/src/pages/Manage/Onboarding/pages/Company/CompanyLicenseUpload.tsx b/src/pages/Manage/Onboarding/pages/Company/CompanyLicenseUpload.tsx
index ec6857ff..a1ae56a6 100644
--- a/src/pages/Manage/Onboarding/pages/Company/CompanyLicenseUpload.tsx
+++ b/src/pages/Manage/Onboarding/pages/Company/CompanyLicenseUpload.tsx
@@ -11,7 +11,7 @@ import {
setCompanyLicenseSkipped,
resetCompanyLicenseSkipped,
} from 'store/features';
-import { mapDisabledFields, hasAllowedRole, deepCopyObject } from 'shared/helpers';
+import { mapDisabledFields, hasAllowedRole, deepCopyObject, getProblemErrors } from 'shared/helpers';
import { ROUTES, UPLOAD_COMPANY_LICENSE_DETAILS_FORM_SCHEMA, USER_PERMISSION_GENERAL_ERROR } from 'shared/constants';
import Form, { FormActionName } from 'components/UI/Form';
import { renderCopyableField } from 'components/UI/helpers/renderCopyableField';
@@ -27,7 +27,7 @@ const CompanyLicenseUploadPage: React.FC = () => {
const { updateStatus, updateError: error } = useAppSelector(selectCompanyLicense);
const { item: company } = useAppSelector(selectCompany);
const skipRef = React.useRef(false);
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
React.useEffect(() => {
return () => {
diff --git a/src/pages/Manage/Onboarding/pages/Company/CompanySettingsCreate.tsx b/src/pages/Manage/Onboarding/pages/Company/CompanySettingsCreate.tsx
index 3670cf30..a057abb0 100644
--- a/src/pages/Manage/Onboarding/pages/Company/CompanySettingsCreate.tsx
+++ b/src/pages/Manage/Onboarding/pages/Company/CompanySettingsCreate.tsx
@@ -15,8 +15,8 @@ import {
CREATE_COMPANY_SETTINGS_DETAILS_FORM_SCHEMA,
USER_PERMISSION_GENERAL_ERROR,
} from 'shared/constants';
-import { CompanySettings, CompanySettingsDTO, EntityInput, ThemeMode } from 'shared/types';
-import { deepCopyObject, hasAllowedRole, mapDisabledFields } from 'shared/helpers';
+import { CompanySettings, EntityInput, ThemeMode } from 'shared/types';
+import { deepCopyObject, getProblemErrors, hasAllowedRole, mapDisabledFields } from 'shared/helpers';
import { COLOR_SCHEMES } from 'shared/themes';
import { useUnmount } from 'shared/hooks';
import PageLayout from 'components/layouts/PageLayout';
@@ -33,7 +33,7 @@ const CompanySettingsCreatePage: React.FC = () => {
const isUserAdmin = useAppSelector(selectIsUserAdmin);
const mode: ThemeMode = isDarkTheme ? 'dark' : 'light';
const filteredSchemes = Object.fromEntries(Object.entries(COLOR_SCHEMES).filter(([, scheme]) => scheme[mode]));
- const errors = isUserAdmin ? deepCopyObject(error?.errors) : USER_PERMISSION_GENERAL_ERROR;
+ const errors = isUserAdmin ? deepCopyObject(getProblemErrors(error)) : USER_PERMISSION_GENERAL_ERROR;
const fields = mapDisabledFields(CREATE_COMPANY_SETTINGS_DETAILS_FORM_SCHEMA.fields, userRoles).map((field) => {
if (field.name === COMPANY_SETTINGS_FIELDS.colorSchemeId!.field) {
@@ -56,11 +56,11 @@ const CompanySettingsCreatePage: React.FC = () => {
dispatch(resetPreviewCompanyColorSchemeSettings());
});
- const handleSubmit = (data: EntityInput) => {
+ const handleSubmit = (data: EntityInput) => {
dispatch(createCompanySettings(data));
};
- const handleChange = (data: EntityInput) => {
+ const handleChange = (data: EntityInput) => {
const { colorSchemeId } = data;
if (colorSchemeId) {
dispatch(setPreviewCompanyColorSchemeSettings(colorSchemeId));
diff --git a/src/pages/Manage/Onboarding/pages/Employee/EmployeeCreate.tsx b/src/pages/Manage/Onboarding/pages/Employee/EmployeeCreate.tsx
index 701644d3..5cf8277a 100644
--- a/src/pages/Manage/Onboarding/pages/Employee/EmployeeCreate.tsx
+++ b/src/pages/Manage/Onboarding/pages/Employee/EmployeeCreate.tsx
@@ -4,7 +4,7 @@ import { selectProfile } from 'store/features';
import { createProfile } from 'store/thunks';
import { Employee } from 'shared/types';
import { EMPLOYEE_DETAILS_FORM_DEFAULT_VALUES, CREATE_EMPLOYEE_DETAILS_FORM_SCHEMA } from 'shared/constants';
-import { deepCopyObject, mapProfileDTO } from 'shared/helpers';
+import { deepCopyObject, getProblemErrors, mapProfileInput } from 'shared/helpers';
import Form, { FormActionName } from 'components/UI/Form';
const testModule = CREATE_EMPLOYEE_DETAILS_FORM_SCHEMA.module;
@@ -13,14 +13,14 @@ const testSubModule = CREATE_EMPLOYEE_DETAILS_FORM_SCHEMA.subModule;
const EmployeeCreatePage: React.FC = () => {
const dispatch = useAppDispatch();
const { item: profile, updateStatus, updateError: error } = useAppSelector(selectProfile);
- const errors = deepCopyObject(error?.errors);
+ const errors = deepCopyObject(getProblemErrors(error));
const actions = CREATE_EMPLOYEE_DETAILS_FORM_SCHEMA.actions.map((action) =>
action.name === FormActionName.submit ? { ...action, loading: updateStatus === 'loading' } : action
);
const handleSubmit = (formValue: Employee) => {
- const submitData = mapProfileDTO(formValue);
+ const submitData = mapProfileInput(formValue);
dispatch(createProfile(submitData));
};
diff --git a/src/pages/Manage/Profile/pages/EditProfile.tsx b/src/pages/Manage/Profile/pages/EditProfile.tsx
index 56e437bb..aa482f17 100644
--- a/src/pages/Manage/Profile/pages/EditProfile.tsx
+++ b/src/pages/Manage/Profile/pages/EditProfile.tsx
@@ -5,7 +5,7 @@ import { resetProfileFetchStatus, selectProfile } from 'store/features';
import { editProfile } from 'store/thunks';
import { EMPLOYEE_DETAILS_FORM_DEFAULT_VALUES, PROFILE_DETAILS_FORM_SCHEMA, ROUTES } from 'shared/constants';
import { Employee, EntityInput } from 'shared/types';
-import { deepCopyObject, mapProfileDTO } from 'shared/helpers';
+import { deepCopyObject, getProblemErrors, mapProfileInput } from 'shared/helpers';
import { useOnFormSubmitEffect } from 'shared/hooks';
import PageLayout from 'components/layouts/PageLayout';
import Form, { FormActionName } from 'components/UI/Form';
@@ -19,7 +19,7 @@ const EditProfilePage: React.FC = () => {
const { item: profile, updateError: error, updateStatus = 'idle' } = useAppSelector(selectProfile);
const [formSubmitted, setFormSubmitted] = React.useState(false);
const defaultValues: EntityInput = profile || EMPLOYEE_DETAILS_FORM_DEFAULT_VALUES;
- const errors = deepCopyObject(error?.errors);
+ const errors = deepCopyObject(getProblemErrors(error));
const navigateToViewProfile = () => {
navigate(ROUTES.viewProfile.path);
@@ -47,7 +47,7 @@ const EditProfilePage: React.FC = () => {
});
const handleSubmit = (formValue: EntityInput) => {
- const submitData = mapProfileDTO(formValue);
+ const submitData = mapProfileInput(formValue);
dispatch(editProfile(submitData));
setFormSubmitted(true);
diff --git a/src/pages/Root.tsx b/src/pages/Root.tsx
index 10c599a3..2a6b494e 100644
--- a/src/pages/Root.tsx
+++ b/src/pages/Root.tsx
@@ -9,7 +9,6 @@ import { useAppTheme } from 'shared/hooks';
import { darkScrollbar, getTestSelectorByModule, lightScrollbar } from 'shared/helpers';
import { Module, SubModule } from 'shared/types';
import MessageLayout from 'layout/MessageLayout';
-import AxiosInterceptor from '../AxiosInterceptor';
import ClientLoader from '../ClientLoader';
const RootPage: React.FC = () => {
@@ -23,20 +22,18 @@ const RootPage: React.FC = () => {
});
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
};
diff --git a/src/shared/configs/BridgeClients.ts b/src/shared/configs/BridgeClients.ts
new file mode 100644
index 00000000..68e482b0
--- /dev/null
+++ b/src/shared/configs/BridgeClients.ts
@@ -0,0 +1,25 @@
+import { HttpTransport_$ctor_3A0BE0FB } from '@fossa-app/bridge/Services/HttpTransport';
+import { JsonSerializer_$ctor } from '@fossa-app/bridge/Services/JsonSerializer';
+import { Clients_$ctor_Z7C557C0 } from '@fossa-app/bridge/Services/Clients';
+import { IClients } from '@fossa-app/bridge/Services/IClients';
+
+import { AppAccessTokenProvider, FetchHttpRequestSender } from './BridgeTransport';
+
+// Initialize the shared Fable HttpTransport
+const sender = new FetchHttpRequestSender();
+const serializer = JsonSerializer_$ctor();
+const tokenProvider = new AppAccessTokenProvider();
+
+export const httpTransport = HttpTransport_$ctor_3A0BE0FB(sender, serializer, tokenProvider);
+
+export const clients: IClients = Clients_$ctor_Z7C557C0(httpTransport);
+
+// Export strongly typed Fable clients
+export const branchClient = clients.BranchClient;
+export const companyClient = clients.CompanyClient;
+export const companySettingsClient = clients.CompanySettingsClient;
+export const departmentClient = clients.DepartmentClient;
+export const employeeClient = clients.EmployeeClient;
+export const identityClient = clients.IdentityClient;
+export const systemLicenseClient = clients.SystemLicenseClient;
+export const companyLicenseClient = clients.CompanyLicenseClient;
diff --git a/src/shared/configs/BridgeResponses.ts b/src/shared/configs/BridgeResponses.ts
new file mode 100644
index 00000000..e924aa96
--- /dev/null
+++ b/src/shared/configs/BridgeResponses.ts
@@ -0,0 +1,79 @@
+import type { ClientResult$1_$union } from '@fossa-app/bridge/Models/ApiModels/ClientResults';
+import type { ClientUnitResult_$union } from '@fossa-app/bridge/Models/ApiModels/ClientUnitResults';
+import type { PagingResponseModel$1 } from '@fossa-app/bridge/Models/ApiModels/EnvelopeModels';
+import {
+ matchClientResult as matchBridgeClientResult,
+ matchClientUnitResult as matchBridgeClientUnitResult,
+ unwrapClientResult,
+ unwrapClientUnitResult,
+} from '@fossa-app/bridge/Models/ApiModels/Helpers/ClientResultHelpers';
+import type { ProblemDetailsModel, PaginatedResponse } from 'shared/types';
+import { createProblemDetails } from 'shared/helpers/error.helpers';
+
+export const matchClientResult = (
+ result: unknown,
+ onSuccess: (value: any) => TSuccess,
+ onFailure: (problem: ProblemDetailsModel) => TFailure
+): TSuccess | TFailure => {
+ return matchBridgeClientResult(result as ClientResult$1_$union, onSuccess, onFailure);
+};
+
+export const matchClientUnitResult = (
+ result: unknown,
+ onSuccess: () => TSuccess,
+ onFailure: (problem: ProblemDetailsModel) => TFailure
+): TSuccess | TFailure => {
+ return matchBridgeClientUnitResult(result as ClientUnitResult_$union, onSuccess, onFailure);
+};
+
+const toNumber = (value: unknown): number | undefined => {
+ if (value === null || value === undefined) {
+ return undefined;
+ }
+
+ return Number(value);
+};
+
+export const unwrapBridgeValue = (result: unknown): T => {
+ return matchClientResult(
+ result as ClientResult$1_$union,
+ (value) => value,
+ (problem) => {
+ throw createProblemDetails(problem);
+ }
+ );
+};
+
+export const unwrapBridgeUnitResult = (result: unknown): void => {
+ matchClientUnitResult(
+ result as ClientUnitResult_$union,
+ () => undefined,
+ (problem) => {
+ throw createProblemDetails(problem);
+ }
+ );
+};
+
+export const toPaginatedResponse = (value: unknown): PaginatedResponse => {
+ const response = value as PagingResponseModel$1;
+ const items = [...(response.Items ?? [])] as T[];
+
+ return {
+ pageNumber: toNumber(response.PageNumber),
+ pageSize: toNumber(response.PageSize),
+ totalItems: toNumber(response.TotalItems),
+ totalPages: toNumber(response.TotalPages),
+ items,
+ };
+};
+
+export const unwrapBridgePagingResponse = (result: unknown): PaginatedResponse => {
+ return toPaginatedResponse(unwrapBridgeValue>(result));
+};
+
+export const createBridgeProblem = (problem: ProblemDetailsModel, overrides?: Partial): ProblemDetailsModel => {
+ return createProblemDetails(problem, overrides);
+};
+
+export const toBridgeValue = unwrapClientResult;
+export const toBridgeUnit = unwrapClientUnitResult;
diff --git a/src/shared/configs/BridgeTransport.ts b/src/shared/configs/BridgeTransport.ts
new file mode 100644
index 00000000..95a5936b
--- /dev/null
+++ b/src/shared/configs/BridgeTransport.ts
@@ -0,0 +1,114 @@
+import { IAccessTokenProvider } from '@fossa-app/bridge/Services/IAccessTokenProvider';
+import { IHttpRequestSender, HttpRequestMessage, HttpResponseMessage } from '@fossa-app/bridge/Services/IHttpRequestSender';
+import { unwrap } from '@fable-org/fable-library-ts/Option';
+import { createProblemDetails, getUserManager } from 'shared/helpers';
+import { MESSAGES } from 'shared/constants';
+import store from 'store';
+import { setError, removeUser } from 'store/features';
+import { getBackendOrigin } from '@fossa-app/bridge/Services/UrlHelpers';
+
+export class AppAccessTokenProvider implements IAccessTokenProvider {
+ async GetTokenAsync(_cancellationToken: AbortSignal): Promise {
+ const userManager = getUserManager();
+ let user = await userManager.getUser();
+
+ if (!user || user.expired) {
+ try {
+ user = await userManager.signinSilent();
+ } catch {
+ return '';
+ }
+ }
+
+ return user?.access_token || '';
+ }
+}
+
+export class FetchHttpRequestSender implements IHttpRequestSender {
+ async SendAsync(request: HttpRequestMessage, cancellationToken: AbortSignal): Promise {
+ const methodMap: Record = {
+ 0: 'GET',
+ 1: 'POST',
+ 2: 'PUT',
+ 3: 'PATCH',
+ 4: 'DELETE',
+ };
+
+ const method = methodMap[request.Method.tag];
+
+ const beOrigin = getBackendOrigin(window.location.origin);
+ const url = `${beOrigin}/${request.Uri.replace(/^\//, '')}`;
+
+ const body = unwrap(request.Content);
+
+ const headers = new Headers();
+ if (request.Headers) {
+ const headersArray = Array.from(request.Headers);
+ headersArray.forEach(([key, value]) => {
+ headers.set(key, value);
+ });
+ }
+
+ // Default fetch config
+ const init: RequestInit = {
+ method,
+ headers,
+ signal: cancellationToken,
+ };
+
+ if (body !== undefined && body !== null) {
+ init.body = body as string;
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(url, init);
+ } catch (_error) {
+ const problem = createProblemDetails(undefined, { Status: 599, Title: MESSAGES.error.general.network });
+
+ store.dispatch(setError(problem));
+
+ return new HttpResponseMessage(problem.Status, JSON.stringify(problem));
+ }
+
+ if (response.status === 401) {
+ const userManager = getUserManager();
+ try {
+ await userManager.removeUser();
+ } catch {
+ // Ignored
+ }
+
+ store.dispatch(removeUser());
+ const problem = createProblemDetails(undefined, { Status: 401, Title: MESSAGES.error.general.unAuthorized });
+ store.dispatch(setError(problem));
+
+ if (typeof window !== 'undefined') {
+ window.location.href = '/login';
+ }
+
+ return new HttpResponseMessage(problem.Status, JSON.stringify(problem));
+ }
+
+ if (!response.ok) {
+ let data = {};
+ try {
+ data = await response.json();
+ } catch {
+ // Ignored
+ }
+
+ if (response.status >= 500) {
+ const problem = createProblemDetails(data, { Status: response.status, Title: MESSAGES.error.general.common });
+ store.dispatch(setError(problem));
+
+ return new HttpResponseMessage(problem.Status, JSON.stringify(problem));
+ }
+
+ return new HttpResponseMessage(response.status, JSON.stringify(data));
+ }
+
+ const text = await response.text();
+ return new HttpResponseMessage(response.status, text);
+ }
+}
diff --git a/src/shared/configs/axios.ts b/src/shared/configs/axios.ts
deleted file mode 100644
index 527b3f22..00000000
--- a/src/shared/configs/axios.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import axios, { CreateAxiosDefaults } from 'axios';
-import { APP_CONFIG, ENDPOINTS } from 'shared/constants';
-import { getBackendOrigin } from 'shared/helpers';
-
-const origin = window.location.origin;
-const beOrigin = getBackendOrigin(origin);
-
-const defaultConfigs: CreateAxiosDefaults = {
- baseURL: `${beOrigin}/${ENDPOINTS.base}`,
- timeout: APP_CONFIG.httpTimeout,
- headers: {
- 'Content-Type': 'application/json',
- },
- // BE sends big numbers in the response, parsing manually in AxiosInterceptor
- transformResponse: [(response) => response],
-};
-
-const instance = axios.create(defaultConfigs);
-
-export * from 'axios';
-export default instance;
diff --git a/src/shared/constants/configs.ts b/src/shared/constants/configs.ts
index 5490d34d..89afd1fd 100644
--- a/src/shared/constants/configs.ts
+++ b/src/shared/constants/configs.ts
@@ -1,6 +1,8 @@
import { OidcClientSettings } from 'oidc-client-ts';
import { FieldErrors, FieldValues } from 'react-hook-form';
-import { ColorSchemeId, CompanySettings, EntityInput, PaginationParams } from 'shared/types';
+import type { CompanySettings } from 'shared/types/company-settings';
+import type { EntityInput } from 'shared/types/common';
+import type { ColorSchemeId } from 'shared/types/theme';
export const OIDC_INITIAL_CONFIG: OidcClientSettings = {
authority: 'http://localhost:9011',
@@ -15,7 +17,7 @@ export const APP_CONFIG = {
snackbarAutoHideDuration: 10000,
httpTimeout: 15000,
table: {
- defaultPagination: { pageNumber: 1, pageSize: 10, search: '', totalItems: undefined } as PaginationParams,
+ defaultPagination: { pageNumber: 1, pageSize: 10, search: '', totalItems: undefined, totalPages: undefined },
defaultPageSizeOptions: [10, 20, 50],
containerWidth: 'calc(100vw - 32px)',
containerMaxHeight: 'calc(100% - 70px)',
diff --git a/src/shared/constants/endpoints.ts b/src/shared/constants/endpoints.ts
deleted file mode 100644
index 728d89ae..00000000
--- a/src/shared/constants/endpoints.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export const ENDPOINTS = {
- base: 'api/1',
- client: 'Identity/Client',
- systemLicense: 'License/System',
- companyLicense: 'License/Company',
- company: 'Company',
- companySettings: 'CompanySettings',
- branches: 'Branches',
- departments: 'Departments',
- employee: 'Employee',
- employees: 'Employees',
-};
diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts
index 05cf5035..f87b0ffa 100644
--- a/src/shared/constants/index.ts
+++ b/src/shared/constants/index.ts
@@ -1,4 +1,3 @@
-export * from './endpoints';
export * from './routes';
export * from './configs';
export * from './flows';
diff --git a/src/shared/constants/routes.ts b/src/shared/constants/routes.ts
index 37a48d17..54516636 100644
--- a/src/shared/constants/routes.ts
+++ b/src/shared/constants/routes.ts
@@ -1,4 +1,4 @@
-import { AppRoute } from 'shared/types';
+import type { AppRoute } from 'shared/types/route';
// TODO: use relative routes instead
export const ROUTES: AppRoute = {
diff --git a/src/shared/helpers/branch.helpers.ts b/src/shared/helpers/branch.helpers.ts
index f55f0824..c3b85383 100644
--- a/src/shared/helpers/branch.helpers.ts
+++ b/src/shared/helpers/branch.helpers.ts
@@ -1,5 +1,5 @@
import { BRANCH_FIELDS } from 'shared/constants';
-import { Branch, BranchDTO, EntityInput, Company, Country, GeoAddress, TimeZone } from 'shared/types';
+import { Branch, EntityInput, Company, Country, GeoAddress, TimeZone } from 'shared/types';
import { FormFieldProps, FieldOption } from 'components/UI/Form';
import { mapCountryToFieldOption } from './company.helpers';
@@ -10,7 +10,7 @@ export const mapBranch = ({
countries,
geoAddress,
}: {
- branch: BranchDTO;
+ branch: Branch;
timeZones: TimeZone[];
companyCountryCode: Company['countryCode'];
countries: Country[];
@@ -37,7 +37,7 @@ export const mapBranch = ({
};
};
-export const mapBranchDTO = (branch: Branch): EntityInput => {
+export const mapBranchInput = (branch: Branch): EntityInput => {
if (branch.noPhysicalAddress) {
return {
name: branch.name,
@@ -71,7 +71,7 @@ export const mapBranchDTO = (branch: Branch): EntityInput => {
};
export const mapBranches = (
- branches: BranchDTO[],
+ branches: Branch[],
timeZones: TimeZone[],
companyCountryCode: Company['countryCode'],
countries: Country[]
@@ -111,7 +111,7 @@ export const mapTimeZoneToFieldOption = (timeZone: TimeZone): FieldOption => {
};
};
-export const mapBranchToFieldOption = (branch: Branch): FieldOption => {
+export const mapBranchToFieldOption = (branch: Pick): FieldOption => {
return {
label: branch.name,
value: String(branch?.id),
diff --git a/src/shared/helpers/company.helpers.ts b/src/shared/helpers/company.helpers.ts
index afa46083..a4369ac5 100644
--- a/src/shared/helpers/company.helpers.ts
+++ b/src/shared/helpers/company.helpers.ts
@@ -1,8 +1,8 @@
-import { Company, CompanyDTO, Country } from 'shared/types';
+import { Company, Country } from 'shared/types';
import { COMPANY_LICENSE_FIELDS } from 'shared/constants';
import { FormFieldProps, FormFieldType, FieldOption } from 'components/UI/Form';
-export const mapCompany = (company: CompanyDTO, countries: Country[]): Company => {
+export const mapCompany = (company: Company, countries: Country[]): Company => {
return {
...company,
countryName: countries.find(({ code }) => code === company.countryCode)?.name,
diff --git a/src/shared/helpers/data.helpers.ts b/src/shared/helpers/data.helpers.ts
index d5927bb5..a170b1d2 100644
--- a/src/shared/helpers/data.helpers.ts
+++ b/src/shared/helpers/data.helpers.ts
@@ -1,3 +1,5 @@
+import { JsonSerializer } from '@fossa-app/bridge/Services/JsonSerializer';
+
export const parseResponse = (response: any): T => {
if (response.data === '') {
return response as T;
@@ -8,20 +10,10 @@ export const parseResponse = (response: any): T => {
}
if (typeof response.data === 'string') {
- const isBigNumber = (num: string | number): boolean => {
- const n = Number(num);
- return !isNaN(n) && !Number.isSafeInteger(n);
- };
-
- const enquoteBigNumber = (jsonString: string, bigNumChecker: (num: string | number) => boolean): string =>
- jsonString.replace(/(:\s*)(\d{15,})(\s*[,}])/g, (match, prefix, numberPart, suffix) =>
- bigNumChecker(numberPart) ? `${prefix}"${numberPart}"${suffix}` : match
- );
-
try {
return {
...response,
- data: JSON.parse(enquoteBigNumber(response.data, isBigNumber)),
+ data: new JsonSerializer().Deserialize(response.data),
} as T;
} catch (error) {
console.error('Error parsing response data:', error);
diff --git a/src/shared/helpers/department.helpers.ts b/src/shared/helpers/department.helpers.ts
index 17f37a82..41957e8f 100644
--- a/src/shared/helpers/department.helpers.ts
+++ b/src/shared/helpers/department.helpers.ts
@@ -1,9 +1,9 @@
import { DEPARTMENT_FIELDS } from 'shared/constants';
-import { Department, DepartmentDTO, Employee, EmployeeDTO, EntityInput } from 'shared/types';
+import { Department, Employee, EntityInput } from 'shared/types';
import { FormFieldProps, FieldOption } from 'components/UI/Form';
import { mapEmployeeToFieldOption } from './employee.helpers';
-export const mapDepartment = (department: DepartmentDTO, parentDepartment?: DepartmentDTO, employee?: EmployeeDTO): Department => {
+export const mapDepartment = (department: Department, parentDepartment?: Department, employee?: Employee): Department => {
const managerName = employee?.fullName;
return {
@@ -13,7 +13,7 @@ export const mapDepartment = (department: DepartmentDTO, parentDepartment?: Depa
};
};
-export const mapDepartmentDTO = (department: Department): EntityInput => {
+export const mapDepartmentInput = (department: Department): EntityInput => {
return {
name: department.name,
parentDepartmentId: department.parentDepartmentId || null,
@@ -22,9 +22,9 @@ export const mapDepartmentDTO = (department: Department): EntityInput {
return departments.map((department) => {
const manager = employees.find(({ id }) => id === department.managerId);
@@ -36,8 +36,8 @@ export const mapDepartments = (
export const mapDepartmentFieldOptionsToFieldOptions = (
fields: FormFieldProps[],
- departments?: Department[],
- employees?: Employee[]
+ departments?: Pick[],
+ employees?: Pick[]
): FormFieldProps[] => {
return fields.map((field) => ({
...field,
@@ -52,7 +52,7 @@ export const mapDepartmentFieldOptionsToFieldOptions = (
}));
};
-export const mapDepartmentToFieldOption = (department: Department): FieldOption => {
+export const mapDepartmentToFieldOption = (department: Pick): FieldOption => {
return {
label: department.name,
value: String(department.id),
diff --git a/src/shared/helpers/employee.helpers.ts b/src/shared/helpers/employee.helpers.ts
index dc6ef447..df360e12 100644
--- a/src/shared/helpers/employee.helpers.ts
+++ b/src/shared/helpers/employee.helpers.ts
@@ -1,4 +1,4 @@
-import { AppUser, Branch, BranchDTO, Department, DepartmentDTO, Employee, EmployeeDTO, EntityInput } from 'shared/types';
+import { AppUser, Branch, Department, Employee, EntityInput } from 'shared/types';
import { FieldOption } from 'components/UI/Form';
import { mapUserProfileToEmployee } from './user.helpers';
@@ -9,7 +9,7 @@ export const mapEmployee = ({
department,
manager,
}: {
- employee: EmployeeDTO;
+ employee: Employee;
user?: AppUser;
branch?: Branch;
department?: Department;
@@ -34,10 +34,10 @@ export const mapEmployees = ({
departments = [],
managers = [],
}: {
- employees: EmployeeDTO[];
- branches?: BranchDTO[];
- departments?: DepartmentDTO[];
- managers?: EmployeeDTO[];
+ employees: Employee[];
+ branches?: Branch[];
+ departments?: Department[];
+ managers?: Employee[];
}): Employee[] => {
return employees.map((employee) => {
const branch = branches.find(({ id }) => id === employee.assignedBranchId);
@@ -48,7 +48,7 @@ export const mapEmployees = ({
});
};
-export const mapEmployeeDTO = (employee: Employee): Omit, 'firstName' | 'lastName' | 'fullName'> => {
+export const mapEmployeeInput = (employee: Employee): Omit, 'firstName' | 'lastName' | 'fullName'> => {
return {
jobTitle: employee.jobTitle,
assignedBranchId: employee.assignedBranchId || null,
@@ -57,7 +57,7 @@ export const mapEmployeeDTO = (employee: Employee): Omit): EntityInput => {
+export const mapProfileInput = (employee: EntityInput): EntityInput => {
return {
firstName: employee.firstName,
lastName: employee.lastName,
@@ -69,7 +69,7 @@ export const mapProfileDTO = (employee: EntityInput): EntityInput {
+export const mapEmployeeToFieldOption = (employee: Pick): FieldOption => {
return {
label: employee.fullName,
value: String(employee.id),
diff --git a/src/shared/helpers/error.helpers.ts b/src/shared/helpers/error.helpers.ts
index 05748faf..d0cf6338 100644
--- a/src/shared/helpers/error.helpers.ts
+++ b/src/shared/helpers/error.helpers.ts
@@ -1,15 +1,83 @@
import { FieldErrors, FieldValues } from 'react-hook-form';
-import { ErrorResponseDTO, ErrorResponse } from 'shared/types';
+import { ProblemDetailsModel as BridgeProblemDetailsModel } from '@fossa-app/bridge/Models/ApiModels/SharedModels';
+import type { ProblemDetailsModel, ErrorResponse } from 'shared/types';
import { FormFieldProps } from 'components/UI/Form';
-export const mapError = (error: ErrorResponseDTO): ErrorResponse => {
+type ProblemDetailsPatch = Partial>;
+
+const asRecord = (value: unknown): Record => {
+ return value && typeof value === 'object' ? (value as Record) : {};
+};
+
+const read = (
+ value: unknown,
+ pascalName: string,
+ camelName = `${pascalName.charAt(0).toLowerCase()}${pascalName.slice(1)}`
+): T | undefined => {
+ const record = asRecord(value);
+
+ return (record[camelName] ?? record[pascalName]) as T | undefined;
+};
+
+export const getProblemTitle = (problem: unknown): string | undefined => {
+ return read(problem, 'Title');
+};
+
+export const getProblemStatus = (problem: unknown): number | undefined => {
+ const status = read(problem, 'Status');
+
+ return status === null || status === undefined ? undefined : Number(status);
+};
+
+export const getProblemErrors = (problem: unknown): Record | undefined => {
+ const errors = read(problem, 'Errors');
+
+ if (!errors) {
+ return undefined;
+ }
+
+ if (errors instanceof Map) {
+ return Object.fromEntries(errors.entries()) as Record;
+ }
+
+ return typeof errors === 'object' ? (errors as Record) : undefined;
+};
+
+const toProblemErrorsMap = (errors: unknown): Map => {
+ if (errors instanceof Map) {
+ return new Map(errors.entries()) as Map;
+ }
+
+ if (!errors || typeof errors !== 'object') {
+ return new Map();
+ }
+
+ return new Map(Object.entries(errors as Record));
+};
+
+export const createProblemDetails = (problem?: unknown, overrides: ProblemDetailsPatch = {}): ProblemDetailsModel => {
+ const status = read(overrides, 'Status') ?? read(problem, 'Status');
+
+ return new BridgeProblemDetailsModel(
+ read(overrides, 'Type') ?? read(problem, 'Type') ?? null,
+ read(overrides, 'Title') ?? read(problem, 'Title') ?? null,
+ status === null || status === undefined ? 0 : Number(status),
+ read(overrides, 'Detail') ?? read(problem, 'Detail') ?? null,
+ read(overrides, 'Instance') ?? read(problem, 'Instance') ?? null,
+ toProblemErrorsMap(read(overrides, 'Errors') ?? read(problem, 'Errors')),
+ read(overrides, 'TraceId') ?? read(problem, 'TraceId') ?? null
+ );
+};
+
+export const mapError = (error: ProblemDetailsModel): ErrorResponse => {
const errors: FieldErrors = {} as FieldErrors;
+ const problemErrors = getProblemErrors(error);
- if (!error.errors) {
+ if (!problemErrors) {
return { ...error, errors };
}
- Object.entries(error.errors).forEach(([key, messages]) => {
+ Object.entries(problemErrors).forEach(([key, messages]) => {
const pathParts = key.split('.').map((part) => part.charAt(0).toLowerCase() + part.slice(1));
let current = errors as FieldErrors;
diff --git a/src/shared/helpers/url.helpers.ts b/src/shared/helpers/url.helpers.ts
index c7838a5e..fee3c86f 100644
--- a/src/shared/helpers/url.helpers.ts
+++ b/src/shared/helpers/url.helpers.ts
@@ -1,20 +1,3 @@
-export const getBackendOrigin = (frontendOrigin: string): string => {
- const suffixMappings = new Map([
- ['.dev.localhost:4211', '.dev.localhost:5210'],
- ['.test.localhost:4210', '.test.localhost:5211'],
- ['.test.localhost:4211', '.test.localhost:5211'],
- ['.localhost:4210', '.localhost:5210'],
- ]);
-
- for (const [frontendSuffix, backendSuffix] of suffixMappings) {
- if (frontendOrigin.endsWith(frontendSuffix)) {
- return `${frontendOrigin.slice(0, frontendOrigin.indexOf(frontendSuffix))}${backendSuffix}`;
- }
- }
-
- return frontendOrigin;
-};
-
export const prepareQueryParams = (params: Record): string => {
const filtered = Object.fromEntries(Object.entries(params).filter(([, value]) => value !== null && value !== undefined && value !== ''));
diff --git a/src/shared/helpers/user.helpers.ts b/src/shared/helpers/user.helpers.ts
index b98fbe4d..18243910 100644
--- a/src/shared/helpers/user.helpers.ts
+++ b/src/shared/helpers/user.helpers.ts
@@ -13,7 +13,7 @@ export const mapUserProfileToEmployee = (userProfile?: UserProfile): Employee |
}
return {
- id: userProfile.sub as unknown as number,
+ id: Number(userProfile.sub),
firstName: userProfile.given_name!,
lastName: userProfile.family_name!,
fullName: userProfile.name!,
diff --git a/src/shared/types/branch.ts b/src/shared/types/branch.ts
index a89bce51..36e82f22 100644
--- a/src/shared/types/branch.ts
+++ b/src/shared/types/branch.ts
@@ -1,31 +1,24 @@
import { Country, FlattenField, GeoAddress, NonNullableFields, TimeZone } from './common';
-export interface AddressDTO {
+export interface Address {
line1?: string;
line2?: string;
city?: string;
subdivision?: string;
postalCode?: string;
countryCode?: Country['code'];
-}
-
-export interface Address extends AddressDTO {
countryName?: string;
}
-export interface BranchDTO {
+export interface Branch {
id: number;
companyId?: number;
name: string;
timeZoneId: TimeZone['id'];
- address: AddressDTO | null;
-}
-
-export interface Branch extends BranchDTO {
+ address: Address | null;
timeZoneName?: TimeZone['name'];
isValid?: boolean;
noPhysicalAddress?: boolean;
- address: Address | null;
fullAddress?: string;
geoAddress?: GeoAddress;
}
diff --git a/src/shared/types/client.ts b/src/shared/types/client.ts
deleted file mode 100644
index 6748c2a9..00000000
--- a/src/shared/types/client.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface Client {
- clientId: string;
- clientName: string;
- tenantId: string;
-}
diff --git a/src/shared/types/company-settings.ts b/src/shared/types/company-settings.ts
index 4bb49208..4a2d9eee 100644
--- a/src/shared/types/company-settings.ts
+++ b/src/shared/types/company-settings.ts
@@ -1,13 +1,11 @@
-import { ColorSchemeId } from 'shared/types';
+import type { ColorSchemeId } from './theme';
-export interface CompanySettingsDTO {
+export interface CompanySettings {
id: number;
companyId?: number;
colorSchemeId?: ColorSchemeId;
}
-export interface CompanySettings extends CompanySettingsDTO {}
-
export type CompanySettingsFieldConfig = {
[K in keyof CompanySettings]: { field: K; name: string };
};
diff --git a/src/shared/types/company.ts b/src/shared/types/company.ts
index 65d7fb90..f88bb83b 100644
--- a/src/shared/types/company.ts
+++ b/src/shared/types/company.ts
@@ -1,13 +1,10 @@
import { Country, FlattenField } from './common';
import { CompanyLicense } from './license';
-export interface CompanyDTO {
+export interface Company {
id: number;
name: string;
countryCode: Country['code'];
-}
-
-export interface Company extends CompanyDTO {
countryName?: Country['name'];
}
diff --git a/src/shared/types/department.ts b/src/shared/types/department.ts
index 13410ab4..53faebb7 100644
--- a/src/shared/types/department.ts
+++ b/src/shared/types/department.ts
@@ -1,11 +1,8 @@
-export interface DepartmentDTO {
+export interface Department {
id: number;
name: string;
parentDepartmentId: number | null;
managerId: number | null;
-}
-
-export interface Department extends DepartmentDTO {
parentDepartmentName?: string;
managerName?: string;
}
diff --git a/src/shared/types/employee.ts b/src/shared/types/employee.ts
index 03b3b558..0e4de312 100644
--- a/src/shared/types/employee.ts
+++ b/src/shared/types/employee.ts
@@ -1,4 +1,4 @@
-export interface EmployeeDTO {
+export interface Employee {
id: number;
companyId?: number;
assignedBranchId: number | null;
@@ -8,9 +8,6 @@ export interface EmployeeDTO {
firstName: string;
lastName: string;
fullName: string;
-}
-
-export interface Employee extends EmployeeDTO {
picture?: string;
isDraft?: boolean;
assignedBranchName?: string;
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts
index 5856a7b5..4c2428f5 100644
--- a/src/shared/types/index.ts
+++ b/src/shared/types/index.ts
@@ -1,4 +1,3 @@
-export * from './client';
export * from './route';
export * from './license';
export * from './response';
diff --git a/src/shared/types/response.ts b/src/shared/types/response.ts
index 896efc78..09fddb91 100644
--- a/src/shared/types/response.ts
+++ b/src/shared/types/response.ts
@@ -1,26 +1,22 @@
import { FieldErrors, FieldValues } from 'react-hook-form';
+import type { ProblemDetailsModel } from '@fossa-app/bridge/Models/ApiModels/SharedModels';
-export interface ErrorResponseDTO {
- type?: string;
- title?: string;
- traceId?: string;
- status?: number;
- errors?: Record;
-}
+type PagingResponseModel = import('@fossa-app/bridge/Models/ApiModels/EnvelopeModels').PagingResponseModel$1;
+export type { ProblemDetailsModel } from '@fossa-app/bridge/Models/ApiModels/SharedModels';
-export interface ErrorResponse extends Omit {
+export type ErrorResponse = Partial & {
errors?: FieldErrors;
-}
+};
-export type GeneralErrorResponse = ErrorResponseDTO | ErrorResponse;
+export type GeneralErrorResponse = ProblemDetailsModel | ErrorResponse;
-export interface PaginatedResponse {
+export type PaginatedResponse = Partial> & {
pageNumber?: number;
pageSize?: number;
totalItems?: number;
totalPages?: number;
items: T[];
-}
+};
export interface PaginationParams {
pageNumber?: number;
diff --git a/src/store/features/authSlice.ts b/src/store/features/authSlice.ts
index 4cdac419..06c8ed9e 100644
--- a/src/store/features/authSlice.ts
+++ b/src/store/features/authSlice.ts
@@ -3,7 +3,7 @@ import { OidcClientSettings } from 'oidc-client-ts';
import { RootState, StateEntity } from 'store';
import { fetchUser } from 'store/thunks';
import { updateUserManager, decodeJwt } from 'shared/helpers';
-import { AppUser, ErrorResponseDTO, UserRole } from 'shared/types';
+import { AppUser, ProblemDetailsModel, UserRole } from 'shared/types';
import { OIDC_INITIAL_CONFIG } from 'shared/constants';
interface AuthState {
@@ -45,7 +45,7 @@ const authSlice = createSlice({
.addCase(fetchUser.pending, (state) => {
state.user.fetchStatus = 'loading';
})
- .addCase(fetchUser.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchUser.rejected, (state, action: PayloadAction) => {
state.user.item = undefined;
state.user.fetchStatus = 'failed';
state.user.fetchError = action.payload;
diff --git a/src/store/features/branchSlice.ts b/src/store/features/branchSlice.ts
index cf11647f..0e8d45e3 100644
--- a/src/store/features/branchSlice.ts
+++ b/src/store/features/branchSlice.ts
@@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { FieldValues } from 'react-hook-form';
import { WritableDraft } from 'immer';
import { RootState, PaginatedStateEntity, StateEntity } from 'store';
-import { Branch, BranchDTO, ErrorResponseDTO, ErrorResponse, PaginatedResponse, PaginationParams } from 'shared/types';
+import { Branch, ProblemDetailsModel, ErrorResponse, PaginatedResponse, PaginationParams } from 'shared/types';
import { APP_CONFIG } from 'shared/constants';
import { mergePaginatedItems } from 'store/helpers';
import {
@@ -18,7 +18,7 @@ import {
interface BranchState {
branch: StateEntity;
branchCatalog: PaginatedStateEntity;
- assignedBranches: PaginatedStateEntity;
+ assignedBranches: PaginatedStateEntity;
}
const initialState: BranchState = {
@@ -73,7 +73,7 @@ const branchSlice = createSlice({
.addCase(fetchBranches.pending, (state) => {
state.branchCatalog.status = 'loading';
})
- .addCase(fetchBranches.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchBranches.rejected, (state, action: PayloadAction) => {
state.branchCatalog.items = [];
state.branchCatalog.status = 'failed';
state.branchCatalog.error = action.payload;
@@ -92,7 +92,7 @@ const branchSlice = createSlice({
.addCase(fetchAssignedBranches.fulfilled, (state, action) => {
const { items = [], ...page } = action.payload || {};
- state.assignedBranches.items = mergePaginatedItems(state.assignedBranches.items, items);
+ state.assignedBranches.items = mergePaginatedItems(state.assignedBranches.items, items);
state.assignedBranches.page = page;
state.assignedBranches.status = 'succeeded';
})
@@ -160,7 +160,7 @@ const branchSlice = createSlice({
.addCase(deleteBranch.pending, (state) => {
state.branch.deleteStatus = 'loading';
})
- .addCase(deleteBranch.rejected, (state, action: PayloadAction) => {
+ .addCase(deleteBranch.rejected, (state, action: PayloadAction) => {
state.branch.deleteStatus = 'failed';
state.branch.deleteError = action.payload;
})
diff --git a/src/store/features/companySettingsSlice.ts b/src/store/features/companySettingsSlice.ts
index 5f33fd66..21df6f5b 100644
--- a/src/store/features/companySettingsSlice.ts
+++ b/src/store/features/companySettingsSlice.ts
@@ -3,7 +3,7 @@ import { FieldValues } from 'react-hook-form';
import { WritableDraft } from 'immer';
import { RootState, StateEntity } from 'store';
import { createCompanySettings, deleteCompanySettings, editCompanySettings, fetchCompanySettings } from 'store/thunks';
-import { CompanySettings, CompanySettingsDTO, ErrorResponse, ErrorResponseDTO } from 'shared/types';
+import { CompanySettings, ErrorResponse, ProblemDetailsModel } from 'shared/types';
interface CompanySettingsState {
companySettings: StateEntity;
@@ -38,11 +38,11 @@ const companySettingsSlice = createSlice({
.addCase(fetchCompanySettings.pending, (state) => {
state.companySettings.fetchStatus = 'loading';
})
- .addCase(fetchCompanySettings.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchCompanySettings.rejected, (state, action: PayloadAction) => {
state.companySettings.fetchStatus = 'failed';
state.companySettings.fetchError = action.payload;
})
- .addCase(fetchCompanySettings.fulfilled, (state, action: PayloadAction) => {
+ .addCase(fetchCompanySettings.fulfilled, (state, action: PayloadAction) => {
state.companySettings.item = action.payload;
state.companySettings.fetchStatus = 'succeeded';
})
@@ -71,7 +71,7 @@ const companySettingsSlice = createSlice({
.addCase(deleteCompanySettings.pending, (state) => {
state.companySettings.deleteStatus = 'loading';
})
- .addCase(deleteCompanySettings.rejected, (state, action: PayloadAction) => {
+ .addCase(deleteCompanySettings.rejected, (state, action: PayloadAction) => {
state.companySettings.deleteStatus = 'failed';
state.companySettings.fetchError = action.payload;
})
diff --git a/src/store/features/companySlice.ts b/src/store/features/companySlice.ts
index 5377eceb..945e36ce 100644
--- a/src/store/features/companySlice.ts
+++ b/src/store/features/companySlice.ts
@@ -13,14 +13,13 @@ import {
fetchEmployeesTotal,
} from 'store/thunks';
import {
- BranchDTO,
+ Branch,
Company,
CompanyDatasourceTotals,
- CompanyDTO,
- DepartmentDTO,
- EmployeeDTO,
+ Department,
+ Employee,
ErrorResponse,
- ErrorResponseDTO,
+ ProblemDetailsModel,
PaginatedResponse,
} from 'shared/types';
import { calculateUsagePercent, filterUniqueByField } from 'shared/helpers';
@@ -62,12 +61,12 @@ const companySlice = createSlice({
.addCase(fetchCompany.pending, (state) => {
state.company.fetchStatus = 'loading';
})
- .addCase(fetchCompany.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchCompany.rejected, (state, action: PayloadAction) => {
state.company.item = undefined;
state.company.fetchStatus = 'failed';
state.company.fetchError = action.payload;
})
- .addCase(fetchCompany.fulfilled, (state, action: PayloadAction) => {
+ .addCase(fetchCompany.fulfilled, (state, action: PayloadAction) => {
state.company.item = action.payload;
state.company.fetchStatus = 'succeeded';
})
@@ -96,7 +95,7 @@ const companySlice = createSlice({
.addCase(deleteCompany.pending, (state) => {
state.company.deleteStatus = 'loading';
})
- .addCase(deleteCompany.rejected, (state, action: PayloadAction) => {
+ .addCase(deleteCompany.rejected, (state, action: PayloadAction) => {
state.company.deleteStatus = 'failed';
state.company.deleteError = action.payload;
})
@@ -113,13 +112,13 @@ const companySlice = createSlice({
.addCase(fetchDepartmentsTotal.rejected, (state) => {
state.companyDatasourceTotals.item.departments = 0;
})
- .addCase(fetchBranchesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchBranchesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.companyDatasourceTotals.item.branches = action.payload?.totalItems;
})
- .addCase(fetchEmployeesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchEmployeesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.companyDatasourceTotals.item.employees = action.payload?.totalItems;
})
- .addCase(fetchDepartmentsTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchDepartmentsTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.companyDatasourceTotals.item.departments = action.payload?.totalItems;
})
.addCase(fetchCompanyDatasourceTotals.pending, (state) => {
diff --git a/src/store/features/departmentSlice.ts b/src/store/features/departmentSlice.ts
index 85a025a2..2a0a38b3 100644
--- a/src/store/features/departmentSlice.ts
+++ b/src/store/features/departmentSlice.ts
@@ -12,7 +12,7 @@ import {
fetchParentDepartments,
fetchSearchedDepartments,
} from 'store/thunks';
-import { ErrorResponseDTO, ErrorResponse, PaginatedResponse, PaginationParams, Department, DepartmentDTO } from 'shared/types';
+import { ProblemDetailsModel, ErrorResponse, PaginatedResponse, PaginationParams, Department } from 'shared/types';
import { APP_CONFIG } from 'shared/constants';
import { mergePaginatedItems } from 'store/helpers';
@@ -20,8 +20,8 @@ interface DepartmentState {
department: StateEntity;
departmentCatalog: PaginatedStateEntity;
searchedDepartments: PaginatedStateEntity;
- parentDepartments: PaginatedStateEntity;
- assignedDepartments: PaginatedStateEntity;
+ parentDepartments: PaginatedStateEntity;
+ assignedDepartments: PaginatedStateEntity;
}
const initialState: DepartmentState = {
@@ -96,7 +96,7 @@ const departmentSlice = createSlice({
.addCase(fetchDepartments.pending, (state) => {
state.departmentCatalog.status = 'loading';
})
- .addCase(fetchDepartments.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchDepartments.rejected, (state, action: PayloadAction) => {
state.departmentCatalog.items = [];
state.departmentCatalog.status = 'failed';
state.departmentCatalog.error = action.payload;
@@ -112,7 +112,7 @@ const departmentSlice = createSlice({
.addCase(fetchSearchedDepartments.pending, (state) => {
state.searchedDepartments.status = 'loading';
})
- .addCase(fetchSearchedDepartments.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchSearchedDepartments.rejected, (state, action: PayloadAction) => {
state.searchedDepartments.items = [];
state.searchedDepartments.status = 'failed';
state.searchedDepartments.error = action.payload;
@@ -131,7 +131,7 @@ const departmentSlice = createSlice({
.addCase(fetchParentDepartments.fulfilled, (state, action) => {
const { items = [], ...page } = action.payload || {};
- state.parentDepartments.items = mergePaginatedItems(state.parentDepartments.items, items);
+ state.parentDepartments.items = mergePaginatedItems(state.parentDepartments.items, items);
state.parentDepartments.page = page;
state.parentDepartments.status = 'succeeded';
})
@@ -144,7 +144,7 @@ const departmentSlice = createSlice({
.addCase(fetchAssignedDepartments.fulfilled, (state, action) => {
const { items = [], ...page } = action.payload || {};
- state.assignedDepartments.items = mergePaginatedItems(state.assignedDepartments.items, items);
+ state.assignedDepartments.items = mergePaginatedItems(state.assignedDepartments.items, items);
state.assignedDepartments.page = page;
state.assignedDepartments.status = 'succeeded';
})
@@ -201,7 +201,7 @@ const departmentSlice = createSlice({
.addCase(deleteDepartment.pending, (state) => {
state.department.deleteStatus = 'loading';
})
- .addCase(deleteDepartment.rejected, (state, action: PayloadAction) => {
+ .addCase(deleteDepartment.rejected, (state, action: PayloadAction) => {
state.department.deleteStatus = 'failed';
state.department.deleteError = action.payload;
})
diff --git a/src/store/features/employeeSlice.ts b/src/store/features/employeeSlice.ts
index 92541ef9..5f9475dd 100644
--- a/src/store/features/employeeSlice.ts
+++ b/src/store/features/employeeSlice.ts
@@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { FieldValues } from 'react-hook-form';
import { WritableDraft } from 'immer';
import { PaginatedStateEntity, RootState, StateEntity } from 'store';
-import { Employee, EmployeeDTO, ErrorResponse, ErrorResponseDTO, PaginatedResponse, PaginationParams } from 'shared/types';
+import { Employee, ErrorResponse, ProblemDetailsModel, PaginatedResponse, PaginationParams } from 'shared/types';
import { APP_CONFIG } from 'shared/constants';
import { mergePaginatedItems } from 'store/helpers';
import { editEmployee, fetchEmployeeById, fetchEmployees, fetchManagers, fetchOrgChartEmployees } from 'store/thunks';
@@ -10,8 +10,8 @@ import { editEmployee, fetchEmployeeById, fetchEmployees, fetchManagers, fetchOr
interface EmployeeState {
employee: StateEntity;
employeeCatalog: PaginatedStateEntity;
- managers: PaginatedStateEntity;
- employeeOrgChart: PaginatedStateEntity;
+ managers: PaginatedStateEntity;
+ employeeOrgChart: PaginatedStateEntity;
}
const initialState: EmployeeState = {
@@ -73,7 +73,7 @@ const employeeSlice = createSlice({
.addCase(fetchEmployees.pending, (state) => {
state.employeeCatalog.status = 'loading';
})
- .addCase(fetchEmployees.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchEmployees.rejected, (state, action: PayloadAction) => {
state.employeeCatalog.items = [];
state.employeeCatalog.status = 'failed';
state.employeeCatalog.error = action.payload;
@@ -92,7 +92,7 @@ const employeeSlice = createSlice({
.addCase(fetchManagers.fulfilled, (state, action) => {
const { items = [], ...page } = action.payload || {};
- state.managers.items = mergePaginatedItems(state.managers.items, items);
+ state.managers.items = mergePaginatedItems(state.managers.items, items);
state.managers.page = page;
state.managers.status = 'succeeded';
})
@@ -109,12 +109,12 @@ const employeeSlice = createSlice({
.addCase(fetchOrgChartEmployees.pending, (state) => {
state.employeeOrgChart.status = 'loading';
})
- .addCase(fetchOrgChartEmployees.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchOrgChartEmployees.rejected, (state, action: PayloadAction) => {
state.employeeOrgChart.items = [];
state.employeeOrgChart.status = 'failed';
state.employeeOrgChart.error = action.payload;
})
- .addCase(fetchOrgChartEmployees.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchOrgChartEmployees.fulfilled, (state, action: PayloadAction | undefined>) => {
const { items = [] } = action.payload || {};
state.employeeOrgChart.items = items;
state.employeeOrgChart.status = 'succeeded';
diff --git a/src/store/features/identitySlice.ts b/src/store/features/identitySlice.ts
index 58213673..0fa525d3 100644
--- a/src/store/features/identitySlice.ts
+++ b/src/store/features/identitySlice.ts
@@ -1,10 +1,11 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, StateEntity } from 'store';
import { fetchClient } from 'store/thunks';
-import { Client, ErrorResponseDTO } from 'shared/types';
+import { ProblemDetailsModel } from 'shared/types';
+import { IdentityClientRetrievalModel } from '@fossa-app/bridge/Models/ApiModels/PayloadModels';
interface IdentityState {
- client: StateEntity;
+ client: StateEntity;
}
const initialState: IdentityState = {
@@ -23,12 +24,12 @@ const identitySlice = createSlice({
.addCase(fetchClient.pending, (state) => {
state.client.fetchStatus = 'loading';
})
- .addCase(fetchClient.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchClient.rejected, (state, action: PayloadAction) => {
state.client.item = undefined;
state.client.fetchStatus = 'failed';
state.client.fetchError = action.payload;
})
- .addCase(fetchClient.fulfilled, (state, action: PayloadAction) => {
+ .addCase(fetchClient.fulfilled, (state, action: PayloadAction) => {
state.client.item = action.payload;
state.client.fetchStatus = 'succeeded';
});
diff --git a/src/store/features/licenseSlice.ts b/src/store/features/licenseSlice.ts
index 90a33e33..281e6fcf 100644
--- a/src/store/features/licenseSlice.ts
+++ b/src/store/features/licenseSlice.ts
@@ -3,7 +3,7 @@ import { FieldValues } from 'react-hook-form';
import { WritableDraft } from 'immer';
import { RootState, StateEntity } from 'store';
import { fetchCompanyLicense, fetchSystemLicense, uploadCompanyLicense } from 'store/thunks';
-import { CompanyLicense, ErrorResponse, ErrorResponseDTO, SystemLicense } from 'shared/types';
+import { CompanyLicense, ErrorResponse, ProblemDetailsModel, SystemLicense } from 'shared/types';
interface LicenseState {
system: StateEntity;
@@ -31,7 +31,7 @@ const licenseSlice = createSlice({
.addCase(fetchSystemLicense.pending, (state) => {
state.system.fetchStatus = 'loading';
})
- .addCase(fetchSystemLicense.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchSystemLicense.rejected, (state, action: PayloadAction) => {
state.system.item = undefined;
state.system.fetchStatus = 'failed';
state.system.fetchError = action.payload;
@@ -43,7 +43,7 @@ const licenseSlice = createSlice({
.addCase(fetchCompanyLicense.pending, (state) => {
state.company.fetchStatus = 'loading';
})
- .addCase(fetchCompanyLicense.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchCompanyLicense.rejected, (state, action: PayloadAction) => {
state.company.item = undefined;
state.company.fetchStatus = 'failed';
state.company.fetchError = action.payload;
diff --git a/src/store/features/messageSlice.ts b/src/store/features/messageSlice.ts
index 32f88e68..88b388f0 100644
--- a/src/store/features/messageSlice.ts
+++ b/src/store/features/messageSlice.ts
@@ -1,9 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from 'store';
-import { ErrorResponseDTO } from 'shared/types';
+import { ProblemDetailsModel } from 'shared/types';
interface MessageState {
- error: ErrorResponseDTO | undefined;
+ error: ProblemDetailsModel | undefined;
success: string | undefined;
}
@@ -16,7 +16,7 @@ const messageSlice = createSlice({
name: 'message',
initialState,
reducers: {
- setError: (state, action: PayloadAction) => {
+ setError: (state, action: PayloadAction) => {
state.error = action.payload;
state.success = undefined;
},
diff --git a/src/store/features/offboardingSlice.ts b/src/store/features/offboardingSlice.ts
index 6d054581..fdba48da 100644
--- a/src/store/features/offboardingSlice.ts
+++ b/src/store/features/offboardingSlice.ts
@@ -12,11 +12,11 @@ import {
fetchProfile,
} from 'store/thunks';
import {
- BranchDTO,
+ Branch,
CompanyDatasourceTotals,
CompanyOffboardingStep,
- DepartmentDTO,
- EmployeeDTO,
+ Department,
+ Employee,
OffboardingStep,
PaginatedResponse,
} from 'shared/types';
@@ -108,7 +108,7 @@ const offboardingSlice = createSlice({
state.company.flags[OffboardingStep.companySettings] = true;
evaluateCompanyOffboardingStep(state);
})
- .addCase(fetchBranchesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchBranchesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.company.flags[OffboardingStep.instructions].branches = action.payload?.totalItems;
evaluateCompanyOffboardingStep(state);
})
@@ -119,11 +119,11 @@ const offboardingSlice = createSlice({
state.company.flags[OffboardingStep.company] = true;
evaluateCompanyOffboardingStep(state);
})
- .addCase(fetchEmployeesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchEmployeesTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.company.flags[OffboardingStep.instructions].employees = action.payload?.totalItems;
evaluateCompanyOffboardingStep(state);
})
- .addCase(fetchDepartmentsTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
+ .addCase(fetchDepartmentsTotal.fulfilled, (state, action: PayloadAction | undefined>) => {
state.company.flags[OffboardingStep.instructions].departments = action.payload?.totalItems;
evaluateCompanyOffboardingStep(state);
})
diff --git a/src/store/features/profileSlice.ts b/src/store/features/profileSlice.ts
index b0d5fb81..8e07289a 100644
--- a/src/store/features/profileSlice.ts
+++ b/src/store/features/profileSlice.ts
@@ -3,7 +3,7 @@ import { FieldValues } from 'react-hook-form';
import { WritableDraft } from 'immer';
import { RootState, StateEntity } from 'store';
import { createProfile, deleteProfile, editProfile, fetchProfile, fetchUser } from 'store/thunks';
-import { AppUser, Employee, ErrorResponse, ErrorResponseDTO } from 'shared/types';
+import { AppUser, Employee, ErrorResponse, ProblemDetailsModel } from 'shared/types';
import { mapUserProfileToEmployee } from 'shared/helpers';
interface ProfileState {
@@ -31,7 +31,7 @@ const profileSlice = createSlice({
.addCase(fetchProfile.pending, (state) => {
state.profile.fetchStatus = 'loading';
})
- .addCase(fetchProfile.rejected, (state, action: PayloadAction) => {
+ .addCase(fetchProfile.rejected, (state, action: PayloadAction) => {
state.profile.fetchStatus = 'failed';
state.profile.fetchError = action.payload;
})
@@ -70,7 +70,7 @@ const profileSlice = createSlice({
.addCase(deleteProfile.pending, (state) => {
state.profile.deleteStatus = 'loading';
})
- .addCase(deleteProfile.rejected, (state, action: PayloadAction) => {
+ .addCase(deleteProfile.rejected, (state, action: PayloadAction) => {
state.profile.deleteStatus = 'failed';
state.profile.fetchError = action.payload;
})
diff --git a/src/store/thunks/authThunk.ts b/src/store/thunks/authThunk.ts
index 79877e51..b7833e46 100644
--- a/src/store/thunks/authThunk.ts
+++ b/src/store/thunks/authThunk.ts
@@ -1,9 +1,9 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
-import { getUserManager, mapUser } from 'shared/helpers';
-import { AppUser, ErrorResponseDTO } from 'shared/types';
+import { getUserManager, mapUser, createProblemDetails } from 'shared/helpers';
+import { AppUser, ProblemDetailsModel } from 'shared/types';
import { MESSAGES } from 'shared/constants';
-export const fetchUser = createAsyncThunk(
+export const fetchUser = createAsyncThunk(
'auth/fetchUser',
async (_, { rejectWithValue }) => {
try {
@@ -13,12 +13,9 @@ export const fetchUser = createAsyncThunk | undefined, void, { rejectValue: ErrorResponseDTO }>(
+import { Branch, ProblemDetailsModel, ErrorResponse, PaginatedResponse, PaginationParams, GeoAddress, EntityInput } from 'shared/types';
+import { MESSAGES } from 'shared/constants';
+import { mapBranch, mapBranches, mapError, getFullAddress, createProblemDetails } from 'shared/helpers';
+import { branchClient } from 'shared/configs/BridgeClients';
+import { matchClientResult, matchClientUnitResult, toPaginatedResponse } from 'shared/configs/BridgeResponses';
+import { BranchQueryRequestModel, BranchModificationModel } from '@fossa-app/bridge/Models/ApiModels/PayloadModels';
+import { AddressModel } from '@fossa-app/bridge/Models/ApiModels/SharedModels';
+
+const toBranchModificationAddress = (address: EntityInput['address']): AddressModel | null => {
+ if (address === null || address === undefined) {
+ return null;
+ }
+
+ return new AddressModel(
+ address.line1 ?? null,
+ address.line2 ?? null,
+ address.city ?? null,
+ address.subdivision ?? null,
+ address.postalCode ?? null,
+ address.countryCode ?? null
+ );
+};
+
+export const fetchBranchesTotal = createAsyncThunk | undefined, void, { rejectValue: ProblemDetailsModel }>(
'branch/fetchBranchesTotal',
async (_, { rejectWithValue }) => {
- try {
- const queryParams = prepareQueryParams({ pageNumber: 1, pageSize: 1 });
- const { data } = await axios.get>(`${ENDPOINTS.branches}?${queryParams}`);
+ const query = new BranchQueryRequestModel([], '', 1, 1);
- if (!data.items.length) {
- return rejectWithValue({
- status: 404,
- title: MESSAGES.error.branches.notFound,
- });
- }
+ return matchClientResult(
+ await branchClient.GetBranchesAsync(query, new AbortController().signal),
+ (response) => {
+ const data = toPaginatedResponse(response);
+ if (!data.items.length) {
+ return rejectWithValue(createProblemDetails(undefined, { Status: 404, Title: MESSAGES.error.branches.notFound }));
+ }
- return data;
- } catch (error) {
- return rejectWithValue({
- ...(error as ErrorResponseDTO),
- title: MESSAGES.error.branches.notFound,
- });
- }
+ return data;
+ },
+ (problem) => rejectWithValue(createProblemDetails(problem, { Title: MESSAGES.error.branches.notFound }))
+ );
}
);
export const fetchBranches = createAsyncThunk<
PaginatedResponse | undefined,
Partial,
- { rejectValue: ErrorResponseDTO }
+ { rejectValue: ProblemDetailsModel }
>('branch/fetchBranches', async ({ pageNumber, pageSize, search }, { getState, rejectWithValue }) => {
- try {
- const queryParams = prepareQueryParams({ pageNumber, pageSize, search });
- const { data } = await axios.get>(`${ENDPOINTS.branches}?${queryParams}`);
-
- if (!data.items.length) {
- return rejectWithValue({
- status: 404,
- title: MESSAGES.error.branches.notFound,
- });
- }
+ const query = new BranchQueryRequestModel([], search || '', pageNumber || null, pageSize || null);
+
+ return matchClientResult(
+ await branchClient.GetBranchesAsync(query, new AbortController().signal),
+ (response) => {
+ const data = toPaginatedResponse(response);
+ if (!data.items.length) {
+ return rejectWithValue(createProblemDetails(undefined, { Status: 404, Title: MESSAGES.error.branches.notFound }));
+ }
- const state = getState() as RootState;
- const timeZones = state.license.system.item?.entitlements.timeZones || [];
- const countries = state.license.system.item?.entitlements.countries || [];
- const companyCountryCode = state.company.company.item!.countryCode;
+ const state = getState() as RootState;
+ const timeZones = state.license.system.item?.entitlements.timeZones || [];
+ const countries = state.license.system.item?.entitlements.countries || [];
+ const companyCountryCode = state.company.company.item!.countryCode;
- return {
- ...data,
- items: mapBranches(data.items, timeZones, companyCountryCode, countries),
- };
- } catch (error) {
- return rejectWithValue({
- ...(error as ErrorResponseDTO),
- title: MESSAGES.error.branches.notFound,
- });
- }
+ return {
+ ...data,
+ items: mapBranches(data.items, timeZones, companyCountryCode, countries),
+ };
+ },
+ (problem) => rejectWithValue(createProblemDetails(problem, { Title: MESSAGES.error.branches.notFound }))
+ );
});
export const fetchAssignedBranches = createAsyncThunk<
- PaginatedResponse | undefined,
+ PaginatedResponse | undefined,
Partial,
- { state: RootState; rejectValue: ErrorResponseDTO }
+ { state: RootState; rejectValue: ProblemDetailsModel }
>('branch/fetchAssignedBranches', async ({ pageNumber, pageSize, search }, { rejectWithValue }) => {
- try {
- const queryParams = prepareQueryParams({ pageNumber, pageSize, search });
- const { data } = await axios.get>(`${ENDPOINTS.branches}?${queryParams}`);
+ const query = new BranchQueryRequestModel([], search || '', pageNumber || null, pageSize || null);
- return data;
- } catch (error) {
- return rejectWithValue({
- ...(error as ErrorResponseDTO),
- title: MESSAGES.error.branches.notFound,
- });
- }
+ return matchClientResult(
+ await branchClient.GetBranchesAsync(query, new AbortController().signal),
+ (response) => toPaginatedResponse(response),
+ (problem) => rejectWithValue(createProblemDetails(problem, { Title: MESSAGES.error.branches.notFound }))
+ );
});
-export const fetchBranchesByIds = createAsyncThunk | undefined, number[], { rejectValue: ErrorResponseDTO }>(
+export const fetchBranchesByIds = createAsyncThunk | undefined, number[], { rejectValue: ProblemDetailsModel }>(
'branch/fetchBranchesByIds',
async (ids, { rejectWithValue }) => {
+ let idList: bigint[] = [];
try {
- const queryParams = prepareCommaSeparatedQueryParamsByKey('id', ids);
- const { data } = await axios.get>(`${ENDPOINTS.branches}?${queryParams}`);
-
- return data;
- } catch (error) {
- return rejectWithValue({
- ...(error as ErrorResponseDTO),
- title: MESSAGES.error.branches.notFound,
- });
+ idList = ids.map((id) => BigInt(id));
+ } catch {
+ // Ignored
}
+
+ const query = new BranchQueryRequestModel(idList, '', null, null);
+
+ return matchClientResult(
+ await branchClient.GetBranchesAsync(query, new AbortController().signal),
+ (response) => toPaginatedResponse(response),
+ (problem) => rejectWithValue(createProblemDetails(problem, { Title: MESSAGES.error.branches.notFound }))
+ );
}
);
export const fetchBranchById = createAsyncThunk<
Branch,
{ id: string; skipState?: boolean; shouldFetchBranchGeoAddress?: boolean },
- { rejectValue: ErrorResponseDTO }
+ { rejectValue: ProblemDetailsModel }
>('branch/fetchBranchById', async ({ id, shouldFetchBranchGeoAddress = true }, { getState, dispatch, rejectWithValue }) => {
try {
- const { data } = await axios.get(`${ENDPOINTS.branches}/${id}`);
- const state = getState() as RootState;
- const timeZones = state.license.system.item?.entitlements.timeZones || [];
- const countries = state.license.system.item?.entitlements.countries || [];
- const companyCountryCode = state.company.company.item!.countryCode;
- const countryName = countries.find((country) => country.code === data.address?.countryCode)?.name;
- const fullAddress = data.address ? getFullAddress({ ...data.address, countryName }, false) : '';
- let geoAddress: GeoAddress | undefined;
-
- if (shouldFetchBranchGeoAddress) {
- geoAddress = await dispatch(fetchGeoAddress(fullAddress)).unwrap();
- }
-
- return mapBranch({
- branch: data,
- timeZones,
- companyCountryCode,
- countries,
- geoAddress,
- });
+ return matchClientResult(
+ await branchClient.GetBranchAsync(BigInt(id), new AbortController().signal),
+ async (data) => {
+ const state = getState() as RootState;
+ const timeZones = state.license.system.item?.entitlements.timeZones || [];
+ const countries = state.license.system.item?.entitlements.countries || [];
+ const companyCountryCode = state.company.company.item!.countryCode;
+ const countryName = countries.find((country) => country.code === data.address?.countryCode)?.name;
+ const fullAddress = data.address ? getFullAddress({ ...data.address, countryName }, false) : '';
+ let geoAddress: GeoAddress | undefined;
+
+ if (shouldFetchBranchGeoAddress) {
+ geoAddress = await dispatch(fetchGeoAddress(fullAddress)).unwrap();
+ }
+
+ return mapBranch({
+ branch: data,
+ timeZones,
+ companyCountryCode,
+ countries,
+ geoAddress,
+ });
+ },
+ (problem) => rejectWithValue(problem)
+ );
} catch (error) {
- return rejectWithValue(error as ErrorResponseDTO);
+ return rejectWithValue(error as ProblemDetailsModel);
}
});
-export const createOnboardingBranch = createAsyncThunk, { rejectValue: ErrorResponse