Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -105,45 +105,35 @@ const useStyles = makeStyles(theme => ({
pegaForm: {}
}));

function RootComponent(props) {
const PegaConnectObj = createPConnectComponent();
const thePConnObj = <PegaConnectObj {...props} />;
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 <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
}

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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -244,13 +226,15 @@ export default function MainScreen(props) {
return (
<div className={classes.pegaViewContainer}>
<div className={classes.pegaForm} id='pega-part-of-page'>
<RootComponent {...props} />
<PegaContainer />
<br />
</div>
</div>
);
}

if (!isAuthenticated) return <div style={{ textAlign: 'center' }}>Loading...</div>;

return (
<div className={classes.appContainer}>
{showLandingPage && renderLandingPage()}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AuthContextType | undefined>(undefined);

const PegaAuthProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(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 <UserAuthContext.Provider value={{ isAuthenticated }}>{children}</UserAuthContext.Provider>;
};

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 };
});
}
}
Original file line number Diff line number Diff line change
@@ -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 = <PegaConnectObj {...props} />;

/**
* 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 <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
}

interface PegaContextProps {
isPegaReady: boolean;
rootPConnect?: typeof PConnect; // Function to get Pega Connect object, if available
createCase: (mashupCaseType: string, options: CaseOptions) => Promise<void>;
PegaContainer: React.FC;
}

declare const myLoadMashup: any;

const PegaContext = createContext<PegaContextProps | undefined>(undefined);

interface PegaReadyProviderProps {
theme: any;
}

export const PegaReadyProvider: React.FC<React.PropsWithChildren<PegaReadyProviderProps>> = ({ children, theme }) => {
const { isAuthenticated } = usePegaAuth();
const [isPegaReady, setIsPegaReady] = useState<boolean>(false);
const [rootProps, setRootProps] = useState<{
getPConnect?: () => typeof PConnect;
[key: string]: any;
}>({});

const [loading, setLoading] = useState<boolean>(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<void>((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 <div style={{ textAlign: 'center' }}>Loading...</div>;

return isPegaReady ? <RootComponent {...rootProps} /> : null;
};

return (
<PegaContext.Provider value={{ isPegaReady, rootPConnect, createCase, PegaContainer }}>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</StyledEngineProvider>
</PegaContext.Provider>
);
};

export const usePega = () => {
const context = useContext(PegaContext);
if (!context) {
throw new Error('usePega must be used within a PegaProvider');
}
return context;
};
Loading