@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +9 -4
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -12
- package/utils/templateVarUtils.js +16 -0
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CapActionButton/index.js +73 -60
- package/v2Components/CapActionButton/index.scss +44 -28
- package/v2Components/CapActionButton/messages.js +7 -3
- package/v2Components/CapActionButton/tests/index.test.js +17 -1
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
- package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/actions.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +8 -0
- package/v2Components/CommonTestAndPreview/index.js +55 -3
- package/v2Components/CommonTestAndPreview/reducer.js +20 -0
- package/v2Components/CommonTestAndPreview/sagas.js +28 -0
- package/v2Components/CommonTestAndPreview/selectors.js +6 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +46 -320
- package/v2Components/FormBuilder/Functional/channels/registry.js +0 -2
- package/v2Components/FormBuilder/Functional/constants.js +8 -43
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +28 -175
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +6 -75
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +20 -10
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +1 -9
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +3 -20
- package/v2Components/FormBuilder/Functional/layout/Section.js +1 -6
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +2 -9
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -17
- package/v2Components/FormBuilder/index.js +12 -34
- package/v2Components/FormBuilder/tests/entryGate.test.js +7 -94
- package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +65 -4
- package/v2Containers/CommunicationFlow/index.js +16 -23
- package/v2Containers/CommunicationFlow/messages.js +11 -3
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
- package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/index.js +29 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
- package/v2Containers/MobilePush/Create/index.js +18 -1
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
- package/v2Containers/MobilePush/Edit/index.js +18 -1
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
- package/v2Containers/MobilePush/commonMethods.js +49 -1
- package/v2Containers/MobilePushNew/index.js +29 -4
- package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
- package/v2Containers/MobilePushNew/utils.js +34 -1
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +34 -0
- package/v2Containers/Rcs/index.scss +15 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
- package/v2Containers/Rcs/tests/index.test.js +67 -0
- package/v2Containers/Sms/Create/index.js +3 -1
- package/v2Containers/Sms/Edit/index.js +25 -0
- package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
- package/v2Containers/Viber/index.js +24 -1
- package/v2Containers/Viber/tests/index.test.js +103 -0
- package/v2Containers/WebPush/Create/index.js +19 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
- package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
- package/v2Containers/Whatsapp/tests/index.test.js +115 -0
- package/v2Containers/Zalo/index.js +28 -1
- package/v2Containers/Zalo/tests/index.test.js +99 -0
- package/v2Containers/mockdata.js +25 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +0 -10
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +0 -82
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +0 -74
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +0 -28
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +0 -19
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +0 -45
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +0 -162
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +0 -37
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +0 -70
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +0 -274
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +0 -251
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +0 -89
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +0 -440
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +0 -430
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +0 -282
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +0 -141
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +0 -306
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +0 -151
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +0 -342
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +0 -110
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +0 -459
|
@@ -181,6 +181,8 @@ export function SlideBoxContent(props) {
|
|
|
181
181
|
onPersonalizationTokensChange,
|
|
182
182
|
isTestAndPreviewMode,
|
|
183
183
|
onHtmlEditorValidationStateChange,
|
|
184
|
+
onEmbeddedSmsFooterValidity,
|
|
185
|
+
onContentValidityChange,
|
|
184
186
|
} = props;
|
|
185
187
|
const localTemplatesConfig = props.localTemplatesConfig || pick(props, constants.LOCAL_TEMPLATE_CONFIG_KEYS);
|
|
186
188
|
const useLocalTemplates = !!get(localTemplatesConfig, 'useLocalTemplates');
|
|
@@ -456,6 +458,7 @@ export function SlideBoxContent(props) {
|
|
|
456
458
|
eventContextTags,
|
|
457
459
|
waitEventContextTags,
|
|
458
460
|
handleClose,
|
|
461
|
+
onContentValidityChange,
|
|
459
462
|
};
|
|
460
463
|
|
|
461
464
|
return (
|
|
@@ -602,6 +605,7 @@ export function SlideBoxContent(props) {
|
|
|
602
605
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
603
606
|
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
604
607
|
onValidationFail={onValidationFail}
|
|
608
|
+
onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity}
|
|
605
609
|
/>
|
|
606
610
|
)}
|
|
607
611
|
{isEditFTP && (
|
|
@@ -644,6 +648,7 @@ export function SlideBoxContent(props) {
|
|
|
644
648
|
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
645
649
|
handleTestAndPreview={handleTestAndPreview}
|
|
646
650
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
651
|
+
onContentValidityChange={onContentValidityChange}
|
|
647
652
|
/>
|
|
648
653
|
}
|
|
649
654
|
{
|
|
@@ -667,6 +672,7 @@ export function SlideBoxContent(props) {
|
|
|
667
672
|
templateData={templateData}
|
|
668
673
|
isGetFormData={isGetFormData}
|
|
669
674
|
getFormSubscriptionData={getFormData}
|
|
675
|
+
onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity}
|
|
670
676
|
getLiquidTags={getLiquidTags}
|
|
671
677
|
getDefaultTags={type}
|
|
672
678
|
isFullMode={isFullMode}
|
|
@@ -866,6 +872,7 @@ export function SlideBoxContent(props) {
|
|
|
866
872
|
restrictPersonalization={restrictPersonalization}
|
|
867
873
|
isAnonymousType={isAnonymousType}
|
|
868
874
|
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
875
|
+
onContentValidityChange={onContentValidityChange}
|
|
869
876
|
/>
|
|
870
877
|
) : (
|
|
871
878
|
<MobilePushNew
|
|
@@ -897,6 +904,7 @@ export function SlideBoxContent(props) {
|
|
|
897
904
|
restrictPersonalization={restrictPersonalization}
|
|
898
905
|
isAnonymousType={isAnonymousType}
|
|
899
906
|
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
907
|
+
onContentValidityChange={onContentValidityChange}
|
|
900
908
|
/>
|
|
901
909
|
)
|
|
902
910
|
)}
|
|
@@ -939,6 +947,7 @@ export function SlideBoxContent(props) {
|
|
|
939
947
|
restrictPersonalization={restrictPersonalization}
|
|
940
948
|
isAnonymousType={isAnonymousType}
|
|
941
949
|
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
950
|
+
onContentValidityChange={onContentValidityChange}
|
|
942
951
|
/>
|
|
943
952
|
) : (
|
|
944
953
|
<MobilePushNew
|
|
@@ -976,6 +985,7 @@ export function SlideBoxContent(props) {
|
|
|
976
985
|
creativesMode={creativesMode}
|
|
977
986
|
restrictPersonalization={restrictPersonalization}
|
|
978
987
|
isAnonymousType={isAnonymousType}
|
|
988
|
+
onContentValidityChange={onContentValidityChange}
|
|
979
989
|
/>
|
|
980
990
|
)
|
|
981
991
|
)}
|
|
@@ -1085,6 +1095,7 @@ export function SlideBoxContent(props) {
|
|
|
1085
1095
|
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
1086
1096
|
handleTestAndPreview={handleTestAndPreview}
|
|
1087
1097
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1098
|
+
onContentValidityChange={onContentValidityChange}
|
|
1088
1099
|
createNew/>
|
|
1089
1100
|
)}
|
|
1090
1101
|
|
|
@@ -1107,6 +1118,7 @@ export function SlideBoxContent(props) {
|
|
|
1107
1118
|
eventContextTags={eventContextTags}
|
|
1108
1119
|
waitEventContextTags={waitEventContextTags}
|
|
1109
1120
|
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
1121
|
+
onContentValidityChange={onContentValidityChange}
|
|
1110
1122
|
createNew/> }
|
|
1111
1123
|
|
|
1112
1124
|
{isCreateWhatsapp && (<Whatsapp
|
|
@@ -1120,6 +1132,7 @@ export function SlideBoxContent(props) {
|
|
|
1120
1132
|
handleTestAndPreview={handleTestAndPreview}
|
|
1121
1133
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1122
1134
|
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
1135
|
+
onContentValidityChange={onContentValidityChange}
|
|
1123
1136
|
/>
|
|
1124
1137
|
)}
|
|
1125
1138
|
|
|
@@ -1145,6 +1158,7 @@ export function SlideBoxContent(props) {
|
|
|
1145
1158
|
search: '',
|
|
1146
1159
|
}}
|
|
1147
1160
|
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
1161
|
+
onContentValidityChange={onContentValidityChange}
|
|
1148
1162
|
/>
|
|
1149
1163
|
)}
|
|
1150
1164
|
|
|
@@ -1270,6 +1284,7 @@ export function SlideBoxContent(props) {
|
|
|
1270
1284
|
restrictPersonalization={restrictPersonalization}
|
|
1271
1285
|
isAnonymousType={isAnonymousType}
|
|
1272
1286
|
waitEventContextTags={waitEventContextTags}
|
|
1287
|
+
onContentValidityChange={onContentValidityChange}
|
|
1273
1288
|
/>
|
|
1274
1289
|
)}
|
|
1275
1290
|
{isCreateRcs && (<Rcs
|
|
@@ -51,6 +51,8 @@ function SlideBoxFooter(props) {
|
|
|
51
51
|
hasPersonalizationTokenError: hasPersonalizationTokenErrorProp = false,
|
|
52
52
|
/** When set (e.g. SMS library create), overrides `creativesTemplatesSave` (“Done”) for the primary button */
|
|
53
53
|
primarySaveButtonMessage,
|
|
54
|
+
/** Live content-validity signal reported by the channel's own editor (currently SMS create) — true disables Save/Done. */
|
|
55
|
+
isContentInvalid = false,
|
|
54
56
|
} = props;
|
|
55
57
|
// Calculate if buttons should be disabled
|
|
56
58
|
// Only apply validation state checks for EMAIL channel in HTML Editor mode (not BEE/DragDrop)
|
|
@@ -193,7 +195,7 @@ function SlideBoxFooter(props) {
|
|
|
193
195
|
<CapRow useLegacy>
|
|
194
196
|
<CapButton
|
|
195
197
|
onClick={onSave}
|
|
196
|
-
disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons || hasPersonalizationTokenError}
|
|
198
|
+
disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons || hasPersonalizationTokenError || isContentInvalid}
|
|
197
199
|
>
|
|
198
200
|
{getSaveButtonLabel()}
|
|
199
201
|
</CapButton>
|
|
@@ -201,7 +203,7 @@ function SlideBoxFooter(props) {
|
|
|
201
203
|
<CapButton
|
|
202
204
|
type="secondary"
|
|
203
205
|
onClick={onTestAndPreview}
|
|
204
|
-
disabled={shouldDisableButtons || hasPersonalizationTokenError}
|
|
206
|
+
disabled={shouldDisableButtons || hasPersonalizationTokenError || isContentInvalid}
|
|
205
207
|
style={{ marginLeft: '8px' }}
|
|
206
208
|
>
|
|
207
209
|
<FormattedMessage {...messages.testAndPreview} />
|
|
@@ -272,6 +274,7 @@ SlideBoxFooter.propTypes = {
|
|
|
272
274
|
id: PropTypes.string,
|
|
273
275
|
defaultMessage: PropTypes.string,
|
|
274
276
|
}),
|
|
277
|
+
isContentInvalid: PropTypes.bool,
|
|
275
278
|
};
|
|
276
279
|
|
|
277
280
|
SlideBoxFooter.defaultProps = {
|
|
@@ -300,5 +303,6 @@ SlideBoxFooter.defaultProps = {
|
|
|
300
303
|
formData: [],
|
|
301
304
|
hasPersonalizationTokenError: false,
|
|
302
305
|
primarySaveButtonMessage: undefined,
|
|
306
|
+
isContentInvalid: false,
|
|
303
307
|
};
|
|
304
308
|
export default SlideBoxFooter;
|
|
@@ -164,7 +164,6 @@ export class Creatives extends React.Component {
|
|
|
164
164
|
isFullMode: props.isFullMode,
|
|
165
165
|
useLocalTemplates,
|
|
166
166
|
});
|
|
167
|
-
|
|
168
167
|
this.state = {
|
|
169
168
|
isLoadingContent: true,
|
|
170
169
|
templateStep: 1, //modeSelection: for template name and (for email selecting upload or editor), and createTemplate to create template create based on modeSelection
|
|
@@ -198,6 +197,9 @@ export class Creatives extends React.Component {
|
|
|
198
197
|
errorsAcknowledged: false, // Flag to track if user has acknowledged errors by clicking redirection icon
|
|
199
198
|
},
|
|
200
199
|
hasPersonalizationTokenError: false, // Track personalization token errors in form
|
|
200
|
+
// Live content-validity signal for channels that report it (currently SMS create) —
|
|
201
|
+
// false by default so unwired channels are never blocked by this flag.
|
|
202
|
+
isContentInvalid: false,
|
|
201
203
|
};
|
|
202
204
|
this.creativesTemplateSteps = {
|
|
203
205
|
1: 'modeSelection',
|
|
@@ -413,7 +415,7 @@ export class Creatives extends React.Component {
|
|
|
413
415
|
this.setState({ isGetFormData: false });
|
|
414
416
|
};
|
|
415
417
|
|
|
416
|
-
mapCarouselDataToCreatives = (cards) => cards.map((card) => {
|
|
418
|
+
mapCarouselDataToCreatives = (cards) => (cards || []).map((card) => {
|
|
417
419
|
const {
|
|
418
420
|
cardVarMapped, bodyTemplate, media, buttons, mediaType,
|
|
419
421
|
} = card || {};
|
|
@@ -1894,6 +1896,26 @@ export class Creatives extends React.Component {
|
|
|
1894
1896
|
this.setState({ isDiscardMessage: true });
|
|
1895
1897
|
}
|
|
1896
1898
|
|
|
1899
|
+
// Reported live by SMS's own content editor (Sms/Create and Sms/Edit's componentDidUpdate)
|
|
1900
|
+
// so the Done/Save button reflects an empty/invalid form immediately, instead of only
|
|
1901
|
+
// failing silently on click.
|
|
1902
|
+
handleEmbeddedSmsFooterValidity = (validity) => {
|
|
1903
|
+
const isContentInvalid = !!validity?.isMessageEmpty;
|
|
1904
|
+
if (this.state.isContentInvalid !== isContentInvalid) {
|
|
1905
|
+
this.setState({ isContentInvalid });
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// Generic counterpart of the above for every other channel wrapper (Email, MobilePush,
|
|
1910
|
+
// Zalo, WebPush, RCS, WhatsApp, Viber) — each reports { isContentEmpty } live from its own
|
|
1911
|
+
// form state so the Done/Preview-and-test buttons disable immediately, not just on click.
|
|
1912
|
+
handleContentValidityChange = (validity) => {
|
|
1913
|
+
const isContentInvalid = !!validity?.isContentEmpty;
|
|
1914
|
+
if (this.state.isContentInvalid !== isContentInvalid) {
|
|
1915
|
+
this.setState({ isContentInvalid });
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1897
1919
|
|
|
1898
1920
|
// NEW: Handler for Test and Preview button
|
|
1899
1921
|
handleTestAndPreview = () => {
|
|
@@ -2214,6 +2236,7 @@ export class Creatives extends React.Component {
|
|
|
2214
2236
|
isTestAndPreviewMode,
|
|
2215
2237
|
inAppEditorType,
|
|
2216
2238
|
htmlEditorValidationState,
|
|
2239
|
+
isContentInvalid,
|
|
2217
2240
|
} = this.state;
|
|
2218
2241
|
const useLocalTemplates = get(
|
|
2219
2242
|
this.props,
|
|
@@ -2411,6 +2434,8 @@ export class Creatives extends React.Component {
|
|
|
2411
2434
|
handleCloseTestAndPreview={this.handleCloseTestAndPreview}
|
|
2412
2435
|
isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
|
|
2413
2436
|
onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
|
|
2437
|
+
onEmbeddedSmsFooterValidity={this.handleEmbeddedSmsFooterValidity}
|
|
2438
|
+
onContentValidityChange={this.handleContentValidityChange}
|
|
2414
2439
|
onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
|
|
2415
2440
|
localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
|
|
2416
2441
|
/>
|
|
@@ -2433,6 +2458,7 @@ export class Creatives extends React.Component {
|
|
|
2433
2458
|
shouldShowDoneFooter={this.shouldShowDoneFooter}
|
|
2434
2459
|
fetchingCmsData={fetchingCmsData}
|
|
2435
2460
|
isTemplateNameEmpty={isTemplateNameEmpty}
|
|
2461
|
+
isContentInvalid={isContentInvalid}
|
|
2436
2462
|
isLiquidValidationError={isLiquidValidationError}
|
|
2437
2463
|
errorMessages={liquidErrorMessage}
|
|
2438
2464
|
currentTab={activeFormBuilderTab}
|
|
@@ -2462,7 +2488,7 @@ export class Creatives extends React.Component {
|
|
|
2462
2488
|
)}
|
|
2463
2489
|
handleClose={this.handleCloseSlideBox}
|
|
2464
2490
|
show={showSlideBox}
|
|
2465
|
-
size="size-
|
|
2491
|
+
size="size-l"
|
|
2466
2492
|
/>
|
|
2467
2493
|
</SlideBoxWrapper>
|
|
2468
2494
|
);
|
|
@@ -658,4 +658,33 @@ describe('SlideBoxFooter — isBEEEditor detection in create mode via emailCreat
|
|
|
658
658
|
expect(screen.getByRole('button', { name: /done/i })).toBeInTheDocument();
|
|
659
659
|
});
|
|
660
660
|
});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
describe('SlideBoxFooter — isContentInvalid (live content-validity signal, e.g. empty SMS body)', () => {
|
|
664
|
+
it('disables Save/Done when isContentInvalid is true, independent of currentChannel/htmlEditorValidationState', () => {
|
|
665
|
+
renderComponent({
|
|
666
|
+
...baseFooterProps,
|
|
667
|
+
currentChannel: 'SMS',
|
|
668
|
+
isContentInvalid: true,
|
|
669
|
+
});
|
|
670
|
+
expect(screen.getByRole('button', { name: /update/i })).toBeDisabled();
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('disables the Test & Preview button when isContentInvalid is true', () => {
|
|
674
|
+
renderComponent({
|
|
675
|
+
...baseFooterProps,
|
|
676
|
+
currentChannel: 'SMS',
|
|
677
|
+
isContentInvalid: true,
|
|
678
|
+
showTestAndPreviewButton: true,
|
|
679
|
+
});
|
|
680
|
+
expect(screen.getByRole('button', { name: /preview.*test/i })).toBeDisabled();
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('leaves Save/Done enabled when isContentInvalid is false (default)', () => {
|
|
684
|
+
renderComponent({
|
|
685
|
+
...baseFooterProps,
|
|
686
|
+
currentChannel: 'SMS',
|
|
687
|
+
});
|
|
688
|
+
expect(screen.getByRole('button', { name: /update/i })).not.toBeDisabled();
|
|
689
|
+
});
|
|
661
690
|
});
|
|
@@ -28,10 +28,12 @@ exports[`Test SlideBoxContent container campaign message, add creative click rcs
|
|
|
28
28
|
}
|
|
29
29
|
onCallTaskSubmit={[Function]}
|
|
30
30
|
onChannelChange={[Function]}
|
|
31
|
+
onContentValidityChange={[Function]}
|
|
31
32
|
onCreateComplete={[MockFunction]}
|
|
32
33
|
onCreateNew={[Function]}
|
|
33
34
|
onCreateNextStep={[Function]}
|
|
34
35
|
onEmailModeChange={[Function]}
|
|
36
|
+
onEmbeddedSmsFooterValidity={[Function]}
|
|
35
37
|
onEnterTemplateName={[Function]}
|
|
36
38
|
onFTPSubmit={[MockFunction]}
|
|
37
39
|
onFacebookSubmit={[MockFunction]}
|
|
@@ -89,7 +91,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click rcs
|
|
|
89
91
|
/>
|
|
90
92
|
}
|
|
91
93
|
show={true}
|
|
92
|
-
size="size-
|
|
94
|
+
size="size-l"
|
|
93
95
|
/>
|
|
94
96
|
</CreativesSlideBoxWrapper>
|
|
95
97
|
`;
|
|
@@ -122,10 +124,12 @@ exports[`Test SlideBoxContent container campaign message, add creative click wha
|
|
|
122
124
|
}
|
|
123
125
|
onCallTaskSubmit={[Function]}
|
|
124
126
|
onChannelChange={[Function]}
|
|
127
|
+
onContentValidityChange={[Function]}
|
|
125
128
|
onCreateComplete={[MockFunction]}
|
|
126
129
|
onCreateNew={[Function]}
|
|
127
130
|
onCreateNextStep={[Function]}
|
|
128
131
|
onEmailModeChange={[Function]}
|
|
132
|
+
onEmbeddedSmsFooterValidity={[Function]}
|
|
129
133
|
onEnterTemplateName={[Function]}
|
|
130
134
|
onFTPSubmit={[MockFunction]}
|
|
131
135
|
onFacebookSubmit={[MockFunction]}
|
|
@@ -183,7 +187,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click wha
|
|
|
183
187
|
/>
|
|
184
188
|
}
|
|
185
189
|
show={true}
|
|
186
|
-
size="size-
|
|
190
|
+
size="size-l"
|
|
187
191
|
/>
|
|
188
192
|
</CreativesSlideBoxWrapper>
|
|
189
193
|
`;
|
|
@@ -216,10 +220,12 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
|
|
|
216
220
|
}
|
|
217
221
|
onCallTaskSubmit={[Function]}
|
|
218
222
|
onChannelChange={[Function]}
|
|
223
|
+
onContentValidityChange={[Function]}
|
|
219
224
|
onCreateComplete={[MockFunction]}
|
|
220
225
|
onCreateNew={[Function]}
|
|
221
226
|
onCreateNextStep={[Function]}
|
|
222
227
|
onEmailModeChange={[Function]}
|
|
228
|
+
onEmbeddedSmsFooterValidity={[Function]}
|
|
223
229
|
onEnterTemplateName={[Function]}
|
|
224
230
|
onFTPSubmit={[MockFunction]}
|
|
225
231
|
onFacebookSubmit={[MockFunction]}
|
|
@@ -281,6 +287,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
|
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
isAnonymousType={false}
|
|
290
|
+
isContentInvalid={false}
|
|
284
291
|
isContinueButtonDisabled={false}
|
|
285
292
|
isCreatingTemplate={false}
|
|
286
293
|
isEmptyContent={false}
|
|
@@ -320,7 +327,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
|
|
|
320
327
|
/>
|
|
321
328
|
}
|
|
322
329
|
show={false}
|
|
323
|
-
size="size-
|
|
330
|
+
size="size-l"
|
|
324
331
|
/>
|
|
325
332
|
</CreativesSlideBoxWrapper>
|
|
326
333
|
`;
|
|
@@ -353,10 +360,12 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
|
|
|
353
360
|
}
|
|
354
361
|
onCallTaskSubmit={[Function]}
|
|
355
362
|
onChannelChange={[Function]}
|
|
363
|
+
onContentValidityChange={[Function]}
|
|
356
364
|
onCreateComplete={[MockFunction]}
|
|
357
365
|
onCreateNew={[Function]}
|
|
358
366
|
onCreateNextStep={[Function]}
|
|
359
367
|
onEmailModeChange={[Function]}
|
|
368
|
+
onEmbeddedSmsFooterValidity={[Function]}
|
|
360
369
|
onEnterTemplateName={[Function]}
|
|
361
370
|
onFTPSubmit={[MockFunction]}
|
|
362
371
|
onFacebookSubmit={[MockFunction]}
|
|
@@ -418,6 +427,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
|
|
|
418
427
|
}
|
|
419
428
|
}
|
|
420
429
|
isAnonymousType={false}
|
|
430
|
+
isContentInvalid={false}
|
|
421
431
|
isContinueButtonDisabled={false}
|
|
422
432
|
isCreatingTemplate={false}
|
|
423
433
|
isEmptyContent={false}
|
|
@@ -457,7 +467,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
|
|
|
457
467
|
/>
|
|
458
468
|
}
|
|
459
469
|
show={false}
|
|
460
|
-
size="size-
|
|
470
|
+
size="size-l"
|
|
461
471
|
/>
|
|
462
472
|
</CreativesSlideBoxWrapper>
|
|
463
473
|
`;
|
|
@@ -490,10 +500,12 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
|
|
|
490
500
|
}
|
|
491
501
|
onCallTaskSubmit={[Function]}
|
|
492
502
|
onChannelChange={[Function]}
|
|
503
|
+
onContentValidityChange={[Function]}
|
|
493
504
|
onCreateComplete={[MockFunction]}
|
|
494
505
|
onCreateNew={[Function]}
|
|
495
506
|
onCreateNextStep={[Function]}
|
|
496
507
|
onEmailModeChange={[Function]}
|
|
508
|
+
onEmbeddedSmsFooterValidity={[Function]}
|
|
497
509
|
onEnterTemplateName={[Function]}
|
|
498
510
|
onFTPSubmit={[MockFunction]}
|
|
499
511
|
onFacebookSubmit={[MockFunction]}
|
|
@@ -555,6 +567,7 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
|
|
|
555
567
|
}
|
|
556
568
|
}
|
|
557
569
|
isAnonymousType={false}
|
|
570
|
+
isContentInvalid={false}
|
|
558
571
|
isContinueButtonDisabled={false}
|
|
559
572
|
isCreatingTemplate={false}
|
|
560
573
|
isEmptyContent={false}
|
|
@@ -594,7 +607,7 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
|
|
|
594
607
|
/>
|
|
595
608
|
}
|
|
596
609
|
show={false}
|
|
597
|
-
size="size-
|
|
610
|
+
size="size-l"
|
|
598
611
|
/>
|
|
599
612
|
</CreativesSlideBoxWrapper>
|
|
600
613
|
`;
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
whatsappGetCreativeData2,
|
|
11
11
|
whatsappGetTemplateData1,
|
|
12
12
|
whatsappGetTemplateData2,
|
|
13
|
+
whatsappGetTemplateDataNullCards,
|
|
13
14
|
rcsTemplates,
|
|
14
15
|
rcsEditTemplateData,
|
|
15
16
|
smsEditTemplateData,
|
|
@@ -77,6 +78,17 @@ describe('Test SlideBoxContent container', () => {
|
|
|
77
78
|
expect(handleCloseCreatives).toHaveBeenCalledWith(true);
|
|
78
79
|
});
|
|
79
80
|
|
|
81
|
+
it('does not throw when templateConfigs.cards is explicitly null (regression: CCS sends null rather than omitting the field on non-carousel WhatsApp templates, and mapCarouselDataToCreatives called .map() straight off it)', () => {
|
|
82
|
+
expect(() =>
|
|
83
|
+
renderFunction(
|
|
84
|
+
'WHATSAPP',
|
|
85
|
+
'editTemplate',
|
|
86
|
+
whatsappTemplates,
|
|
87
|
+
whatsappGetTemplateDataNullCards,
|
|
88
|
+
),
|
|
89
|
+
).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
|
|
80
92
|
it('it should clear the url, on channel change from new whatsapp to another', () => {
|
|
81
93
|
renderFunction(
|
|
82
94
|
'WHATSAPP',
|
|
@@ -30,7 +30,7 @@ import getEventsMap from '../eventsMap';
|
|
|
30
30
|
import { GA } from '@capillarytech/cap-ui-utils';
|
|
31
31
|
import { CREATE, TRACK_CREATE_MPUSH } from '../../App/constants';
|
|
32
32
|
import { MOBILE_PUSH } from '../../CreativesContainer/constants';
|
|
33
|
-
import { getContent } from '../commonMethods';
|
|
33
|
+
import { getContent, getMobilePushEmbeddedContentValidity } from '../commonMethods';
|
|
34
34
|
import { getCdnUrl } from '../../../utils/cdnTransformation';
|
|
35
35
|
import injectReducer from '../../../utils/injectReducer';
|
|
36
36
|
import injectSaga from '../../../utils/injectSaga';
|
|
@@ -67,6 +67,22 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
67
67
|
}
|
|
68
68
|
this.hasFetchedInitialTagsRef = false;
|
|
69
69
|
this.lastFetchedTagContextRef = null;
|
|
70
|
+
// Tracks the last content-emptiness value reported to the parent so componentDidUpdate
|
|
71
|
+
// does not dispatch on every render. Intentionally undefined (not true) so the first
|
|
72
|
+
// render always reports the real validity rather than assuming the form starts invalid.
|
|
73
|
+
this._lastReportedMobilePushContentEmpty = undefined;
|
|
74
|
+
}
|
|
75
|
+
componentDidUpdate() {
|
|
76
|
+
// Reports live form validity so the host (CreativesContainer) can disable Done/Preview
|
|
77
|
+
// and Test as soon as the message content is cleared — mirrors Sms/Create's own reporting.
|
|
78
|
+
if (typeof this.props.onContentValidityChange !== 'function') {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const validity = getMobilePushEmbeddedContentValidity(this.state?.formData, this.state?.tabCount);
|
|
82
|
+
const isContentEmpty = !!validity.isContentEmpty;
|
|
83
|
+
if (this._lastReportedMobilePushContentEmpty === isContentEmpty) return;
|
|
84
|
+
this._lastReportedMobilePushContentEmpty = isContentEmpty;
|
|
85
|
+
this.props.onContentValidityChange({ isContentEmpty });
|
|
70
86
|
}
|
|
71
87
|
componentWillMount = () => {
|
|
72
88
|
if (this.props.route.name === 'view') {
|
|
@@ -2072,6 +2088,7 @@ Create.propTypes = {
|
|
|
2072
2088
|
getFormLibraryData: PropTypes.func,
|
|
2073
2089
|
onPreviewContentClicked: PropTypes.func,
|
|
2074
2090
|
onTestContentClicked: PropTypes.func,
|
|
2091
|
+
onContentValidityChange: PropTypes.func,
|
|
2075
2092
|
eventContextTags: PropTypes.array,
|
|
2076
2093
|
waitEventContextTags: PropTypes.object,
|
|
2077
2094
|
getLiquidTags: PropTypes.func,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import { Create } from '../index';
|
|
4
|
+
|
|
5
|
+
// Shallow-render the plain class (bypassing the withCreatives/Redux/saga HOC) so this test
|
|
6
|
+
// only exercises the componentDidUpdate → onContentValidityChange wiring in isolation.
|
|
7
|
+
// Mirrors app/v2Containers/Sms/Create/index.js's own reporting and
|
|
8
|
+
// app/v2Containers/MobilePush/Edit/test/contentValidity.test.js.
|
|
9
|
+
|
|
10
|
+
const baseProps = () => ({
|
|
11
|
+
actions: {},
|
|
12
|
+
globalActions: {
|
|
13
|
+
fetchSchemaForEntity: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
Templates: { selectedWeChatAccount: { id: 'acc-1' } },
|
|
16
|
+
Create: {},
|
|
17
|
+
params: {},
|
|
18
|
+
location: { query: { type: 'library', module: 'default' } },
|
|
19
|
+
route: { name: 'mobilepush' },
|
|
20
|
+
router: { push: jest.fn() },
|
|
21
|
+
metaEntities: {},
|
|
22
|
+
intl: { formatMessage: (m) => (m && (m.defaultMessage || m.id)) || '' },
|
|
23
|
+
isFullMode: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('MobilePush/Create — onContentValidityChange reporting', () => {
|
|
27
|
+
it('reports isContentEmpty: true on mount when both Android title and message are empty', () => {
|
|
28
|
+
const onContentValidityChange = jest.fn();
|
|
29
|
+
const wrapper = shallow(<Create {...baseProps()} onContentValidityChange={onContentValidityChange} />);
|
|
30
|
+
wrapper.setState({
|
|
31
|
+
formData: { 0: { 'message-title': '', 'message-editor': '' } },
|
|
32
|
+
tabCount: 1,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
36
|
+
expect.objectContaining({ isContentEmpty: true }),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('reports isContentEmpty: false once the Android message has content', () => {
|
|
41
|
+
const onContentValidityChange = jest.fn();
|
|
42
|
+
const wrapper = shallow(<Create {...baseProps()} onContentValidityChange={onContentValidityChange} />);
|
|
43
|
+
wrapper.setState({
|
|
44
|
+
formData: { 0: { 'message-title': '', 'message-editor': '' } },
|
|
45
|
+
tabCount: 1,
|
|
46
|
+
});
|
|
47
|
+
onContentValidityChange.mockClear();
|
|
48
|
+
|
|
49
|
+
wrapper.setState({
|
|
50
|
+
formData: { 0: { 'message-title': 'Hello', 'message-editor': 'World' } },
|
|
51
|
+
tabCount: 1,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
55
|
+
expect.objectContaining({ isContentEmpty: false }),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('re-reports empty when a previously entered message is cleared out', () => {
|
|
60
|
+
const onContentValidityChange = jest.fn();
|
|
61
|
+
const wrapper = shallow(<Create {...baseProps()} onContentValidityChange={onContentValidityChange} />);
|
|
62
|
+
wrapper.setState({
|
|
63
|
+
formData: { 0: { 'message-title': 'Existing', 'message-editor': 'content' } },
|
|
64
|
+
tabCount: 1,
|
|
65
|
+
});
|
|
66
|
+
onContentValidityChange.mockClear();
|
|
67
|
+
|
|
68
|
+
wrapper.setState({
|
|
69
|
+
formData: { 0: { 'message-title': '', 'message-editor': '' } },
|
|
70
|
+
tabCount: 1,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
74
|
+
expect.objectContaining({ isContentEmpty: true }),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('does not throw and does not report when onContentValidityChange is not provided', () => {
|
|
79
|
+
const wrapper = shallow(<Create {...baseProps()} />);
|
|
80
|
+
expect(() => {
|
|
81
|
+
wrapper.setState({ formData: { 0: { 'message-title': '', 'message-editor': '' } }, tabCount: 1 });
|
|
82
|
+
}).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('does not dispatch again on a re-render that does not change validity (dedup guard)', () => {
|
|
86
|
+
const onContentValidityChange = jest.fn();
|
|
87
|
+
const wrapper = shallow(<Create {...baseProps()} onContentValidityChange={onContentValidityChange} />);
|
|
88
|
+
wrapper.setState({ formData: { 0: { 'message-title': '', 'message-editor': '' } }, tabCount: 1 });
|
|
89
|
+
onContentValidityChange.mockClear();
|
|
90
|
+
|
|
91
|
+
// Unrelated state change — content emptiness is unchanged.
|
|
92
|
+
wrapper.setState({ currentTab: 1 });
|
|
93
|
+
|
|
94
|
+
expect(onContentValidityChange).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -28,7 +28,7 @@ import withCreatives from '../../../hoc/withCreatives';
|
|
|
28
28
|
import { gtmPush } from '../../../utils/gtmTrackers';
|
|
29
29
|
|
|
30
30
|
import getEventsMap from '../eventsMap';
|
|
31
|
-
import {getPrimaryCtaFields, getSecondaryCtaFields, getLinkTypeFields, getContent} from '../commonMethods';
|
|
31
|
+
import {getPrimaryCtaFields, getSecondaryCtaFields, getLinkTypeFields, getContent, getMobilePushEmbeddedContentValidity} from '../commonMethods';
|
|
32
32
|
import { GA } from '@capillarytech/cap-ui-utils';
|
|
33
33
|
import { EDIT, TRACK_EDIT_MPUSH } from '../../App/constants';
|
|
34
34
|
import { MOBILE_PUSH } from '../../CreativesContainer/constants';
|
|
@@ -85,6 +85,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
85
85
|
this.hasFetchedInitialTagsRef = false;
|
|
86
86
|
// Guard: avoid duplicate fetch when multiple TagList instances trigger same context
|
|
87
87
|
this.lastFetchedTagContextRef = null;
|
|
88
|
+
// Tracks the last content-emptiness value reported to the parent so componentDidUpdate
|
|
89
|
+
// does not dispatch on every render. Intentionally undefined (not true) so the first
|
|
90
|
+
// render always reports the real validity rather than assuming the form starts invalid.
|
|
91
|
+
this._lastReportedMobilePushContentEmpty = undefined;
|
|
88
92
|
}
|
|
89
93
|
componentWillMount() {
|
|
90
94
|
this.props.actions.getWeCrmAccounts("mobilepush");
|
|
@@ -126,6 +130,18 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
126
130
|
parent.postMessage(JSON.stringify(response), '*');
|
|
127
131
|
}
|
|
128
132
|
}
|
|
133
|
+
componentDidUpdate() {
|
|
134
|
+
// Reports live form validity so the host (CreativesContainer) can disable Done/Preview
|
|
135
|
+
// and Test as soon as the message content is cleared — mirrors Sms/Edit's own reporting.
|
|
136
|
+
if (typeof this.props.onContentValidityChange !== 'function') {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const validity = getMobilePushEmbeddedContentValidity(this.state.formData, this.state.tabCount);
|
|
140
|
+
const isContentEmpty = !!validity.isContentEmpty;
|
|
141
|
+
if (this._lastReportedMobilePushContentEmpty === isContentEmpty) return;
|
|
142
|
+
this._lastReportedMobilePushContentEmpty = isContentEmpty;
|
|
143
|
+
this.props.onContentValidityChange({ isContentEmpty });
|
|
144
|
+
}
|
|
129
145
|
componentWillReceiveProps(nextProps) {
|
|
130
146
|
if (nextProps.params?.id !== this.props.params?.id) {
|
|
131
147
|
this.hasFetchedInitialTagsRef = false;
|
|
@@ -2337,6 +2353,7 @@ Edit.propTypes = {
|
|
|
2337
2353
|
onValidationFail: PropTypes.func,
|
|
2338
2354
|
onPreviewContentClicked: PropTypes.func,
|
|
2339
2355
|
onTestContentClicked: PropTypes.func,
|
|
2356
|
+
onContentValidityChange: PropTypes.func,
|
|
2340
2357
|
creativesMode: PropTypes.string,
|
|
2341
2358
|
eventContextTags: PropTypes.array,
|
|
2342
2359
|
waitEventContextTags: PropTypes.object,
|