@springtree/eva-suite-bootstrap
v2.1.2
Published
Shared bootstrap logic for EVA Suite Apps to setup the React context that interacts with the EVA SDK
Downloads
18
Keywords
Readme
EVA Suite bootstrap
Shared bootstrap logic for EVA Suite Apps to setup the React context that interacts with the EVA SDK
Installation
npm install @springtree/eva-suite-bootstrap --save
NOTE: A breaking change was made to upgrade to a newer version of the SDK. See the update notes for more details
Usage
This library is intended to be used with a TypeScript based React app from the EVA Suite. When using the library you need to provide:
- Client details (name, version)
- Firebase Configuration
- Redux store interface
- EVA Storage implementation
- EVA Login widget instance
Storage
You can use the below code to create your storage implementation.
Replace MyApp
as needed.
File: context/EvaContext/storage.ts
:
import { EvaStorage } from '@springtree/eva-sdk-core-storage';
import { IEvaSuiteStorage } from '@springtree/eva-suite-bootstrap';
export const STORAGE_PREFIX='evaAdminMyApp';
/**
* This interface defines all the storage used for this application.
* It includes the auto-synced properties from the EVA SDK store builder
*
* @export
* @interface IEvaSuiteMyAppStorage
*/
export interface IEvaSuiteMyAppStorage extends IEvaSuiteStorage {
// Declare your storage properties here
// ex: 'evaAdminMyApp:mySetting': string;
//
...
}
export const evaStorage = new EvaStorage<IEvaSuiteMyAppStorage>();
Store interface
You need to declare the interface of your Redux store.
You should extend IEvaReduxStoreDefaultShape
for the basics added by the SDK.
File: context/EvaContext/state.ts
:
import {
IEvaReduxStoreDefaultShape,
IEvaServiceState,
} from '@springtree/eva-sdk-redux-store-builder';
import { Core } from '@springtree/eva-services-core';
export interface IEvaSuiteMyAppStore extends IEvaReduxStoreDefaultShape {
/**
* The application configuration will be retrieved after the user logs in
* as it may contain more data for an employee
* The base configuration is available on the `EvaEndpoint` if needed
*
* @type {IEvaServiceState<Core.GetApplicationConfiguration>}
*/
'Core:GetApplicationConfiguration': IEvaServiceState<Core.GetApplicationConfiguration>;
....
}
Store builder
Next step is to use the storage instance and store definitions to prepare the builder. You will also provide the builder with your EVA Login widget instance which can be styled and configured as required.
File: context/EvaContext/store-builder.ts
:
// The EVA Login widget component
//
import EvaLogin from '@bit/springtree_solutions.eva-suite-ui.eva-login';
import '@material-ui/icons/Lock';
import theme from '../../theme/EvaMaterialTheme';
// We need the firebase config for the customer manager login flow
//
import firebaseConfig from '../../utils/firebase/firebaseConfig';
// The store bootstrap helper and our store and storage interfaces
//
import { EvaContextBuilder, IEvaSession } from '@springtree/eva-suite-bootstrap';
import { IEvaSuiteMyAppStore } from './state';
import { IEvaSuiteMyAppStorage } from './storage';
// Helper type for the application specific EVA Session
// with store and storage types set
//
export type IEvaSuiteMyAppSession = IEvaSession<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>;
// Client application details from our package file
//
const pkg = require('../../../package.json');
// Our login widget instance
//
const loginWidget = new EvaLogin({
isModal: true,
isOpen: false,
language: 'nl-nl',
theme,
});
// Our configured store builder
//
export const contextBuilder = new EvaContextBuilder<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>({
firebaseConfig,
loginWidget,
appName: pkg.name,
appVersion: pkg.version,
});
// Our fully typed session manager
//
export const sessionManager = new EvaSessionManager<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>();
Store
And finally you will add all your EVA Services, custom reducers and logic to the builder to generate the store.
File: context/EvaContext/store-builder.ts
:
// The EVA Login widget component
//
import EvaLogin from '@bit/springtree_solutions.eva-suite-ui.eva-login';
import '@material-ui/icons/Lock';
// We need the firebase config for the customer manager login flow
//
import firebaseConfig from '../../utils/firebase/firebaseConfig';
// The store bootstrap helper and our store and storage interfaces
//
import { EvaContextBuilder, IEvaSession, EvaSessionManager } from '@springtree/eva-suite-bootstrap';
import { IEvaSuiteMyAppStore } from './state';
import { IEvaSuiteMyAppStorage } from './storage';
// Helper type for the application specific EVA Session
// with store and storage types set
//
export type IEvaSuiteMyAppSession = IEvaSession<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>;
// Client application details from our package file
//
const pkg = require('../../../package.json');
// Our login widget instance
//
const loginWidget = new EvaLogin({
isModal: true,
isOpen: false
});
// Our configured store builder
//
export const contextBuilder = new EvaContextBuilder<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>({
firebaseConfig,
loginWidget,
appName: pkg.name,
appVersion: pkg.version,
});
// Our fully typed session manager
//
export const sessionManager = new EvaSessionManager<IEvaSuiteMyAppStore, IEvaSuiteMyAppStorage>();
The definition of store reducers and logic can be best isolated in it's own file.
File: store.ts
:
import { Core } from '@springtree/eva-services-core';
import { Admin } from '@springtree/eva-services-admin';
import { PIM } from '@springtree/eva-services-pim';
import { createChainLogic } from '@springtree/eva-sdk-redux-store-builder';
import { Logic } from 'redux-logic';
import { contextBuilder } from './store-builder';
console.log( '[EVA-CONTEXT]: Configuring store...' );
////////////////////////////////////////////////////////////////////////////////
// EVA Services
////////////////////////////////////////////////////////////////////////////////
//
contextBuilder.addEvaService( 'Admin:AdminGetAllModules', Admin.AdminGetAllModules );
// Reducers for all the Core services
//
contextBuilder.addEvaService( 'Core:GetApplicationConfiguration', Core.GetApplicationConfiguration);
...
// Custom services
//
contextBuilder.addEvaService( 'Custom:ListDiscountOrderTypes', Core.ListEnumValues );
////////////////////////////////////////////////////////////////////////////////
// Custom reducers
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
// Custom logic
////////////////////////////////////////////////////////////////////////////////
//
// Logic chains for all the Core services
//
// Fetch the ApplicationConfiguration when current user is logged in as an employee
//
const fetchApplicationConfigurationOnUserLogin = createChainLogic({
from: {
service: Core.GetCurrentUser,
stateOptions: { stateKey: 'currentUser' },
type: 'RESPONSE',
},
to: {
service: Core.GetApplicationConfiguration,
validate: (currentUserState) => {
const currentUserDetails = currentUserState ? currentUserState.data.response : undefined;
if (!currentUserDetails) {
return false;
}
// Must be an employee
//
const userType = currentUserDetails.User.Type;
const isEmployee = (userType & 1) !== 0;
return isEmployee;
},
},
});
contextBuilder.addLogic( fetchApplicationConfigurationOnUserLogin as Logic[] );
Service helpers and hooks
For convenience you will likely add service specific hooks in hooks/...
and service access helper in service-helpers.ts
.
The hooks are React app specific but the service helpers follow a standard pattern described below.
File: context/EvaContext/service-helpers.ts
:
import { createServiceContext } from '@springtree/eva-suite-bootstrap';
import { IEvaSuiteMyAppSession } from './store-builder';
// Import the EVA Service definitions you need here
//
import { Core } from '@springtree/eva-services-core';
// Export all service context helpers for easy consumption in our React components
// These will also be handy to see where each service is being used
// You can use 'find all references' on them in VSCode for instance
//
export function coreGetApplicationConfiguration(session: IEvaSession) {
return createServiceContext( {
session,
path: 'Core:GetApplicationConfiguration',
service: Core.GetApplicationConfiguration,
});
}
// Add more service helpers here...
React context provider
The context can be made available to the React with the following code:
import React, { useEffect, useState, ComponentProps } from 'react';
import { IEvaSuiteMyAppSession, contextBuilder, sessionManager } from './store-builder';
import { evaStorage } from './storage';
console.log( '[EVA-CONTEXT]: Initializing...' );
// Import your store reducers and logic definitions for the builder
//
import './store';
/** Create React context */
export const EvaSession = React.createContext<IEvaSuiteMyAppSession>(sessionManager.currentSession as IEvaSuiteMyAppSession);
/**
* This is the interface for the `Data` property return from the `Admin.AdminGetAllModules` service
* This property is managed by the front-ends and passed along by the backend
*
* @export
* @interface IEvaAdminModuleData
*/
export interface IEvaAdminModuleData {
Actions: Array<{ Label: string, Url: string }>;
Description: string;
Url: string;
Title: string;
Version?: string;
}
/**
* The React component properties for the EVA Context
*
* @interface EvaContextProps
* @extends {ComponentProps}
*/
interface EvaContextProps extends ComponentProps<any> {};
/* Create the React context element */
const EvaContext = (props: EvaContextProps) => {
console.log('[EVA-CONTEXT]: Constructing EVA context');
const [session, setSession] = useState();
/** Debug logging of session changes */
useEffect( () => {
console.log('[EVA-CONTEXT]: Session updated', session);
}, [session] );
/** On mount bootstrap the store */
useEffect( () => {
// Create an EVA Session
// We will not await it so that the app and the backend can initialise at the same time
// Also by not setting a user token it will be fetched from localStorage by default
// Persisting to localStorage
//
console.log( '[EVA-CONTEXT]: Creating EVA Session for context' );
sessionManager.createSession(
contextBuilder,
{
storage: evaStorage,
},
)
.then( ( evaSession ) => setSession( evaSession ) )
.catch( ( error ) => {
console.error( '[EVA-CONTEXT]: Failed to create EVA Session for context', error );
} )
;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );
// You can add the provider in your App.tsx
//
// ...
// <EvaContext>
// <TranslationProvider>
// <EvaHeader appName={'Promotion Engine'} />
// <Box display={'flex'}>
// <EvaSideNav routes={this.routes} activeRoute={this.activatedRoute} />
// <Routes></Routes>
// </Box>
// </TranslationProvider>
// </EvaContext>
// ...
//
return (
<EvaSession.Provider value={sessionManager.currentSession as IEvaSuiteMyAppSession}>
{session ? ( props.children ) : (
<span>loading...</span>
)}
</EvaSession.Provider>
);
};
export default EvaContext;
/** Export all the service context helpers */
export * from './service-helpers';
Test
npm run test