@capillarytech/creatives-library 8.0.304 → 8.0.306

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.
@@ -14,6 +14,8 @@ import CapRow from "@capillarytech/cap-ui-library/CapRow";
14
14
  import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
15
15
  import CapButton from "@capillarytech/cap-ui-library/CapButton";
16
16
  import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
17
+ import CapTab from "@capillarytech/cap-ui-library/CapTab";
18
+ import CapInput from "@capillarytech/cap-ui-library/CapInput";
17
19
  import { makeSelectInApp, makeSelectAccount, makeSelectGetTemplateDetailsInProgress } from "./selectors";
18
20
  import * as globalActions from '../Cap/actions';
19
21
  import {
@@ -52,18 +54,20 @@ import {
52
54
  IOS_CAPITAL,
53
55
  } from "./constants";
54
56
  import { INAPP, SMS } from "../CreativesContainer/constants";
57
+ import { AI_CONTENT_BOT_DISABLED } from "../../constants/unified";
55
58
  import {
56
59
  ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
57
60
  } from "../Whatsapp/constants";
58
61
  import { getCdnUrl } from "../../utils/cdnTransformation";
59
62
  import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
60
63
  import { validateInAppContent } from "../../utils/commonUtils";
61
- import { hasLiquidSupportFeature } from "../../utils/common";
64
+ import { hasLiquidSupportFeature, hasNewEditorFlowInAppEnabled } from "../../utils/common";
62
65
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
63
66
  import HTMLEditor from "../../v2Components/HtmlEditor";
64
67
  import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
65
68
  import { INAPP_EDITOR_TYPES } from "../InAppWrapper/constants";
66
69
  import InappAdvanced from "../InappAdvance/index";
70
+ import CapDeviceContent from "../../v2Components/CapDeviceContent";
67
71
  import { ErrorInfoNote } from "../../v2Components/ErrorInfoNote";
68
72
 
69
73
  let editContent = {};
@@ -74,6 +78,7 @@ export const InApp = (props) => {
74
78
  actions,
75
79
  globalActions,
76
80
  isFullMode,
81
+ isLoyaltyModule,
77
82
  onCreateComplete,
78
83
  params,
79
84
  templateData = {},
@@ -878,7 +883,7 @@ export const InApp = (props) => {
878
883
  // Use 'html editor template' as title for HTML editor to differentiate from BEE editor
879
884
  title: isHTMLTemplate ? 'html editor template' : titleAndroid,
880
885
  message: androidMessage,
881
- bodyType: templateLayoutType,
886
+ bodyType: bodyTypeForBackend,
882
887
  expandableDetails: {
883
888
  style: androidExpandableStyle,
884
889
  message: androidMessage,
@@ -1240,11 +1245,13 @@ export const InApp = (props) => {
1240
1245
  && !isBEEeditor
1241
1246
  && !isBeeFreeTemplate;
1242
1247
 
1248
+ const isNewEditorFlowEnabled = !isLoyaltyModule && hasNewEditorFlowInAppEnabled();
1249
+
1243
1250
  // Use state if available, otherwise fall back to direct data check
1244
- const shouldUseHTMLEditor = isHTMLTemplate || isHTMLTemplateFromData;
1251
+ const shouldUseHTMLEditor = isNewEditorFlowEnabled && (isHTMLTemplate || isHTMLTemplateFromData);
1245
1252
 
1246
1253
  // Only route to Bee editor if it's explicitly a Bee editor AND not an HTML template
1247
- const shouldUseBeeEditor = (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
1254
+ const shouldUseBeeEditor = isNewEditorFlowEnabled && (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
1248
1255
 
1249
1256
  // Early returns to avoid nested ternary
1250
1257
  if (isEditInApp && getTemplateDetailsInProgress) {
@@ -1280,10 +1287,157 @@ export const InApp = (props) => {
1280
1287
  );
1281
1288
  }
1282
1289
 
1290
+ // ── Old-flow helpers (used by CapDeviceContent when flag is disabled) ──────
1291
+ const isAiContentBotDisabled = currentOrgDetails?.accessibleFeatures?.includes(AI_CONTENT_BOT_DISABLED);
1292
+
1293
+ const templateDescErrorHandler = (value) => {
1294
+ const { unsupportedTags, isBraceError } = validateTags({
1295
+ content: value,
1296
+ tagsParam: tags,
1297
+ injectedTagsParams: injectedTags,
1298
+ location,
1299
+ tagModule: getDefaultTags,
1300
+ isFullMode,
1301
+ }) || {};
1302
+ if (unsupportedTags?.length > 0) {
1303
+ return formatMessage(globalMessages.unsupportedTagsValidationError, { unsupportedTags });
1304
+ }
1305
+ if (isBraceError) {
1306
+ return formatMessage(globalMessages.braceValidationError);
1307
+ }
1308
+ return '';
1309
+ };
1310
+
1311
+ const onCopyTitleAndContent = () => {
1312
+ if (panes === ANDROID) {
1313
+ setTitleAndroid(titleIos);
1314
+ setTemplateMessageAndroid(templateMessageIos);
1315
+ setInAppImageSrcAndroid(inAppImageSrcIos);
1316
+ } else {
1317
+ setTitleIos(titleAndroid);
1318
+ setTemplateMessageIos(templateMessageAndroid);
1319
+ setInAppImageSrcIos(inAppImageSrcAndroid);
1320
+ }
1321
+ };
1322
+
1323
+ const onTagSelect = (value, index) => {
1324
+ const tag = `{{${value}}}`;
1325
+ if (panes === ANDROID) {
1326
+ if (index === 0) setTitleAndroid((prev) => prev + tag);
1327
+ else setTemplateMessageAndroid((prev) => prev + tag);
1328
+ } else if (index === 0) {
1329
+ setTitleIos((prev) => prev + tag);
1330
+ } else {
1331
+ setTemplateMessageIos((prev) => prev + tag);
1332
+ }
1333
+ };
1334
+
1335
+ // Device support flags (same derivation as InappAdvance)
1336
+ const isAndroidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
1337
+ const isIosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
1338
+
1339
+ // CapDeviceContent tab panes (old flow)
1340
+ const DEVICE_PANES = [
1341
+ {
1342
+ content: (
1343
+ <CapDeviceContent
1344
+ panes={ANDROID}
1345
+ actions={actions}
1346
+ editData={editData}
1347
+ isFullMode={isFullMode}
1348
+ inAppImageSrc={inAppImageSrcAndroid}
1349
+ setInAppImageSrc={setInAppImageSrcAndroid}
1350
+ isEditFlow={isEditFlow}
1351
+ ctaData={ctaDataAndroid}
1352
+ setCtaData={setCtaDataAndroid}
1353
+ buttonType={buttonTypeAndroid}
1354
+ setButtonType={setButtonTypeAndroid}
1355
+ templateMediaType={templateMediaType}
1356
+ setTemplateMediaType={setTemplateMediaType}
1357
+ title={titleAndroid}
1358
+ setTitle={setTitleAndroid}
1359
+ templateMessageError={templateMessageErrorAndroid}
1360
+ templateMessage={templateMessageAndroid}
1361
+ setTemplateMessage={setTemplateMessageAndroid}
1362
+ setTemplateMessageError={setTemplateMessageErrorAndroid}
1363
+ addActionLink={addActionLinkAndroid}
1364
+ setAddActionLink={setAddActionLinkAndroid}
1365
+ deepLink={deepLink}
1366
+ deepLinkValue={deepLinkValueAndroid}
1367
+ setDeepLinkValue={setDeepLinkValueAndroid}
1368
+ onCopyTitleAndContent={onCopyTitleAndContent}
1369
+ isOtherDeviceSupported={isIosSupported}
1370
+ tags={tags}
1371
+ onTagSelect={onTagSelect}
1372
+ handleOnTagsContextChange={handleOnTagsContextChange}
1373
+ templateDescErrorHandler={templateDescErrorHandler}
1374
+ templateTitleError={templateTitleErrorAndroid}
1375
+ setTemplateTitleError={setTemplateTitleErrorAndroid}
1376
+ isAiContentBotDisabled={isAiContentBotDisabled}
1377
+ injectedTags={injectedTags}
1378
+ selectedOfferDetails={selectedOfferDetails}
1379
+ location={location}
1380
+ />
1381
+ ),
1382
+ tab: <FormattedMessage {...messages.Android} />,
1383
+ key: ANDROID,
1384
+ isSupported: isAndroidSupported,
1385
+ },
1386
+ {
1387
+ content: (
1388
+ <CapDeviceContent
1389
+ panes={IOS}
1390
+ actions={actions}
1391
+ editData={editData}
1392
+ isFullMode={isFullMode}
1393
+ inAppImageSrc={inAppImageSrcIos}
1394
+ setInAppImageSrc={setInAppImageSrcIos}
1395
+ isEditFlow={isEditFlow}
1396
+ ctaData={ctaDataIos}
1397
+ setCtaData={setCtaDataIos}
1398
+ buttonType={buttonTypeIos}
1399
+ setButtonType={setButtonTypeIos}
1400
+ templateMediaType={templateMediaType}
1401
+ setTemplateMediaType={setTemplateMediaType}
1402
+ title={titleIos}
1403
+ setTitle={setTitleIos}
1404
+ templateMessageError={templateMessageErrorIos}
1405
+ templateMessage={templateMessageIos}
1406
+ setTemplateMessage={setTemplateMessageIos}
1407
+ setTemplateMessageError={setTemplateMessageErrorIos}
1408
+ addActionLink={addActionLinkIos}
1409
+ setAddActionLink={setAddActionLinkIos}
1410
+ deepLink={deepLink}
1411
+ deepLinkValue={deepLinkValueIos}
1412
+ setDeepLinkValue={setDeepLinkValueIos}
1413
+ onCopyTitleAndContent={onCopyTitleAndContent}
1414
+ isOtherDeviceSupported={isAndroidSupported}
1415
+ tags={tags}
1416
+ onTagSelect={onTagSelect}
1417
+ handleOnTagsContextChange={handleOnTagsContextChange}
1418
+ templateDescErrorHandler={templateDescErrorHandler}
1419
+ templateTitleError={templateTitleErrorIos}
1420
+ setTemplateTitleError={setTemplateTitleErrorIos}
1421
+ isAiContentBotDisabled={isAiContentBotDisabled}
1422
+ injectedTags={injectedTags}
1423
+ selectedOfferDetails={selectedOfferDetails}
1424
+ location={location}
1425
+ />
1426
+ ),
1427
+ tab: <FormattedMessage {...messages.Ios} />,
1428
+ key: IOS,
1429
+ isSupported: isIosSupported,
1430
+ },
1431
+ ];
1432
+ // ─────────────────────────────────────────────────────────────────────────
1433
+
1434
+ // Calculate column span: HTML editor = 18, everything else = 24
1435
+ const editorColumnSpan = shouldUseHTMLEditor ? 18 : 24;
1436
+
1283
1437
  return (
1284
1438
  <CapSpin spinning={spin || fetchingLiquidValidation} tip={fetchingLiquidValidation ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : ""}>
1285
1439
  <CapRow className="cap-inapp-creatives">
1286
- <CapColumn span={shouldUseHTMLEditor ? 18 : 24}>
1440
+ <CapColumn span={editorColumnSpan}>
1287
1441
  {/* Creative layout type */}
1288
1442
  {shouldUseHTMLEditor && (
1289
1443
  <>
@@ -1304,7 +1458,7 @@ export const InApp = (props) => {
1304
1458
  />
1305
1459
  </>
1306
1460
  )}
1307
- {shouldUseHTMLEditor ? (
1461
+ {shouldUseHTMLEditor && (
1308
1462
  <HTMLEditor
1309
1463
  key={`inapp-html-editor-v${htmlEditorContentVersion}`}
1310
1464
  variant={HTML_EDITOR_VARIANTS.INAPP}
@@ -1320,19 +1474,16 @@ export const InApp = (props) => {
1320
1474
  location={location}
1321
1475
  selectedOfferDetails={selectedOfferDetails}
1322
1476
  onTagSelect={() => {
1323
- // Tag insertion is handled by HTMLEditor's CodeEditorPane at cursor position
1324
- // Content updates will be propagated via onContentChange callback
1477
+ // Tag insertion handled by HTMLEditor's CodeEditorPane at cursor position
1325
1478
  }}
1326
- // Don't pass globalActions to prevent duplicate API calls
1327
- // HTMLEditor will use onContextChange instead
1328
1479
  onContextChange={handleOnTagsContextChange}
1329
- // Pass validation errors to HTMLEditor for display
1330
1480
  errors={errorMessage}
1331
1481
  isFullMode={isFullMode}
1332
1482
  data-test="inapp-html-editor"
1333
1483
  style={{ width: '138%' }}
1334
1484
  />
1335
- ) : (
1485
+ )}
1486
+ {!shouldUseHTMLEditor && isNewEditorFlowEnabled && (
1336
1487
  <InappAdvanced
1337
1488
  getFormData={getFormData}
1338
1489
  setIsLoadingContent={setIsLoadingContent}
@@ -1357,11 +1508,29 @@ export const InApp = (props) => {
1357
1508
  onCreateComplete={onCreateComplete}
1358
1509
  />
1359
1510
  )}
1511
+ {!shouldUseHTMLEditor && !isNewEditorFlowEnabled && (
1512
+ <>
1513
+ <CapInput
1514
+ label={<FormattedMessage {...messages.creativeName} />}
1515
+ onChange={({ target: { value } }) => setTempName(value)}
1516
+ value={tempName}
1517
+ labelPosition="top"
1518
+ size="default"
1519
+ />
1520
+ <CapTab
1521
+ panes={DEVICE_PANES.filter((devicePane) => devicePane?.isSupported === true)}
1522
+ onChange={(value) => setPanes(value)}
1523
+ activeKey={panes}
1524
+ defaultActiveKey={panes}
1525
+ className="inapp-template-device-tab"
1526
+ />
1527
+ </>
1528
+ )}
1360
1529
  </CapColumn>
1361
1530
  </CapRow>
1362
- {/* Footer with Done/Update button - Only show for HTML editor, bee editor has its own footer */}
1531
+ {/* Footer with Done/Update button - show for HTML editor and old CapDeviceContent flow */}
1363
1532
  {
1364
- shouldUseHTMLEditor && (
1533
+ (shouldUseHTMLEditor || !isNewEditorFlowEnabled) && (
1365
1534
  <>
1366
1535
  {hasAnyErrors(errorMessage) && (
1367
1536
  <ErrorInfoNote
@@ -894,5 +894,5 @@ export const deviceContentProps = {
894
894
  },
895
895
  ],
896
896
  tags: [],
897
+ isOtherDeviceSupported: true,
897
898
  };
898
-
@@ -821,6 +821,13 @@ export const InappAdvanced = (props) => {
821
821
  const isLiquidFlow = hasLiquidSupportFeature();
822
822
  // Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
823
823
  const hasTags = tags && tags.length > 0;
824
+
825
+ // When liquid is enabled and isFullMode is true, skip liquid validation and proceed directly
826
+ if (isLiquidFlow && isFullMode) {
827
+ onSuccess();
828
+ return;
829
+ }
830
+
824
831
  if (isLiquidFlow && hasTags) {
825
832
  validateInAppContent(payload, {
826
833
  currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
@@ -2652,6 +2652,12 @@ const MobilePushNew = ({
2652
2652
  };
2653
2653
  const onSuccess = () => handleSave();
2654
2654
 
2655
+ // When liquid is enabled and isFullMode is true, skip liquid validation and proceed directly
2656
+ if (hasLiquidSupportFeature() && isFullMode) {
2657
+ onSuccess();
2658
+ return;
2659
+ }
2660
+
2655
2661
  validateMobilePushContent([androidContent, iosContent], {
2656
2662
  currentTab: activeTab === ANDROID ? 1 : 2,
2657
2663
  onError,
@@ -2683,6 +2689,7 @@ const MobilePushNew = ({
2683
2689
  formatMessage,
2684
2690
  metaEntities,
2685
2691
  accountData,
2692
+ isFullMode,
2686
2693
  ]);
2687
2694
 
2688
2695
  const isLiquidFlow = hasLiquidSupportFeature();
@@ -300,6 +300,13 @@ export const SmsTraiEdit = (props) => {
300
300
  const onSuccess = () => {
301
301
  onDoneCallback();
302
302
  };
303
+
304
+ // When liquid is enabled and isFullMode is true, skip liquid validation and proceed directly
305
+ if (isFullMode) {
306
+ onSuccess();
307
+ return;
308
+ }
309
+
303
310
  validateLiquidTemplateContent(content, {
304
311
  getLiquidTags,
305
312
  formatMessage: intl.formatMessage,