@capillarytech/creatives-library 8.0.281 → 8.0.282-alpha.0

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.281",
4
+ "version": "8.0.282-alpha.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -49,10 +49,16 @@ const mockGetValidationState = jest.fn(() => ({
49
49
  issueCounts: { errors: 0, warnings: 0, total: 0 },
50
50
  }));
51
51
 
52
+ // Ref to capture apiValidationErrors passed to HTMLEditor (for mergedApiValidationErrors tests)
53
+ const capturedApiValidationErrorsRef = { current: null };
54
+
52
55
  // Mock HtmlEditor - it exports a lazy-loaded component by default
53
56
  jest.mock('../../../../v2Components/HtmlEditor/index.lazy', () => {
54
57
  const React = require('react');
55
58
  const MockHTMLEditor = React.forwardRef((props, ref) => {
59
+ if (global.__captureApiValidationErrorsRef && props.apiValidationErrors) {
60
+ global.__captureApiValidationErrorsRef.current = props.apiValidationErrors;
61
+ }
56
62
  React.useImperativeHandle(ref, () => ({
57
63
  getAllIssues: () => mockGetAllIssues(),
58
64
  getValidationState: () => mockGetValidationState(),
@@ -109,6 +115,9 @@ jest.mock('../../../../v2Components/HtmlEditor/index.lazy', () => {
109
115
  jest.mock('../../../../v2Components/HtmlEditor', () => {
110
116
  const React = require('react');
111
117
  const MockHTMLEditor = React.forwardRef((props, ref) => {
118
+ if (global.__captureApiValidationErrorsRef && props.apiValidationErrors) {
119
+ global.__captureApiValidationErrorsRef.current = props.apiValidationErrors;
120
+ }
112
121
  React.useImperativeHandle(ref, () => ({
113
122
  getAllIssues: () => mockGetAllIssues(),
114
123
  getValidationState: () => mockGetValidationState(),
@@ -421,6 +430,86 @@ describe('EmailHTMLEditor', () => {
421
430
  });
422
431
  // Reset hasLiquidSupportFeature mock to return true by default
423
432
  mockHasLiquidSupportFeature.mockReturnValue(true);
433
+ capturedApiValidationErrorsRef.current = null;
434
+ });
435
+
436
+ describe('mergedApiValidationErrors (lines 124-125)', () => {
437
+ beforeEach(() => {
438
+ global.__captureApiValidationErrorsRef = capturedApiValidationErrorsRef;
439
+ });
440
+
441
+ afterEach(() => {
442
+ delete global.__captureApiValidationErrorsRef;
443
+ });
444
+
445
+ it('merges tag unsupported errors into standardErrors when tagValidationError has unsupportedTags', async () => {
446
+ validateTags.mockReturnValue({
447
+ valid: false,
448
+ unsupportedTags: ['tagA', 'tagB'],
449
+ });
450
+ renderWithIntl({
451
+ metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
452
+ tags: [{ name: 'customer.name' }],
453
+ supportedTags: [],
454
+ });
455
+ const changeButton = screen.getByTestId('trigger-content-change');
456
+ await act(async () => {
457
+ fireEvent.click(changeButton);
458
+ });
459
+ await waitFor(() => {
460
+ expect(capturedApiValidationErrorsRef.current).not.toBeNull();
461
+ expect(capturedApiValidationErrorsRef.current.liquidErrors).toEqual([]);
462
+ expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
463
+ 'Unsupported tags are: tagA, tagB',
464
+ );
465
+ });
466
+ });
467
+
468
+ it('merges tag missing errors into standardErrors when tagValidationError has missingTags and unsubscribe not mandatory', async () => {
469
+ isEmailUnsubscribeTagMandatory.mockReturnValue(false);
470
+ validateTags.mockReturnValue({
471
+ valid: false,
472
+ missingTags: ['unsubscribe'],
473
+ });
474
+ renderWithIntl({
475
+ metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
476
+ tags: [{ name: 'customer.name' }],
477
+ supportedTags: [],
478
+ });
479
+ const changeButton = screen.getByTestId('trigger-content-change');
480
+ await act(async () => {
481
+ fireEvent.click(changeButton);
482
+ });
483
+ await waitFor(() => {
484
+ expect(capturedApiValidationErrorsRef.current).not.toBeNull();
485
+ expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
486
+ 'Missing tags are: unsubscribe',
487
+ );
488
+ });
489
+ });
490
+
491
+ it('uses apiValidationErrors.liquidErrors and concatenates apiValidationErrors.standardErrors with tag messages (merge shape)', async () => {
492
+ // When tag messages exist, mergedApiValidationErrors returns liquidErrors from apiValidationErrors
493
+ // and standardErrors = [...(apiValidationErrors?.standardErrors || []), ...tagMessages] (lines 124-125)
494
+ validateTags.mockReturnValue({
495
+ valid: false,
496
+ unsupportedTags: ['customTag'],
497
+ });
498
+ renderWithIntl({
499
+ metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
500
+ tags: [{ name: 'customer.name' }],
501
+ });
502
+ const changeButton = screen.getByTestId('trigger-content-change');
503
+ await act(async () => {
504
+ fireEvent.click(changeButton);
505
+ });
506
+ await waitFor(() => {
507
+ expect(capturedApiValidationErrorsRef.current).not.toBeNull();
508
+ const { liquidErrors, standardErrors } = capturedApiValidationErrorsRef.current;
509
+ expect(liquidErrors).toEqual([]);
510
+ expect(standardErrors).toContain('Unsupported tags are: customTag');
511
+ });
512
+ });
424
513
  });
425
514
 
426
515
  describe('Default Parameter Values (lines 60-63)', () => {
@@ -768,6 +768,130 @@ describe('useEmailWrapper', () => {
768
768
  });
769
769
  });
770
770
 
771
+ describe('templateId resolution (lines 209-213)', () => {
772
+ it('should call getTemplateDetails with templateId from params.id', async () => {
773
+ const templateId = 'from-params-id';
774
+ const editProps = {
775
+ ...newFlowMockProps,
776
+ params: { id: templateId },
777
+ location: { pathname: '/email/edit/other', query: {} },
778
+ Email: {
779
+ ...newFlowMockProps.Email,
780
+ templateDetails: null,
781
+ getTemplateDetailsInProgress: false,
782
+ },
783
+ };
784
+
785
+ renderHook((props) => useEmailWrapper(props), {
786
+ initialProps: editProps,
787
+ });
788
+
789
+ await waitFor(() => {
790
+ expect(mockEmailActions.getTemplateDetails).toHaveBeenCalledWith(templateId, 'email');
791
+ });
792
+ });
793
+
794
+ it('should call getTemplateDetails with templateId from location.query.id when params.id is missing', async () => {
795
+ const templateId = 'from-query-id';
796
+ const editProps = {
797
+ ...newFlowMockProps,
798
+ params: {},
799
+ location: {
800
+ pathname: '/email/edit/something',
801
+ query: { id: templateId },
802
+ },
803
+ Email: {
804
+ ...newFlowMockProps.Email,
805
+ templateDetails: null,
806
+ getTemplateDetailsInProgress: false,
807
+ },
808
+ };
809
+
810
+ renderHook((props) => useEmailWrapper(props), {
811
+ initialProps: editProps,
812
+ });
813
+
814
+ await waitFor(() => {
815
+ expect(mockEmailActions.getTemplateDetails).toHaveBeenCalledWith(templateId, 'email');
816
+ });
817
+ });
818
+
819
+ it('should call getTemplateDetails with templateId from location.params.id when params and query id are missing', async () => {
820
+ const templateId = 'from-location-params-id';
821
+ const editProps = {
822
+ ...newFlowMockProps,
823
+ params: {},
824
+ location: {
825
+ pathname: '/email/edit/fallback',
826
+ query: {},
827
+ params: { id: templateId },
828
+ },
829
+ Email: {
830
+ ...newFlowMockProps.Email,
831
+ templateDetails: null,
832
+ getTemplateDetailsInProgress: false,
833
+ },
834
+ };
835
+
836
+ renderHook((props) => useEmailWrapper(props), {
837
+ initialProps: editProps,
838
+ });
839
+
840
+ await waitFor(() => {
841
+ expect(mockEmailActions.getTemplateDetails).toHaveBeenCalledWith(templateId, 'email');
842
+ });
843
+ });
844
+
845
+ it('should call getTemplateDetails with templateId extracted from pathname /edit/ID when no params or query id', async () => {
846
+ const templateId = 'extracted-from-pathname';
847
+ const editProps = {
848
+ ...newFlowMockProps,
849
+ params: {},
850
+ location: {
851
+ pathname: `/email/edit/${templateId}`,
852
+ query: {},
853
+ },
854
+ Email: {
855
+ ...newFlowMockProps.Email,
856
+ templateDetails: null,
857
+ getTemplateDetailsInProgress: false,
858
+ },
859
+ };
860
+
861
+ renderHook((props) => useEmailWrapper(props), {
862
+ initialProps: editProps,
863
+ });
864
+
865
+ await waitFor(() => {
866
+ expect(mockEmailActions.getTemplateDetails).toHaveBeenCalledWith(templateId, 'email');
867
+ });
868
+ });
869
+
870
+ it('should not call getTemplateDetails when pathname includes /edit/ but has no id segment', async () => {
871
+ const editProps = {
872
+ ...newFlowMockProps,
873
+ params: {},
874
+ location: {
875
+ pathname: '/email/edit/',
876
+ query: {},
877
+ },
878
+ Email: {
879
+ ...newFlowMockProps.Email,
880
+ templateDetails: null,
881
+ getTemplateDetailsInProgress: false,
882
+ },
883
+ };
884
+
885
+ renderHook((props) => useEmailWrapper(props), {
886
+ initialProps: editProps,
887
+ });
888
+
889
+ await waitFor(() => {
890
+ expect(mockEmailActions.getTemplateDetails).not.toHaveBeenCalled();
891
+ }, { timeout: 500 });
892
+ });
893
+ });
894
+
771
895
  describe('Edit Flow - BEE Editor Template', () => {
772
896
  it('should call getTemplateDetails and set BEE editor for BEE template', async () => {
773
897
  const templateId = 'bee-template-123';
@@ -177,9 +177,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
177
177
  }
178
178
  if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema)) {
179
179
  this.setState({fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {}}, () => {
180
- this.handleEditSchemaOnPropsChange(nextProps, selectedWeChatAccount);
180
+ // Use this.props (latest) in callback to avoid race: templateDetails may have arrived by now
181
+ const latestSelectedAccount = this.getSelectedWeChatAccountFromProps(this.props);
182
+ this.handleEditSchemaOnPropsChange(this.props, latestSelectedAccount);
181
183
  const templateId = get(this, "props.params.id");
182
- if (nextProps.location.query.module !== 'loyalty' && templateId && templateId !== 'temp') {
184
+ if (this.props.location.query.module !== 'loyalty' && templateId && templateId !== 'temp') {
183
185
  this.props.actions.getTemplateDetails(templateId);
184
186
  }
185
187
  if (queryType === EMBEDDED && templateId === 'temp' && _.isEmpty(this.state.formData)) { // when his.props.params.id is temp that means mobile push template content will be passed from post message from parent with startTemplateCreation action
@@ -293,7 +295,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
293
295
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
294
296
  const id = field.id;
295
297
  const fieldIndex = findIndex(inputFields, {identifier: id});
296
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
298
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
297
299
  const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
298
300
  // let inputId = deeplinkValue && deeplinkValue.toLowerCase() === "deeplink" ? `${id}-select` : `${id}-text`;
299
301
  // if (field.id === "cta-deeplink-secondary-cta-1" && (tabIndex > 1 || this.state.currentTab > 1)) {
@@ -665,8 +667,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
665
667
 
666
668
  getLinkName = (link) => {
667
669
  const selectedWeChatAccount = this.getWeChatAccount();
668
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
669
- const deepLinkOptions = _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (l) => l.link === link);
670
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
671
+ const deepLinkOptions = _.filter(JSON.parse(ck ? selectedWeChatAccount?.configs?.deeplink ?? '[]' : '[]'), (l) => l.link === link);
670
672
  if (deepLinkOptions[0]) {
671
673
  return deepLinkOptions[0].name;
672
674
  }
@@ -727,6 +729,27 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
727
729
  parent.postMessage(JSON.stringify(response), '*');
728
730
  };
729
731
 
732
+ /**
733
+ * Compute selectedWeChatAccount from props (used so we can call with latest props
734
+ * in setState callback to avoid stale closure and intermittent empty form).
735
+ */
736
+ getSelectedWeChatAccountFromProps = (props) => {
737
+ const queryType = String(get(props, 'location.query.type', ''))?.toLowerCase();
738
+ const creativesMode = String(get(props, 'creativesMode', ''))?.toLowerCase();
739
+ const { Edit: EditProps, Templates } = props || {};
740
+ const { selectedWeChatAccount: editSelectedWeChatAccount } = EditProps || {};
741
+ const { Templates: nextTemplates } = props || {};
742
+ if (isEmbeddedEditOrPreview(queryType, creativesMode)) {
743
+ return !_.isEmpty(editSelectedWeChatAccount)
744
+ ? editSelectedWeChatAccount
745
+ : nextTemplates?.selectedWeChatAccount;
746
+ }
747
+ if (!_.isEmpty(Templates?.selectedWeChatAccount)) {
748
+ return Templates?.selectedWeChatAccount;
749
+ }
750
+ return undefined;
751
+ };
752
+
730
753
  getFormData = (e) => {
731
754
  const response = {
732
755
  action: "getFormData",
@@ -749,6 +772,9 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
749
772
  formData["mobilepush-accounts"] = this.state.formData["mobilepush-accounts"];
750
773
  formData['mobilepush-template'] = this.state.formData['mobilepush-template'];
751
774
  }
775
+ if (data.definition?.accountId) {
776
+ formData['mobilepush-accounts'] = data.definition.accountId;
777
+ }
752
778
  formData['template-name'] = data.name;
753
779
  const androidData = data.versions.base.ANDROID;
754
780
  const iosData = data.versions.base.IOS;
@@ -1088,7 +1114,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1088
1114
  const currentTab = tab || this.state.currentTab;
1089
1115
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1090
1116
  const selectedWeChatAccount = this.getWeChatAccount();
1091
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1117
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1092
1118
  const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
1093
1119
 
1094
1120
  const inputFields = get(schema, `containers[0].panes[${currentTab - 1}].sections[0].childSections[0].childSections[0].inputFields`);
@@ -1240,7 +1266,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1240
1266
  delete formData[self.state.currentTab - 1][`${id.replace('-delete', '')}`];
1241
1267
  }
1242
1268
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1243
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1269
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1244
1270
  const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1245
1271
  if (configkeys.length) {
1246
1272
  const options = configkeys[0].keys;
@@ -1352,7 +1378,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1352
1378
  delete formData[self.state.currentTab - 1][`cta-deeplink-${id.replace('delete', 'text')}`];
1353
1379
  }
1354
1380
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1355
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1381
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1356
1382
  const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1357
1383
 
1358
1384
  if (configkeys.length) {
@@ -1706,7 +1732,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1706
1732
  const {id} = field;
1707
1733
  const fieldIndex = findIndex(inputFields, {identifier: id});
1708
1734
  const formDataKey = id.replace("-show-keys", "");
1709
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1735
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1710
1736
  let keys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === formData[currentTab - 1][formDataKey]) : [];
1711
1737
  if (keys[0]) {
1712
1738
  keys = keys[0].keys;
@@ -1756,7 +1782,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1756
1782
  const selectedWeChatAccount = this.getWeChatAccount();
1757
1783
  const id = field.id;
1758
1784
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1759
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1785
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1760
1786
  const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1761
1787
  // const eventsMap = _.cloneDeep(this.state.eventsMap);
1762
1788
  const tabIndex = currentTab || this.state.currentTab;
@@ -1787,7 +1813,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1787
1813
  const formDataCopy = cloneDeep(formData);
1788
1814
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1789
1815
  const selectedWeChatAccount = this.getWeChatAccount();
1790
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1816
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1791
1817
  const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1792
1818
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
1793
1819
  const fieldIndex = findIndex(inputFields, {identifier: id});
@@ -2004,8 +2030,23 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2004
2030
  }
2005
2031
 
2006
2032
  handleEditSchemaOnPropsChange = (nextProps, selectedWeChatAccount) => {
2007
- if (!_.isEmpty(nextProps.templateDetails) && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.fullSchema) && selectedWeChatAccount && (this.props.location.query.type !== 'embedded' || this.props.isFullMode === false)) {
2033
+ const queryType = String(get(this.props, 'location.query.type', ''))?.toLowerCase();
2034
+ const isEmbeddedLibrary = queryType === EMBEDDED && !nextProps.isFullMode;
2035
+ const canSetAccountFromTemplate =
2036
+ !selectedWeChatAccount &&
2037
+ nextProps.templateDetails?.definition?.accountId &&
2038
+ nextProps.Edit?.weCrmAccounts?.length > 0;
2039
+ const canPopulateForm =
2040
+ !_.isEmpty(nextProps.templateDetails) &&
2041
+ _.isEmpty(this.state.editData) &&
2042
+ !_.isEmpty(this.state.fullSchema) &&
2043
+ (this.props.location.query.type !== 'embedded' || this.props.isFullMode === false) &&
2044
+ (selectedWeChatAccount || isEmbeddedLibrary || canSetAccountFromTemplate);
2045
+ if (canPopulateForm) {
2008
2046
  this.props = nextProps;
2047
+ if (canSetAccountFromTemplate) {
2048
+ this.setMobilePushAccountOptions(nextProps.Edit.weCrmAccounts, nextProps.templateDetails.definition.accountId);
2049
+ }
2009
2050
  const mode = nextProps.templateDetails.definition ? nextProps.templateDetails.definition.mode : nextProps.templateDetails.mode;
2010
2051
  const schema = mode === "text" ? this.state.fullSchema?.textSchema : this.state.fullSchema?.imageSchema;
2011
2052
  const isAndroidSupported = get(this, "props.Templates.selectedWeChatAccount.configs.android") === '1';
@@ -2040,7 +2081,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2040
2081
  <CapSpin spinning={spinning}>
2041
2082
  <CapRow>
2042
2083
  <CapColumn>
2043
- <FormBuilder
2084
+ {!this.props.isLoadingMetaEntities && <FormBuilder
2044
2085
  channel={MOBILE_PUSH}
2045
2086
  schema={schema}
2046
2087
  showLiquidErrorInFooter={this.props.showLiquidErrorInFooter}
@@ -2075,7 +2116,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2075
2116
  hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
2076
2117
  isFullMode={this.props.isFullMode}
2077
2118
  eventContextTags={this.props?.eventContextTags}
2078
- />
2119
+ />}
2079
2120
  </CapColumn>
2080
2121
  {this.props.iosCtasData && this.state.showIosCtaTable &&
2081
2122
  <CapSlideBox