@capillarytech/creatives-library 8.0.316-alpha.3 → 8.0.317

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.316-alpha.3",
4
+ "version": "8.0.317",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -762,6 +762,7 @@ export class Creatives extends React.Component {
762
762
  smsFallBackContent = {},
763
763
  creativeName = "",
764
764
  channel = constants.RCS,
765
+ accountId = "",
765
766
  } = templateData || {};
766
767
  const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
767
768
  const Status = RCS_STATUSES.approved || '';
@@ -775,6 +776,7 @@ export class Creatives extends React.Component {
775
776
  content: {
776
777
  RCS: {
777
778
  rcsContent: {
779
+ ...(accountId && !isFullMode && { accountId }),
778
780
  ...rcsContent,
779
781
  cardContent: [
780
782
  {
@@ -1279,6 +1281,7 @@ export class Creatives extends React.Component {
1279
1281
  contentType = "",
1280
1282
  cardType = "",
1281
1283
  cardSettings = {},
1284
+ accountId = "",
1282
1285
  } = get(versions, 'base.content.RCS.rcsContent', {});
1283
1286
  const rcsContent = {
1284
1287
  contentType,
@@ -1290,6 +1293,7 @@ export class Creatives extends React.Component {
1290
1293
  channel,
1291
1294
  creativeName: name,
1292
1295
  rcsContent,
1296
+ accountId: accountId,
1293
1297
  };
1294
1298
  }
1295
1299
  }
@@ -300,6 +300,7 @@ export const Rcs = (props) => {
300
300
  const [accessToken, setAccessToken] = useState('');
301
301
  const [hostName, setHostName] = useState('');
302
302
  const [accountName, setAccountName] = useState('');
303
+ const [rcsAccount, setRcsAccount] = useState('');
303
304
  useEffect(() => {
304
305
  const accountObj = accountData.selectedRcsAccount || {};
305
306
  if (!isEmpty(accountObj)) {
@@ -312,6 +313,7 @@ export const Rcs = (props) => {
312
313
  setAccessToken(configs.accessToken || '');
313
314
  setHostName(accountObj.hostName || '');
314
315
  setAccountName(accountObj.name || '');
316
+ setRcsAccount(accountObj.id || '');
315
317
  }
316
318
  }, [accountData.selectedRcsAccount]);
317
319
 
@@ -606,6 +608,8 @@ export const Rcs = (props) => {
606
608
  const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
607
609
  const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
608
610
  setMediaData(mediaData, mediaType, cardSettings);
611
+ const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
612
+ setRcsAccount(rcsAccountId);
609
613
  }
610
614
  }, [rcsData, templateData, isFullMode, isEditFlow]);
611
615
 
@@ -1961,6 +1965,7 @@ const splitTemplateVarString = (str) => {
1961
1965
  content: {
1962
1966
  RCS: {
1963
1967
  rcsContent: {
1968
+ ...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
1964
1969
  cardType: STANDALONE,
1965
1970
  cardSettings: {
1966
1971
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
@@ -12,6 +12,52 @@ import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
12
12
  import { MESSAGE_MAX_LENGTH, SHOW_CHARACTER_COUNT } from '../../constants';
13
13
  import { useAiraTriggerPosition } from '../hooks/useAiraTriggerPosition';
14
14
 
15
+ const InputWrapper = ({
16
+ children,
17
+ inputRef,
18
+ error,
19
+ value,
20
+ onChange,
21
+ isAiContentBotDisabled,
22
+ }) => {
23
+ const wrapperRef = useRef(null);
24
+ const airaRootStyle = useAiraTriggerPosition({ wrapperRef, inputRef, error });
25
+
26
+ return (
27
+ <div className="webpush-message-input-wrapper" ref={wrapperRef}>
28
+ {children}
29
+ {!isAiContentBotDisabled && (
30
+ <CapAskAira.ContentGenerationBot
31
+ text={value || ''}
32
+ setText={(text) => onChange(text)}
33
+ iconPlacement="float-br"
34
+ iconSize="1.6rem"
35
+ rootStyle={airaRootStyle}
36
+ />
37
+ )}
38
+ </div>
39
+ );
40
+ };
41
+
42
+ InputWrapper.propTypes = {
43
+ children: PropTypes.node.isRequired,
44
+ inputRef: PropTypes.oneOfType([
45
+ PropTypes.shape({ current: PropTypes.any }),
46
+ PropTypes.func,
47
+ ]),
48
+ error: PropTypes.string,
49
+ value: PropTypes.string,
50
+ onChange: PropTypes.func.isRequired,
51
+ isAiContentBotDisabled: PropTypes.bool,
52
+ };
53
+
54
+ InputWrapper.defaultProps = {
55
+ inputRef: null,
56
+ error: '',
57
+ value: '',
58
+ isAiContentBotDisabled: false,
59
+ };
60
+
15
61
  /**
16
62
  * MessageSection component - Message textarea with tags, emoji picker, and character count
17
63
  */
@@ -27,13 +73,6 @@ export const MessageSection = ({
27
73
  handleMessageTextAreaRef,
28
74
  isAiContentBotDisabled,
29
75
  }) => {
30
- const messageInputWrapperRef = useRef(null);
31
- const airaRootStyle = useAiraTriggerPosition({
32
- wrapperRef: messageInputWrapperRef,
33
- inputRef: messageTextAreaRef,
34
- error,
35
- });
36
-
37
76
  const renderCharacterCount = () => {
38
77
  if (!SHOW_CHARACTER_COUNT) return null;
39
78
 
@@ -60,7 +99,13 @@ export const MessageSection = ({
60
99
  <CapHeading type="h4" className="webpush-message">
61
100
  <FormattedMessage {...messages.message} />
62
101
  </CapHeading>
63
- <div className="webpush-message-input-wrapper" ref={messageInputWrapperRef}>
102
+ <InputWrapper
103
+ inputRef={messageTextAreaRef}
104
+ error={error}
105
+ value={value}
106
+ onChange={onChange}
107
+ isAiContentBotDisabled={isAiContentBotDisabled}
108
+ >
64
109
  <CapEmojiPicker.Wrapper
65
110
  value={value}
66
111
  onChange={onChange}
@@ -84,16 +129,7 @@ export const MessageSection = ({
84
129
  }
85
130
  />
86
131
  </CapEmojiPicker.Wrapper>
87
- {!isAiContentBotDisabled && (
88
- <CapAskAira.ContentGenerationBot
89
- text={value || ''}
90
- setText={(text) => onChange(text)}
91
- iconPlacement="float-br"
92
- iconSize="1.6rem"
93
- rootStyle={airaRootStyle}
94
- />
95
- )}
96
- </div>
132
+ </InputWrapper>
97
133
  {renderCharacterCount()}
98
134
  </CapRow>
99
135
  <CapDivider className="webpush-message-divider" />
@@ -318,5 +318,33 @@ describe('MessageSection', () => {
318
318
  expect(textArea.prop('value')).toBe(longValue);
319
319
  });
320
320
  });
321
+
322
+ describe('InputWrapper', () => {
323
+ it('should render the wrapper div with correct class', () => {
324
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} />);
325
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
326
+ expect(inputWrapper.exists()).toBe(true);
327
+ });
328
+
329
+ it('should render children inside InputWrapper', () => {
330
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} />);
331
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
332
+ expect(inputWrapper.find(CapEmojiPicker.Wrapper).exists()).toBe(true);
333
+ });
334
+
335
+ it('should not render aiRA bot inside InputWrapper when disabled', () => {
336
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} isAiContentBotDisabled />);
337
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
338
+ expect(inputWrapper.find(CapAskAira.ContentGenerationBot).exists()).toBe(false);
339
+ });
340
+
341
+ it('should render aiRA bot inside InputWrapper when enabled', () => {
342
+ const wrapper = mountWithIntl(
343
+ <MessageSection {...defaultProps} isAiContentBotDisabled={false} />
344
+ );
345
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
346
+ expect(inputWrapper.find(CapAskAira.ContentGenerationBot).exists()).toBe(true);
347
+ });
348
+ });
321
349
  });
322
350
 
@@ -18,8 +18,12 @@ exports[`MessageSection Rendering should render correctly with default props 1`]
18
18
  values={Object {}}
19
19
  />
20
20
  </CapHeading>
21
- <div
22
- className="webpush-message-input-wrapper"
21
+ <InputWrapper
22
+ error=""
23
+ inputRef={null}
24
+ isAiContentBotDisabled={true}
25
+ onChange={[MockFunction]}
26
+ value="Test Message"
23
27
  >
24
28
  <InjectIntl(Wrapper)
25
29
  onChange={[MockFunction]}
@@ -43,7 +47,7 @@ exports[`MessageSection Rendering should render correctly with default props 1`]
43
47
  value="Test Message"
44
48
  />
45
49
  </InjectIntl(Wrapper)>
46
- </div>
50
+ </InputWrapper>
47
51
  <CapLabel
48
52
  className="webpush-character-count"
49
53
  type="label2"
@@ -2957,12 +2957,13 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2957
2957
  };
2958
2958
 
2959
2959
  const isEditDoneDisabled = () => {
2960
+ const isBlankInput = (inputValue) => !String(inputValue ?? '').trim();
2960
2961
  let carouselDisableCheck = false;
2961
2962
  if (isMediaTypeCarousel) {
2962
2963
  carouselDisableCheck = carouselData.some((data) => {
2963
2964
  return (
2964
2965
  data.carouselTagValidationErr ||
2965
- Object.values(data.varMap).some((inputValue) => inputValue === "") ||
2966
+ Object.values(data.varMap).some((inputValue) => isBlankInput(inputValue)) ||
2966
2967
  computeTextLength(CAROUSEL_TEXT, data) > TEMPLATE_MESSAGE_MAX_LENGTH ||
2967
2968
  (carouselMediaType === IMAGE.toLowerCase() && !data.imageSrc) ||
2968
2969
  (carouselMediaType === VIDEO.toLowerCase() && !data.videoSrc) ||
@@ -2972,8 +2973,8 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2972
2973
  }
2973
2974
  return (isTagValidationError ||
2974
2975
  isHeaderTagValidationError ||
2975
- Object.values(varMap).some((inputValue) => inputValue === "") ||
2976
- Object.values(headerVarMappedData).some((inputValue) => inputValue === "") ||
2976
+ Object.values(varMap).some((inputValue) => isBlankInput(inputValue)) ||
2977
+ Object.values(headerVarMappedData).some((inputValue) => isBlankInput(inputValue)) ||
2977
2978
  computeTextLength(MESSAGE_TEXT) > TEMPLATE_MESSAGE_MAX_LENGTH ||
2978
2979
  computeTextLength(HEADER_TEXT) > TEMPLATE_HEADER_MAX_LENGTH ||
2979
2980
  (isBtnTypeCta && ctaData.some((btn) => btn?.url?.includes("{{1}}"))) || isMediatypeValid()) || carouselDisableCheck;
@@ -1354,3 +1354,175 @@ describe('Haptic carousel file handle mapping', () => {
1354
1354
  expect(cd[0].karixFileHandle).toBe('');
1355
1355
  });
1356
1356
  });
1357
+
1358
+ describe('WhatsApp edit Done button whitespace validation', () => {
1359
+ let renderedComponent;
1360
+
1361
+ const createWhatsappTemplate = jest.fn();
1362
+ const getFormData = jest.fn();
1363
+ const clearCreateResponse = jest.fn();
1364
+ const getTemplateDetails = jest.fn();
1365
+ const resetEditTemplate = jest.fn();
1366
+ const fetchSchemaForEntity = jest.fn();
1367
+ const handleClose = jest.fn();
1368
+ const onCreateComplete = jest.fn();
1369
+ const formatMessage = jest.fn();
1370
+ const getMetaTags = jest.fn();
1371
+ const resetMetaTags = jest.fn();
1372
+
1373
+ const mountEditFlow = (editData, accountData = mockData.accountData1) => {
1374
+ renderedComponent = mountWithIntl(
1375
+ <Provider store={store}>
1376
+ <Whatsapp
1377
+ actions={{
1378
+ createWhatsappTemplate,
1379
+ clearCreateResponse,
1380
+ getTemplateDetails,
1381
+ resetEditTemplate,
1382
+ getMetaTags,
1383
+ resetMetaTags,
1384
+ }}
1385
+ globalActions={{ fetchSchemaForEntity }}
1386
+ onCreateComplete={onCreateComplete}
1387
+ handleClose={handleClose}
1388
+ getFormData={getFormData}
1389
+ params={{ id: editData?.templateDetails?._id || 'edit-whitespace-id' }}
1390
+ editData={editData}
1391
+ accountData={accountData}
1392
+ location={mockData.location}
1393
+ intl={{ formatMessage }}
1394
+ isFullMode={false}
1395
+ isEditFlow
1396
+ loadingTags={false}
1397
+ metaEntities={[]}
1398
+ getDefaultTags
1399
+ Templates={{
1400
+ senderDetails: {
1401
+ status: 'SUCCESS',
1402
+ domainProperties: [
1403
+ {
1404
+ domainProperties: {
1405
+ connectionProperties: {
1406
+ sourceAccountIdentifier: '2000222347',
1407
+ baseUrl: 'https://api.gupshup.io/whatsapp/v1',
1408
+ },
1409
+ },
1410
+ },
1411
+ ],
1412
+ },
1413
+ }}
1414
+ />
1415
+ </Provider>,
1416
+ );
1417
+ };
1418
+
1419
+ const getDoneButton = () => renderedComponent.find('CapButton.whatsapp-create-btn').at(0);
1420
+
1421
+ const getFirstVariableTextarea = () => renderedComponent
1422
+ .find('textarea.TextArea__StyledTextArea-sc-177dfyt-2')
1423
+ .at(0);
1424
+
1425
+ it('keeps Done disabled for whitespace-only message variable', () => {
1426
+ const editData = {
1427
+ templateDetails: {
1428
+ _id: 'edit-msg-whitespace',
1429
+ name: 'msg_whitespace',
1430
+ type: 'WHATSAPP',
1431
+ versions: {
1432
+ base: {
1433
+ content: {
1434
+ whatsapp: {
1435
+ status: 'approved',
1436
+ category: 'ALERT_UPDATE',
1437
+ mediaType: 'TEXT',
1438
+ buttonType: 'NONE',
1439
+ varMapped: {},
1440
+ languages: [{ language: 'en', content: 'Your code is {{1}}' }],
1441
+ },
1442
+ },
1443
+ },
1444
+ },
1445
+ },
1446
+ };
1447
+
1448
+ mountEditFlow(editData);
1449
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_3' } });
1450
+ renderedComponent.update();
1451
+
1452
+ expect(getDoneButton().props().disabled).toBe(true);
1453
+ });
1454
+
1455
+ it('keeps Done disabled for whitespace-only header variable', () => {
1456
+ const editData = {
1457
+ templateDetails: {
1458
+ _id: 'edit-header-whitespace',
1459
+ name: 'header_whitespace',
1460
+ type: 'WHATSAPP',
1461
+ versions: {
1462
+ base: {
1463
+ content: {
1464
+ whatsapp: {
1465
+ status: 'approved',
1466
+ category: 'ALERT_UPDATE',
1467
+ mediaType: 'TEXT',
1468
+ buttonType: 'NONE',
1469
+ varMapped: {},
1470
+ whatsappMedia: {
1471
+ header: 'Hi {{1}}',
1472
+ footer: '',
1473
+ headerVarMapped: {},
1474
+ },
1475
+ languages: [{ language: 'en', content: 'Simple message without vars' }],
1476
+ },
1477
+ },
1478
+ },
1479
+ },
1480
+ },
1481
+ };
1482
+
1483
+ mountEditFlow(editData);
1484
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_1' } });
1485
+ renderedComponent.update();
1486
+
1487
+ expect(getDoneButton().props().disabled).toBe(true);
1488
+ });
1489
+
1490
+ it('keeps Done disabled for whitespace-only carousel variable', () => {
1491
+ const editData = {
1492
+ templateDetails: {
1493
+ _id: 'edit-carousel-whitespace',
1494
+ name: 'carousel_whitespace',
1495
+ type: 'WHATSAPP',
1496
+ versions: {
1497
+ base: {
1498
+ content: {
1499
+ whatsapp: {
1500
+ status: 'approved',
1501
+ category: 'MARKETING',
1502
+ mediaType: 'CAROUSEL',
1503
+ buttonType: 'NONE',
1504
+ varMapped: {},
1505
+ languages: [{ language: 'en', content: 'Main message' }],
1506
+ carouselData: [
1507
+ {
1508
+ mediaType: 'image',
1509
+ imageUrl: 'https://cdn.example.com/card.jpg',
1510
+ bodyText: 'Card body {{1}}',
1511
+ varMap: {},
1512
+ buttons: [],
1513
+ },
1514
+ ],
1515
+ },
1516
+ },
1517
+ },
1518
+ },
1519
+ },
1520
+ };
1521
+
1522
+ mountEditFlow(editData, mockData.accountData2);
1523
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_2' } });
1524
+ renderedComponent.update();
1525
+
1526
+ expect(getDoneButton().props().disabled).toBe(true);
1527
+ });
1528
+ });