Skip to content

Commit e327a78

Browse files
authored
refactor: initialization of Pega logic in Embedded sample (#512)
* refactor: initialization of Pega logic in Embedded sample * refactor: create case mashup api moved to PegaReady Context
1 parent 398a476 commit e327a78

File tree

4 files changed

+264
-118
lines changed

4 files changed

+264
-118
lines changed

packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { useEffect, useMemo, useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { getSdkConfig } from '@pega/auth/lib/sdk-auth-manager';
33
import { makeStyles } from '@mui/styles';
44

5-
import StoreContext from '../../../bridge/Context/StoreContext';
6-
import createPConnectComponent from '../../../bridge/react_pconnect';
5+
import { usePegaAuth } from '../context/PegaAuthProvider';
6+
import { usePega } from '../context/PegaReadyContext';
77

88
import ShoppingOptionCard from '../ShoppingOptionCard';
99
import ResolutionScreen from '../ResolutionScreen';
@@ -105,45 +105,35 @@ const useStyles = makeStyles(theme => ({
105105
pegaForm: {}
106106
}));
107107

108-
function RootComponent(props) {
109-
const PegaConnectObj = createPConnectComponent();
110-
const thePConnObj = <PegaConnectObj {...props} />;
108+
export default function MainScreen() {
109+
const { isAuthenticated } = usePegaAuth();
110+
const { isPegaReady, PegaContainer, createCase } = usePega();
111111

112-
/**
113-
* NOTE: For Embedded mode, we add in displayOnlyFA to our React context
114-
* so it is available to any component that may need it.
115-
* VRS: Attempted to remove displayOnlyFA but it presently handles various components which
116-
* SDK does not yet support, so all those need to be fixed up before it can be removed.
117-
* To be done in a future sprint.
118-
*/
119-
const contextValue = useMemo(() => {
120-
return { store: PCore.getStore(), displayOnlyFA: true };
121-
}, [PCore.getStore()]);
122-
123-
return <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
124-
}
125-
126-
export default function MainScreen(props) {
127112
const classes = useStyles();
113+
128114
const [showPega, setShowPega] = useState(false);
129115
const [showLandingPage, setShowLandingPage] = useState(true);
130116
const [showResolution, setShowResolution] = useState(false);
131117

132118
useEffect(() => {
133-
// Subscribe to the EVENT_CANCEL event to handle the assignment cancellation
134-
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment');
135-
// Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion
136-
PCore.getPubSubUtils().subscribe(
137-
PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
138-
() => assignmentFinished(),
139-
'endOfAssignmentProcessing'
140-
);
119+
if (isPegaReady) {
120+
// Subscribe to the EVENT_CANCEL event to handle the assignment cancellation
121+
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment');
122+
123+
// Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion
124+
PCore.getPubSubUtils().subscribe(
125+
PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
126+
() => assignmentFinished(),
127+
'endOfAssignmentProcessing'
128+
);
129+
}
130+
141131
return () => {
142132
// unsubscribe to the events
143133
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, 'cancelAssignment');
144134
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING, 'endOfAssignmentProcessing');
145135
};
146-
}, []);
136+
}, [isPegaReady]);
147137

148138
const cancelAssignment = () => {
149139
setShowLandingPage(true);
@@ -159,14 +149,8 @@ export default function MainScreen(props) {
159149
setShowLandingPage(false);
160150
setShowPega(true);
161151
const sdkConfig = await getSdkConfig();
162-
let mashupCaseType = sdkConfig.serverConfig.appMashupCaseType;
163-
// If mashupCaseType is null or undefined, get the first case type from the environment info
164-
if (!mashupCaseType) {
165-
const caseTypes = PCore.getEnvironmentInfo()?.environmentInfoObject?.pyCaseTypeList;
166-
if (caseTypes && caseTypes.length > 0) {
167-
mashupCaseType = mashupCaseType = caseTypes[0].pyWorkTypeImplementationClassName;
168-
}
169-
}
152+
const mashupCaseType = sdkConfig.serverConfig.appMashupCaseType;
153+
170154
let selectedPhoneGUID = '';
171155
const phoneName = optionClicked ? optionClicked.trim() : '';
172156
switch (phoneName) {
@@ -198,12 +182,10 @@ export default function MainScreen(props) {
198182
console.warn(`Unexpected case type: ${mashupCaseType}. PhoneModelss field not set.`);
199183
}
200184

201-
// Create a new case using the mashup API
202-
PCore.getMashupApi()
203-
.createCase(mashupCaseType, PCore.getConstants().APP.APP, options)
204-
.then(() => {
205-
console.log('createCase rendering is complete');
206-
});
185+
// Call the createCase function from context to create a new case using the mashup API
186+
createCase(mashupCaseType, options).then(() => {
187+
console.log('createCase rendering is complete');
188+
});
207189
};
208190

209191
function renderLandingPage() {
@@ -244,13 +226,15 @@ export default function MainScreen(props) {
244226
return (
245227
<div className={classes.pegaViewContainer}>
246228
<div className={classes.pegaForm} id='pega-part-of-page'>
247-
<RootComponent {...props} />
229+
<PegaContainer />
248230
<br />
249231
</div>
250232
</div>
251233
);
252234
}
253235

236+
if (!isAuthenticated) return <div style={{ textAlign: 'center' }}>Loading...</div>;
237+
254238
return (
255239
<div className={classes.appContainer}>
256240
{showLandingPage && renderLandingPage()}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { createContext, useContext, useEffect, useState } from 'react';
2+
import { getSdkConfig, loginIfNecessary, sdkSetAuthHeader, sdkSetCustomTokenParamsCB } from '@pega/auth/lib/sdk-auth-manager';
3+
4+
interface AuthContextType {
5+
isAuthenticated: boolean;
6+
}
7+
8+
const UserAuthContext = createContext<AuthContextType | undefined>(undefined);
9+
10+
const PegaAuthProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
11+
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
12+
13+
const initialize = async () => {
14+
try {
15+
// Add event listener for when logged in and constellation bootstrap is loaded
16+
document.addEventListener('SdkConstellationReady', () => {
17+
setIsAuthenticated(true);
18+
});
19+
20+
// Initialize authentication settings
21+
await initializeAuthentication();
22+
23+
// this function will handle login process, and SdkConstellationReady event will be fired once PCore is ready
24+
loginIfNecessary({ appName: 'embedded', mainRedirect: false });
25+
} catch (error) {
26+
console.error('Something went wrong while login', error);
27+
}
28+
};
29+
30+
useEffect(() => {
31+
initialize();
32+
}, []);
33+
34+
return <UserAuthContext.Provider value={{ isAuthenticated }}>{children}</UserAuthContext.Provider>;
35+
};
36+
37+
export default PegaAuthProvider;
38+
39+
export const usePegaAuth = (): AuthContextType => {
40+
const context = useContext(UserAuthContext);
41+
if (context === undefined) {
42+
throw new Error('useAuth must be used within an AuthProvider');
43+
}
44+
return context;
45+
};
46+
47+
async function initializeAuthentication() {
48+
const { authConfig } = await getSdkConfig();
49+
50+
if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'Basic') {
51+
// Service package to use custom auth with Basic
52+
const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}`);
53+
sdkSetAuthHeader(`Basic ${sB64}`);
54+
}
55+
56+
if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'BasicTO') {
57+
const now = new Date();
58+
const expTime = new Date(now.getTime() + 5 * 60 * 1000);
59+
let sISOTime = `${expTime.toISOString().split('.')[0]}Z`;
60+
const regex = /[-:]/g;
61+
sISOTime = sISOTime.replace(regex, '');
62+
// Service package to use custom auth with Basic
63+
const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}:${sISOTime}`);
64+
sdkSetAuthHeader(`Basic ${sB64}`);
65+
}
66+
67+
if (authConfig.mashupGrantType === 'customBearer' && authConfig.customAuthType === 'CustomIdentifier') {
68+
// Use custom bearer with specific custom parameter to set the desired operator via
69+
// a userIdentifier property. (Caution: highly insecure...being used for simple demonstration)
70+
sdkSetCustomTokenParamsCB(() => {
71+
return { userIdentifier: authConfig.mashupUserIdentifier };
72+
});
73+
}
74+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
2+
import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material';
3+
import type { CaseOptions } from '@pega/pcore-pconnect-typedefs/mashup/types';
4+
5+
import StoreContext from '../../../bridge/Context/StoreContext';
6+
import createPConnectComponent from '../../../bridge/react_pconnect';
7+
import { getSdkComponentMap } from '../../../bridge/helpers/sdk_component_map';
8+
9+
import localSdkComponentMap from '../../../../sdk-local-component-map';
10+
11+
import { usePegaAuth } from './PegaAuthProvider';
12+
13+
function RootComponent(props) {
14+
const PegaConnectObj = createPConnectComponent();
15+
const thePConnObj = <PegaConnectObj {...props} />;
16+
17+
/**
18+
* NOTE: For Embedded mode, we add in displayOnlyFA to our React context
19+
* so it is available to any component that may need it.
20+
* VRS: Attempted to remove displayOnlyFA but it presently handles various components which
21+
* SDK does not yet support, so all those need to be fixed up before it can be removed.
22+
* To be done in a future sprint.
23+
*/
24+
const contextValue = useMemo(() => {
25+
return { store: PCore.getStore(), displayOnlyFA: true };
26+
}, [PCore.getStore()]);
27+
28+
return <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
29+
}
30+
31+
interface PegaContextProps {
32+
isPegaReady: boolean;
33+
rootPConnect?: typeof PConnect; // Function to get Pega Connect object, if available
34+
createCase: (mashupCaseType: string, options: CaseOptions) => Promise<void>;
35+
PegaContainer: React.FC;
36+
}
37+
38+
declare const myLoadMashup: any;
39+
40+
const PegaContext = createContext<PegaContextProps | undefined>(undefined);
41+
42+
interface PegaReadyProviderProps {
43+
theme: any;
44+
}
45+
46+
export const PegaReadyProvider: React.FC<React.PropsWithChildren<PegaReadyProviderProps>> = ({ children, theme }) => {
47+
const { isAuthenticated } = usePegaAuth();
48+
const [isPegaReady, setIsPegaReady] = useState<boolean>(false);
49+
const [rootProps, setRootProps] = useState<{
50+
getPConnect?: () => typeof PConnect;
51+
[key: string]: any;
52+
}>({});
53+
54+
const [loading, setLoading] = useState<boolean>(false);
55+
56+
const startMashup = async () => {
57+
try {
58+
PCore.onPCoreReady(async renderObj => {
59+
console.log(`PCore ready!`);
60+
61+
const theComponentMap = await getSdkComponentMap(localSdkComponentMap);
62+
console.log(`SdkComponentMap initialized`, theComponentMap);
63+
64+
const { props } = renderObj;
65+
setRootProps(props);
66+
setIsPegaReady(true);
67+
});
68+
69+
// load the Mashup and handle the onPCoreEntry response that establishes the
70+
// top level Pega root element (likely a RootContainer)
71+
myLoadMashup('pega-root', false); // this is defined in bootstrap shell that's been loaded already
72+
} catch (error) {
73+
console.error('Error loading pega:', error);
74+
}
75+
};
76+
77+
/**
78+
* Start the mashup once authenticated
79+
* This ensures that the Pega environment is ready for use
80+
*/
81+
useEffect(() => {
82+
if (isAuthenticated) {
83+
startMashup();
84+
}
85+
}, [isAuthenticated]);
86+
87+
// Memoize the root PConnect function to avoid unnecessary re-renders
88+
const rootPConnect = useMemo(() => {
89+
if (rootProps && rootProps?.getPConnect) {
90+
return rootProps.getPConnect();
91+
}
92+
93+
return undefined;
94+
}, [rootProps]);
95+
96+
const createCase = (mashupCaseType: string, options: CaseOptions) => {
97+
if (!isPegaReady) {
98+
console.error('Pega is not ready. Cannot create case.');
99+
return Promise.reject('Pega is not ready');
100+
}
101+
102+
setLoading(true);
103+
return new Promise<void>((resolve, reject) => {
104+
// If mashupCaseType is null or undefined, get the first case type from the environment info
105+
if (!mashupCaseType) {
106+
const caseTypes = PCore.getEnvironmentInfo()?.environmentInfoObject?.pyCaseTypeList;
107+
if (caseTypes && caseTypes.length > 0) {
108+
mashupCaseType = caseTypes[0].pyWorkTypeImplementationClassName;
109+
}
110+
}
111+
112+
PCore.getMashupApi()
113+
.createCase(mashupCaseType, PCore.getConstants().APP.APP, options)
114+
.then(() => {
115+
resolve();
116+
})
117+
.catch(error => {
118+
console.error('Error creating case:', error);
119+
reject(error);
120+
})
121+
.finally(() => {
122+
setLoading(false);
123+
});
124+
});
125+
};
126+
127+
const PegaContainer = () => {
128+
if (loading) return <div style={{ textAlign: 'center' }}>Loading...</div>;
129+
130+
return isPegaReady ? <RootComponent {...rootProps} /> : null;
131+
};
132+
133+
return (
134+
<PegaContext.Provider value={{ isPegaReady, rootPConnect, createCase, PegaContainer }}>
135+
<StyledEngineProvider injectFirst>
136+
<ThemeProvider theme={theme}>
137+
<CssBaseline />
138+
{children}
139+
</ThemeProvider>
140+
</StyledEngineProvider>
141+
</PegaContext.Provider>
142+
);
143+
};
144+
145+
export const usePega = () => {
146+
const context = useContext(PegaContext);
147+
if (!context) {
148+
throw new Error('usePega must be used within a PegaProvider');
149+
}
150+
return context;
151+
};

0 commit comments

Comments
 (0)