Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
7 changes: 6 additions & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
yarnPath: .yarn/releases/yarn-4.5.1.cjs
checksumBehavior: update
nodeLinker: node-modules
npmRegistryServer: "https://registry.npmjs.org"

npmScopes:
webex:
npmRegistryServer: 'https://engci-maven-master.cisco.com/artifactory/api/npm/webex-release-npm/'
npmAuthToken: 'ACCESS_TOKEN_PLACEHOLDER'
npmAlwaysAuth: true
3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// This is a workaround for the fact that JSDOM does not support canvas methods like getContext.
import 'jest-canvas-mock';

// Set up the global that @webex/cc-digital-interactions expects
global.AGENTX_SERVICE = {};

// Web components used in @momentum-design imports rely on browser-only APIs like attachInternals.
// Jest (via JSDOM) doesn't support these, causing runtime errors in tests.
// We mock these methods on HTMLElement to prevent test failures.
Expand Down
20 changes: 20 additions & 0 deletions packages/contact-center/cc-digital-channels/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintConfigPrettier from 'eslint-config-prettier';

export default [
{files: ['**/src/**/*.{js,mjs,cjs,ts,jsx,tsx}']},
{ignores: ['.babelrc.js', '*config.{js,ts}', 'dist', 'node_modules', 'coverage']},
{languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
settings: {react: {version: 'detect'}},
},
eslintPluginPrettierRecommended,
eslintConfigPrettier,
];
9 changes: 9 additions & 0 deletions packages/contact-center/cc-digital-channels/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const jestConfig = require('../../../jest.config.js');

jestConfig.rootDir = '../../../';
jestConfig.testMatch = ['**/cc-digital-channels/tests/**/*.ts', '**/cc-digital-channels/tests/**/*.tsx'];
jestConfig.transformIgnorePatterns = [
'/node_modules/(?!(@momentum-design/components|@momentum-ui/web-components|@momentum-ui/react-collaboration|@lit|lit|cheerio|@popperjs|@webex-engage|@interactjs|react-error-boundary))',
];

module.exports = jestConfig;
72 changes: 72 additions & 0 deletions packages/contact-center/cc-digital-channels/package.json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest refering cc-statino-login or user-state's package.json rather than creating a new one.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@webex/cc-digital-channels",
"description": "Webex Contact Center Widgets: Digital Channels",
"license": "Cisco's General Terms (https://www.cisco.com/site/us/en/about/legal/contract-experience/index.html)",
"version": "1.28.0-ccwidgets.126",
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist/",
"package.json"
],
"scripts": {
"clean": "rm -rf dist && rm -rf node_modules",
"clean:dist": "rm -rf dist",
"build": "yarn run -T tsc",
"build:src": "yarn run clean:dist && yarn run build && webpack",
"build:watch": "webpack --watch",
"test:unit": "jest --coverage",
"test:styles": "eslint",
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/cc-digital-interactions": "3.0.4-beta.3",
"@webex/cc-store": "workspace:*",
"mobx-react-lite": "^4.1.0"
},
"devDependencies": {
"@babel/core": "7.25.2",
"@babel/preset-env": "7.25.4",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.25.9",
"@eslint/js": "^9.20.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.2",
"@testing-library/react": "16.0.1",
"@types/jest": "29.5.14",
"@webex/test-fixtures": "workspace:*",
"babel-jest": "29.7.0",
"babel-loader": "9.2.1",
"css-loader": "7.1.2",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"prettier": "^3.5.1",
"sass": "1.79.5",
"sass-loader": "16.0.2",
"style-loader": "4.0.0",
"ts-jest": "^29.1.1",
"ts-loader": "9.5.1",
"typescript": "5.6.3",
"typescript-eslint": "^8.24.1",
"webpack": "5.94.0",
"webpack-cli": "5.1.4",
"webpack-merge": "6.0.1"
},
"peerDependencies": {
"@momentum-ui/web-components": "^2.23.35",
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, {useMemo} from 'react';
import Engage from '@webex/cc-digital-interactions';

import '@momentum-ui/web-components';
import {DigitalChannelsComponentProps} from './digital-channels.types';

/**
* Presentation component for Digital Channels.
* Renders the Engage widget with proper theming.
*/
const DigitalChannelsComponent: React.FunctionComponent<DigitalChannelsComponentProps> = ({
conversationId,
jwtToken,
dataCenter,
currentTheme = 'LIGHT',
}) => {
// Create a stable key based on critical props to force remount when they change
// This prevents issues with the Froala editor trying to cleanup/reinitialize improperly
const componentKey = useMemo(() => {
return `${conversationId}-${jwtToken.slice(-8)}-${dataCenter}`;
}, [conversationId, jwtToken, dataCenter]);

const isDarkTheme = currentTheme === 'DARK';

return (
<div>
<md-theme id="app-theme" theme="momentumV2" {...(isDarkTheme ? {darktheme: true} : {lighttheme: true})}>
<Engage
key={componentKey}
conversationId={conversationId}
jwtToken={jwtToken}
dataCenter={dataCenter}
interactionId=""
readonly={false}
theme={isDarkTheme ? 'dark' : 'light'}
isVisualRebrand={true}
/>
</md-theme>
</div>
);
};

export {DigitalChannelsComponent};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {ITask} from '@webex/cc-store';

export interface UseDigitalChannelsInitProps {
currentTask: ITask;
jwtToken: string;
dataCenter: string;
logger: {
log: (message: string, meta?: Record<string, unknown>) => void;
error: (message: string, error?: unknown, meta?: Record<string, unknown>) => void;
};
isDigitalChannelsInitialized: boolean;
setDigitalChannelsInitialized: (value: boolean) => void;
skipInit?: boolean;
}

export interface UseDigitalChannelsDataProps {
getAccessToken: () => Promise<string>;
getDataCenter: () => Promise<string | undefined>;
currentTask: ITask | null;
logger?: {
log: (message: string, meta?: Record<string, unknown>) => void;
error: (message: string, meta?: Record<string, unknown>) => void;
};
}

export interface DigitalChannelsProps {
currentTheme?: string;
}

export interface DigitalChannelsComponentProps {
conversationId: string;
jwtToken: string;
dataCenter: string;
currentTheme?: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DigitalChannels dont follow the widget's design.

  • index file is only to bring together the store, helper and presentation.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import {observer} from 'mobx-react-lite';
import {ErrorBoundary} from 'react-error-boundary';

import store from '@webex/cc-store';
import {useDigitalChannelsInit, useDigitalChannelsData} from '../helper';
import {DigitalChannelsComponent} from './DigitalChannelsComponent';
import {DigitalChannelsProps} from './digital-channels.types';

const DigitalChannelsInternal: React.FunctionComponent<DigitalChannelsProps> = observer(({currentTheme}) => {
const {
logger,
currentTask,
isDigitalChannelsInitialized,
setDigitalChannelsInitialized,
getAccessToken,
getDataCenter,
} = store;

// Fetch all required data (token, datacenter, conversationId)
const {jwtToken, dataCenter, conversationId, hasError} = useDigitalChannelsData({
getAccessToken,
getDataCenter,
currentTask,
logger,
});

// Initialize Digital Channels app once we have all required data
const {initialized} = useDigitalChannelsInit({
currentTask: currentTask || ({} as typeof currentTask),
jwtToken: jwtToken || '',
dataCenter: dataCenter || '',
logger,
isDigitalChannelsInitialized,
setDigitalChannelsInitialized,
// Skip initialization if we don't have required data
skipInit: !currentTask || !jwtToken || !dataCenter,
});

// Early return after all hooks are called
if (!currentTask || !jwtToken || !dataCenter || hasError || !initialized || !conversationId) {
return null;
}

return (
<DigitalChannelsComponent
conversationId={conversationId}
jwtToken={jwtToken}
dataCenter={dataCenter}
currentTheme={currentTheme}
/>
);
});

const DigitalChannels: React.FunctionComponent<DigitalChannelsProps> = (props) => {
return (
<ErrorBoundary
fallbackRender={() => <></>}
onError={(error: Error) => {
if (store.onErrorCallback) store.onErrorCallback('DigitalChannels', error);
}}
>
<DigitalChannelsInternal {...props} />
</ErrorBoundary>
);
};

export {DigitalChannels};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to wrap this in react-error-boundary

Loading
Loading