diff --git a/packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx b/packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx index de8b91f6..3ed8c5f6 100644 --- a/packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx +++ b/packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx @@ -1,9 +1,9 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { getSdkConfig } from '@pega/auth/lib/sdk-auth-manager'; import { makeStyles } from '@mui/styles'; -import StoreContext from '../../../bridge/Context/StoreContext'; -import createPConnectComponent from '../../../bridge/react_pconnect'; +import { usePegaAuth } from '../context/PegaAuthProvider'; +import { usePega } from '../context/PegaReadyContext'; import ShoppingOptionCard from '../ShoppingOptionCard'; import ResolutionScreen from '../ResolutionScreen'; @@ -105,45 +105,35 @@ const useStyles = makeStyles(theme => ({ pegaForm: {} })); -function RootComponent(props) { - const PegaConnectObj = createPConnectComponent(); - const thePConnObj = ; +export default function MainScreen() { + const { isAuthenticated } = usePegaAuth(); + const { isPegaReady, PegaContainer, createCase } = usePega(); - /** - * NOTE: For Embedded mode, we add in displayOnlyFA to our React context - * so it is available to any component that may need it. - * VRS: Attempted to remove displayOnlyFA but it presently handles various components which - * SDK does not yet support, so all those need to be fixed up before it can be removed. - * To be done in a future sprint. - */ - const contextValue = useMemo(() => { - return { store: PCore.getStore(), displayOnlyFA: true }; - }, [PCore.getStore()]); - - return {thePConnObj}; -} - -export default function MainScreen(props) { const classes = useStyles(); + const [showPega, setShowPega] = useState(false); const [showLandingPage, setShowLandingPage] = useState(true); const [showResolution, setShowResolution] = useState(false); useEffect(() => { - // Subscribe to the EVENT_CANCEL event to handle the assignment cancellation - PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment'); - // Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion - PCore.getPubSubUtils().subscribe( - PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING, - () => assignmentFinished(), - 'endOfAssignmentProcessing' - ); + if (isPegaReady) { + // Subscribe to the EVENT_CANCEL event to handle the assignment cancellation + PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment'); + + // Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion + PCore.getPubSubUtils().subscribe( + PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING, + () => assignmentFinished(), + 'endOfAssignmentProcessing' + ); + } + return () => { // unsubscribe to the events PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, 'cancelAssignment'); PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING, 'endOfAssignmentProcessing'); }; - }, []); + }, [isPegaReady]); const cancelAssignment = () => { setShowLandingPage(true); @@ -159,14 +149,8 @@ export default function MainScreen(props) { setShowLandingPage(false); setShowPega(true); const sdkConfig = await getSdkConfig(); - let mashupCaseType = sdkConfig.serverConfig.appMashupCaseType; - // If mashupCaseType is null or undefined, get the first case type from the environment info - if (!mashupCaseType) { - const caseTypes = PCore.getEnvironmentInfo()?.environmentInfoObject?.pyCaseTypeList; - if (caseTypes && caseTypes.length > 0) { - mashupCaseType = mashupCaseType = caseTypes[0].pyWorkTypeImplementationClassName; - } - } + const mashupCaseType = sdkConfig.serverConfig.appMashupCaseType; + let selectedPhoneGUID = ''; const phoneName = optionClicked ? optionClicked.trim() : ''; switch (phoneName) { @@ -198,12 +182,10 @@ export default function MainScreen(props) { console.warn(`Unexpected case type: ${mashupCaseType}. PhoneModelss field not set.`); } - // Create a new case using the mashup API - PCore.getMashupApi() - .createCase(mashupCaseType, PCore.getConstants().APP.APP, options) - .then(() => { - console.log('createCase rendering is complete'); - }); + // Call the createCase function from context to create a new case using the mashup API + createCase(mashupCaseType, options).then(() => { + console.log('createCase rendering is complete'); + }); }; function renderLandingPage() { @@ -244,13 +226,15 @@ export default function MainScreen(props) { return (
- +
); } + if (!isAuthenticated) return
Loading...
; + return (
{showLandingPage && renderLandingPage()} diff --git a/packages/react-sdk-components/src/samples/Embedded/context/PegaAuthProvider.tsx b/packages/react-sdk-components/src/samples/Embedded/context/PegaAuthProvider.tsx new file mode 100644 index 00000000..6ebecae6 --- /dev/null +++ b/packages/react-sdk-components/src/samples/Embedded/context/PegaAuthProvider.tsx @@ -0,0 +1,74 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { getSdkConfig, loginIfNecessary, sdkSetAuthHeader, sdkSetCustomTokenParamsCB } from '@pega/auth/lib/sdk-auth-manager'; + +interface AuthContextType { + isAuthenticated: boolean; +} + +const UserAuthContext = createContext(undefined); + +const PegaAuthProvider: React.FC = ({ children }) => { + const [isAuthenticated, setIsAuthenticated] = useState(false); + + const initialize = async () => { + try { + // Add event listener for when logged in and constellation bootstrap is loaded + document.addEventListener('SdkConstellationReady', () => { + setIsAuthenticated(true); + }); + + // Initialize authentication settings + await initializeAuthentication(); + + // this function will handle login process, and SdkConstellationReady event will be fired once PCore is ready + loginIfNecessary({ appName: 'embedded', mainRedirect: false }); + } catch (error) { + console.error('Something went wrong while login', error); + } + }; + + useEffect(() => { + initialize(); + }, []); + + return {children}; +}; + +export default PegaAuthProvider; + +export const usePegaAuth = (): AuthContextType => { + const context = useContext(UserAuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; + +async function initializeAuthentication() { + const { authConfig } = await getSdkConfig(); + + if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'Basic') { + // Service package to use custom auth with Basic + const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}`); + sdkSetAuthHeader(`Basic ${sB64}`); + } + + if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'BasicTO') { + const now = new Date(); + const expTime = new Date(now.getTime() + 5 * 60 * 1000); + let sISOTime = `${expTime.toISOString().split('.')[0]}Z`; + const regex = /[-:]/g; + sISOTime = sISOTime.replace(regex, ''); + // Service package to use custom auth with Basic + const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}:${sISOTime}`); + sdkSetAuthHeader(`Basic ${sB64}`); + } + + if (authConfig.mashupGrantType === 'customBearer' && authConfig.customAuthType === 'CustomIdentifier') { + // Use custom bearer with specific custom parameter to set the desired operator via + // a userIdentifier property. (Caution: highly insecure...being used for simple demonstration) + sdkSetCustomTokenParamsCB(() => { + return { userIdentifier: authConfig.mashupUserIdentifier }; + }); + } +} diff --git a/packages/react-sdk-components/src/samples/Embedded/context/PegaReadyContext.tsx b/packages/react-sdk-components/src/samples/Embedded/context/PegaReadyContext.tsx new file mode 100644 index 00000000..0cba0fa5 --- /dev/null +++ b/packages/react-sdk-components/src/samples/Embedded/context/PegaReadyContext.tsx @@ -0,0 +1,151 @@ +import { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material'; +import type { CaseOptions } from '@pega/pcore-pconnect-typedefs/mashup/types'; + +import StoreContext from '../../../bridge/Context/StoreContext'; +import createPConnectComponent from '../../../bridge/react_pconnect'; +import { getSdkComponentMap } from '../../../bridge/helpers/sdk_component_map'; + +import localSdkComponentMap from '../../../../sdk-local-component-map'; + +import { usePegaAuth } from './PegaAuthProvider'; + +function RootComponent(props) { + const PegaConnectObj = createPConnectComponent(); + const thePConnObj = ; + + /** + * NOTE: For Embedded mode, we add in displayOnlyFA to our React context + * so it is available to any component that may need it. + * VRS: Attempted to remove displayOnlyFA but it presently handles various components which + * SDK does not yet support, so all those need to be fixed up before it can be removed. + * To be done in a future sprint. + */ + const contextValue = useMemo(() => { + return { store: PCore.getStore(), displayOnlyFA: true }; + }, [PCore.getStore()]); + + return {thePConnObj}; +} + +interface PegaContextProps { + isPegaReady: boolean; + rootPConnect?: typeof PConnect; // Function to get Pega Connect object, if available + createCase: (mashupCaseType: string, options: CaseOptions) => Promise; + PegaContainer: React.FC; +} + +declare const myLoadMashup: any; + +const PegaContext = createContext(undefined); + +interface PegaReadyProviderProps { + theme: any; +} + +export const PegaReadyProvider: React.FC> = ({ children, theme }) => { + const { isAuthenticated } = usePegaAuth(); + const [isPegaReady, setIsPegaReady] = useState(false); + const [rootProps, setRootProps] = useState<{ + getPConnect?: () => typeof PConnect; + [key: string]: any; + }>({}); + + const [loading, setLoading] = useState(false); + + const startMashup = async () => { + try { + PCore.onPCoreReady(async renderObj => { + console.log(`PCore ready!`); + + const theComponentMap = await getSdkComponentMap(localSdkComponentMap); + console.log(`SdkComponentMap initialized`, theComponentMap); + + const { props } = renderObj; + setRootProps(props); + setIsPegaReady(true); + }); + + // load the Mashup and handle the onPCoreEntry response that establishes the + // top level Pega root element (likely a RootContainer) + myLoadMashup('pega-root', false); // this is defined in bootstrap shell that's been loaded already + } catch (error) { + console.error('Error loading pega:', error); + } + }; + + /** + * Start the mashup once authenticated + * This ensures that the Pega environment is ready for use + */ + useEffect(() => { + if (isAuthenticated) { + startMashup(); + } + }, [isAuthenticated]); + + // Memoize the root PConnect function to avoid unnecessary re-renders + const rootPConnect = useMemo(() => { + if (rootProps && rootProps?.getPConnect) { + return rootProps.getPConnect(); + } + + return undefined; + }, [rootProps]); + + const createCase = (mashupCaseType: string, options: CaseOptions) => { + if (!isPegaReady) { + console.error('Pega is not ready. Cannot create case.'); + return Promise.reject('Pega is not ready'); + } + + setLoading(true); + return new Promise((resolve, reject) => { + // If mashupCaseType is null or undefined, get the first case type from the environment info + if (!mashupCaseType) { + const caseTypes = PCore.getEnvironmentInfo()?.environmentInfoObject?.pyCaseTypeList; + if (caseTypes && caseTypes.length > 0) { + mashupCaseType = caseTypes[0].pyWorkTypeImplementationClassName; + } + } + + PCore.getMashupApi() + .createCase(mashupCaseType, PCore.getConstants().APP.APP, options) + .then(() => { + resolve(); + }) + .catch(error => { + console.error('Error creating case:', error); + reject(error); + }) + .finally(() => { + setLoading(false); + }); + }); + }; + + const PegaContainer = () => { + if (loading) return
Loading...
; + + return isPegaReady ? : null; + }; + + return ( + + + + + {children} + + + + ); +}; + +export const usePega = () => { + const context = useContext(PegaContext); + if (!context) { + throw new Error('usePega must be used within a PegaProvider'); + } + return context; +}; diff --git a/packages/react-sdk-components/src/samples/Embedded/index.tsx b/packages/react-sdk-components/src/samples/Embedded/index.tsx index e7c891a8..7a88c9d3 100644 --- a/packages/react-sdk-components/src/samples/Embedded/index.tsx +++ b/packages/react-sdk-components/src/samples/Embedded/index.tsx @@ -1,83 +1,20 @@ -import { useEffect, useState } from 'react'; -import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material'; -import { getSdkConfig, loginIfNecessary } from '@pega/auth/lib/sdk-auth-manager'; - -import { getSdkComponentMap } from '../../bridge/helpers/sdk_component_map'; -import { compareSdkPCoreVersions } from '../../components/helpers/versionHelpers'; +import PegaAuthProvider from './context/PegaAuthProvider'; +import { PegaReadyProvider } from './context/PegaReadyContext'; import Header from './Header'; import MainScreen from './MainScreen'; -import localSdkComponentMap from '../../../sdk-local-component-map'; -import { initializeAuthentication } from './utils'; import { theme } from '../../theme'; import './styles.css'; -declare const myLoadMashup: any; - export default function Embedded() { - const [isLoggedIn, setIsLoggedIn] = useState(false); - const [rootProps, setRootProps] = useState({}); - - useEffect(() => { - initialize(); - }, []); - - const initialize = async () => { - try { - // Add event listener for when logged in and constellation bootstrap is loaded - document.addEventListener('SdkConstellationReady', () => handleSdkConstellationReady()); - - const { authConfig } = await getSdkConfig(); - initializeAuthentication(authConfig); - - // this function will handle login process, and SdkConstellationReady event will be fired once PCore is ready - loginIfNecessary({ appName: 'embedded', mainRedirect: false }); - } catch (error) { - console.error('Something went wrong while login', error); - } - }; - - const initializeRootContainerProps = renderObj => { - const { props } = renderObj; - - setRootProps(props); - }; - - const startMashup = () => { - PCore.onPCoreReady(async renderObj => { - // Check that we're seeing the PCore version we expect - compareSdkPCoreVersions(); - - await getSdkComponentMap(localSdkComponentMap); - - console.log(`SdkComponentMap initialized`); - - // Don't call initializeRootContainerProps until SdkComponentMap is fully initialized - initializeRootContainerProps(renderObj); - }); - - myLoadMashup('pega-root', false); // this is defined in bootstrap shell that's been loaded already - }; - - const handleSdkConstellationReady = () => { - setIsLoggedIn(true); - - startMashup(); - }; - return ( - - - - {isLoggedIn ? ( - <> -
- - - ) : ( -
Loading...
- )} - - + + + <> +
+ + + + ); }