@capillarytech/creatives-library 8.0.282 → 8.0.284

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.282",
4
+ "version": "8.0.284",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -55,19 +55,26 @@ export const CapWhatsappCarouselButton = (props) => {
55
55
  const invalidVarRegex = /{{(.*?)}}/g;
56
56
 
57
57
  const handleButtonType = ({ target: { value } }, buttonIndex) => {
58
- const cloneCarouselData = cloneDeep(carouselData);
59
58
  const dataMap = {
60
59
  [PHONE_NUMBER]: INITIAL_CAROUSEL_PHONE_NUMBER_DATA,
61
60
  [URL]: INITIAL_CAROUSEL_URL_DATA,
62
61
  [QUICK_REPLY]: INITIAL_CAROUSEL_QUICK_REPLY_DATA,
63
62
  };
63
+ const initialData = dataMap[value];
64
+ const newButtonData = initialData ? cloneDeep(initialData) : {};
64
65
 
65
- const updatedCarouselData = cloneCarouselData.map((carousel) => {
66
- carousel.buttons[buttonIndex] = dataMap[value] || {};
67
- return carousel;
68
- });
69
-
70
- setCarouselData(updatedCarouselData);
66
+ setCarouselData(
67
+ carouselData.map((item, index) =>
68
+ index === carouselIndex
69
+ ? {
70
+ ...item,
71
+ buttons: item.buttons.map((btn, i) =>
72
+ i === buttonIndex ? newButtonData : btn
73
+ ),
74
+ }
75
+ : item
76
+ )
77
+ );
71
78
  };
72
79
 
73
80
  const onValueChange = (buttonIndex, fields = []) => {
@@ -148,17 +155,28 @@ export const CapWhatsappCarouselButton = (props) => {
148
155
  };
149
156
 
150
157
  const handleDeleteButton = (buttonIndex) => {
151
- setCarouselData((prevData) => prevData.map((carousel) => ({
152
- ...carousel,
153
- buttons: carousel.buttons.filter((_, index) => index !== buttonIndex),
154
- })));
158
+ setCarouselData((prevData) =>
159
+ prevData.map((carousel, index) =>
160
+ index === carouselIndex
161
+ ? {
162
+ ...carousel,
163
+ buttons: carousel.buttons.filter((_, i) => i !== buttonIndex),
164
+ }
165
+ : carousel
166
+ )
167
+ );
155
168
  };
156
169
 
157
170
  const addCarouselButton = () => {
158
171
  setCarouselData(
159
- carouselData.map((item, index) => index === carouselIndex
160
- ? { ...item, buttons: [...buttonData, INITIAL_CAROUSEL_PHONE_NUMBER_DATA] }
161
- : item)
172
+ carouselData.map((item, index) =>
173
+ index === carouselIndex
174
+ ? {
175
+ ...item,
176
+ buttons: [...buttonData, cloneDeep(INITIAL_CAROUSEL_PHONE_NUMBER_DATA)],
177
+ }
178
+ : item
179
+ )
162
180
  );
163
181
  };
164
182
 
@@ -3,9 +3,10 @@ import { injectIntl } from 'react-intl';
3
3
  import '@testing-library/jest-dom';
4
4
  import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { CapWhatsappCarouselButton } from '../index';
6
- import {
6
+ import {
7
7
  INITIAL_CAROUSEL_PHONE_NUMBER_DATA,
8
- INITIAL_CAROUSEL_URL_DATA
8
+ INITIAL_CAROUSEL_URL_DATA,
9
+ URL,
9
10
  } from '../constant';
10
11
  import { HOST_TWILIO } from "../../../v2Containers/Whatsapp/constants";
11
12
 
@@ -234,4 +235,121 @@ describe('CapWhatsappCarouselButton', () => {
234
235
  })
235
236
  ]));
236
237
  });
238
+
239
+ describe('carousel-scoped updates (only current card is updated)', () => {
240
+ it('handleButtonType only updates the carousel at carouselIndex', () => {
241
+ const card0 = {
242
+ bodyText: '',
243
+ buttons: [{ ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA, text: 'Card 0' }],
244
+ };
245
+ const card1 = {
246
+ bodyText: '',
247
+ buttons: [{ ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA, text: 'Card 1' }],
248
+ };
249
+ const carouselData = [card0, card1];
250
+
251
+ initializeComponent(carouselData, false, HOST_TWILIO, 0);
252
+
253
+ const radios = screen.getAllByRole('radio');
254
+ const urlRadio = radios.find((r) => r.value === URL) || radios[1];
255
+ if (urlRadio) fireEvent.click(urlRadio);
256
+
257
+ expect(setCarouselData).toHaveBeenCalledWith(expect.any(Array));
258
+ const updated = setCarouselData.mock.calls[0][0];
259
+ expect(updated).toHaveLength(2);
260
+ expect(updated[0].buttons[0]).toMatchObject(INITIAL_CAROUSEL_URL_DATA);
261
+ expect(updated[1]).toEqual(card1);
262
+ });
263
+
264
+ it('handleDeleteButton only removes button from the carousel at carouselIndex', () => {
265
+ const card0 = {
266
+ bodyText: '',
267
+ buttons: [
268
+ { ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA, text: 'A', isSaved: true },
269
+ { ...INITIAL_CAROUSEL_URL_DATA, text: 'B', isSaved: true },
270
+ ],
271
+ };
272
+ const card1 = {
273
+ bodyText: '',
274
+ buttons: [
275
+ { ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA, text: 'C', isSaved: true },
276
+ { ...INITIAL_CAROUSEL_URL_DATA, text: 'D', isSaved: true },
277
+ ],
278
+ };
279
+ const carouselData = [card0, card1];
280
+
281
+ const { container } = initializeComponent(carouselData, false, HOST_TWILIO, 0);
282
+
283
+ const deleteIconBtns = container.querySelectorAll('.whatsapp-carousel-delete-icon-btn');
284
+ expect(deleteIconBtns.length).toBeGreaterThanOrEqual(2);
285
+ fireEvent.click(deleteIconBtns[1]);
286
+
287
+ expect(setCarouselData).toHaveBeenCalledTimes(1);
288
+ const setter = setCarouselData.mock.calls[0][0];
289
+ expect(typeof setter).toBe('function');
290
+ const result = setter(carouselData);
291
+ expect(result[0].buttons).toHaveLength(1);
292
+ expect(result[0].buttons[0].text).toBe('A');
293
+ expect(result[1].buttons).toHaveLength(2);
294
+ expect(result[1].buttons[0].text).toBe('C');
295
+ expect(result[1].buttons[1].text).toBe('D');
296
+ });
297
+
298
+ it('addCarouselButton only adds button to the carousel at carouselIndex', () => {
299
+ const card0 = {
300
+ bodyText: '',
301
+ buttons: [{
302
+ ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA,
303
+ text: 'Call us',
304
+ phone_number: '9112345678',
305
+ isSaved: true,
306
+ }],
307
+ };
308
+ const card1 = {
309
+ bodyText: '',
310
+ buttons: [{ ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA, text: 'Other' }],
311
+ };
312
+ const carouselData = [card0, card1];
313
+
314
+ initializeComponent(carouselData, false, HOST_TWILIO, 0);
315
+
316
+ const addButton = screen.getByRole('button', { name: /add button/i });
317
+ fireEvent.click(addButton);
318
+
319
+ expect(setCarouselData).toHaveBeenCalledWith(expect.any(Array));
320
+ const updated = setCarouselData.mock.calls[0][0];
321
+ expect(updated).toHaveLength(2);
322
+ expect(updated[0].buttons).toHaveLength(2);
323
+ expect(updated[0].buttons[0].text).toBe('Call us');
324
+ expect(updated[0].buttons[1]).toMatchObject(INITIAL_CAROUSEL_PHONE_NUMBER_DATA);
325
+ expect(updated[0].buttons[1]).not.toBe(INITIAL_CAROUSEL_PHONE_NUMBER_DATA);
326
+ expect(updated[1]).toEqual(card1);
327
+ });
328
+
329
+ it('addCarouselButton adds a new button object (clone) so state is not shared', () => {
330
+ const carouselData = [{
331
+ bodyText: '',
332
+ buttons: [{
333
+ ...INITIAL_CAROUSEL_PHONE_NUMBER_DATA,
334
+ text: 'Saved',
335
+ isSaved: true,
336
+ }],
337
+ }];
338
+
339
+ initializeComponent(carouselData, false, HOST_TWILIO, 0);
340
+
341
+ const addButton = screen.getByRole('button', { name: /add button/i });
342
+ fireEvent.click(addButton);
343
+
344
+ const updated = setCarouselData.mock.calls[0][0];
345
+ const addedButton = updated[0].buttons[1];
346
+ expect(addedButton).toMatchObject({
347
+ buttonType: 'PHONE_NUMBER',
348
+ text: '',
349
+ phone_number: '',
350
+ isSaved: false,
351
+ });
352
+ expect(addedButton).not.toBe(INITIAL_CAROUSEL_PHONE_NUMBER_DATA);
353
+ });
354
+ });
237
355
  });
@@ -47,6 +47,17 @@ import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox
47
47
  const PrefixWrapper = styled.div`
48
48
  margin-right: 16px;
49
49
  `;
50
+
51
+ const safeParseDeeplinkConfig = (value) => {
52
+ if (!value || typeof value !== 'string') return [];
53
+ try {
54
+ const parsed = JSON.parse(value);
55
+ return Array.isArray(parsed) ? parsed : [];
56
+ } catch {
57
+ return [];
58
+ }
59
+ };
60
+
50
61
  export class Edit extends React.Component { // eslint-disable-line react/prefer-stateless-function
51
62
  constructor(props) {
52
63
  super(props);
@@ -96,11 +107,12 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
96
107
  if (!_.isEmpty(this.state.schema)) {
97
108
  this.injectEvents(this.state.schema);
98
109
  }
99
- if (this.props.location.query && (typeof this.props.location.query.module === 'undefined' || this.props.isFullMode === false)) { // when this containers is imported from creatives library
100
- const templateId = get(this, "props.params.id");
101
- if (templateId) {
110
+ const templateId = get(this, "props.params.id");
111
+ const hasTemplateData = !_.isEmpty(this.props.templateData);
112
+ if (this.props.location?.query && (templateId || hasTemplateData)) {
113
+ if (templateId && templateId !== 'temp') {
102
114
  this.props.actions.getTemplateDetails(templateId);
103
- } else {
115
+ } else if (hasTemplateData) {
104
116
  this.props.actions.setTemplateDetails(this.props.templateData);
105
117
  }
106
118
  }
@@ -176,7 +188,9 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
176
188
  this.props.actions.getMobilepushTemplatesList('mobilepush', params);
177
189
  }
178
190
  if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema)) {
179
- this.setState({fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {}}, () => {
191
+ const definition = nextProps.metaEntities.layouts[0].definition;
192
+ const initialSchema = definition?.textSchema || definition?.imageSchema || {};
193
+ this.setState({fullSchema: definition, schema: initialSchema}, () => {
180
194
  // Use this.props (latest) in callback to avoid race: templateDetails may have arrived by now
181
195
  const latestSelectedAccount = this.getSelectedWeChatAccountFromProps(this.props);
182
196
  this.handleEditSchemaOnPropsChange(this.props, latestSelectedAccount);
@@ -240,7 +254,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
240
254
  newFormData[this.state.currentTab - 1][`secondary-cta-${this.state.currentTab - 1}-action2`] = get(templateCta, 'ctaTemplateDetails[1].buttonText');
241
255
  }
242
256
  }
243
- if (newFormData["mobilepush-accounts"] !== this.state.formData["mobilepush-accounts"]) {
257
+ const newAccount = newFormData["mobilepush-accounts"];
258
+ const currentAccount = this.state.formData["mobilepush-accounts"];
259
+ const isExplicitAccountChange = newAccount !== undefined && newAccount !== currentAccount;
260
+ if (isExplicitAccountChange) {
244
261
  this.setMobilePushAccountOptions(this.props.Edit.weCrmAccounts, newFormData["mobilepush-accounts"]);
245
262
  delete newFormData['mobilepush-template'];
246
263
  delete newFormData['template-name'];
@@ -250,6 +267,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
250
267
  if (!_.isEmpty(newFormData[1])) {
251
268
  delete newFormData[1];
252
269
  }
270
+ } else if (newAccount === undefined && currentAccount !== undefined) {
271
+ newFormData["mobilepush-accounts"] = currentAccount;
253
272
  }
254
273
  if (this.state.isSchemaChanged) {
255
274
  this.setState({isSchemaChanged: false});
@@ -281,7 +300,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
281
300
  if (isEmbeddedEditOrPreview(queryType, mode)) {
282
301
  selectedWeChatAccount = !_.isEmpty(editSelectedWeChatAccount)
283
302
  ? editSelectedWeChatAccount
284
- : templateSelectedWeChatAccount;
303
+ : (templateSelectedWeChatAccount || {});
285
304
  } else if (!_.isEmpty(templateSelectedWeChatAccount)) {
286
305
  selectedWeChatAccount = templateSelectedWeChatAccount;
287
306
  }
@@ -295,8 +314,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
295
314
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
296
315
  const id = field.id;
297
316
  const fieldIndex = findIndex(inputFields, {identifier: id});
298
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
299
- const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
317
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
318
+ const deepLinkOptions = _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
300
319
  // let inputId = deeplinkValue && deeplinkValue.toLowerCase() === "deeplink" ? `${id}-select` : `${id}-text`;
301
320
  // if (field.id === "cta-deeplink-secondary-cta-1" && (tabIndex > 1 || this.state.currentTab > 1)) {
302
321
  // inputId = `${inputId}${tabIndex || this.state.currentTab}`;
@@ -667,8 +686,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
667
686
 
668
687
  getLinkName = (link) => {
669
688
  const selectedWeChatAccount = this.getWeChatAccount();
670
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
671
- const deepLinkOptions = _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (l) => l.link === link);
689
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
690
+ const deepLinkOptions = _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (l) => l.link === link);
672
691
  if (deepLinkOptions[0]) {
673
692
  return deepLinkOptions[0].name;
674
693
  }
@@ -766,6 +785,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
766
785
 
767
786
 
768
787
  setEditState(data, schema) {
788
+ if (!data?.versions?.base || typeof data.versions.base !== 'object') return;
769
789
  const tabCount = Object.keys(data.versions.base).length;
770
790
  const formData = {};
771
791
  if (this.props.location.query.type === 'embedded' && this.props.location.query.module === "loyalty") {
@@ -1114,8 +1134,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1114
1134
  const currentTab = tab || this.state.currentTab;
1115
1135
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1116
1136
  const selectedWeChatAccount = this.getWeChatAccount();
1117
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1118
- const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
1137
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1138
+ const deepLinkOptions = _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
1119
1139
 
1120
1140
  const inputFields = get(schema, `containers[0].panes[${currentTab - 1}].sections[0].childSections[0].childSections[0].inputFields`);
1121
1141
  forEach(inputFields, (inputField, fieldIndex) => {
@@ -1266,8 +1286,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1266
1286
  delete formData[self.state.currentTab - 1][`${id.replace('-delete', '')}`];
1267
1287
  }
1268
1288
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1269
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1270
- const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1289
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1290
+ const configkeys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1271
1291
  if (configkeys.length) {
1272
1292
  const options = configkeys[0].keys;
1273
1293
  _.forEach(options, (opt) => {
@@ -1378,8 +1398,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1378
1398
  delete formData[self.state.currentTab - 1][`cta-deeplink-${id.replace('delete', 'text')}`];
1379
1399
  }
1380
1400
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1381
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1382
- const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1401
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1402
+ const configkeys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1383
1403
 
1384
1404
  if (configkeys.length) {
1385
1405
  const options = configkeys[0].keys;
@@ -1732,8 +1752,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1732
1752
  const {id} = field;
1733
1753
  const fieldIndex = findIndex(inputFields, {identifier: id});
1734
1754
  const formDataKey = id.replace("-show-keys", "");
1735
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1736
- let keys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === formData[currentTab - 1][formDataKey]) : [];
1755
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1756
+ let keys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === formData[currentTab - 1][formDataKey]) : [];
1737
1757
  if (keys[0]) {
1738
1758
  keys = keys[0].keys;
1739
1759
  }
@@ -1763,7 +1783,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1763
1783
  inputFields.splice(fieldIndex + index + 1, 0, row);
1764
1784
  });
1765
1785
  if (!tabIndex) { // removes the existing selected options keys so that newly selected options keys can be added.
1766
- const configkeys = _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === this.state.formData[currentTab - 1][field.id]);
1786
+ const configkeys = _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === this.state.formData[currentTab - 1][field.id]);
1767
1787
  if (configkeys.length) {
1768
1788
  const options = configkeys[0].keys;
1769
1789
  inputFields = inputFields.slice(0, fieldIndex + 1).concat(inputFields.slice(fieldIndex + options.length + 1, inputFields.length));
@@ -1782,8 +1802,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1782
1802
  const selectedWeChatAccount = this.getWeChatAccount();
1783
1803
  const id = field.id;
1784
1804
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1785
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1786
- const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1805
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1806
+ const deepLinkOptions = selectedWeChatAccount ? _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount?.configs?.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1787
1807
  // const eventsMap = _.cloneDeep(this.state.eventsMap);
1788
1808
  const tabIndex = currentTab || this.state.currentTab;
1789
1809
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
@@ -1813,8 +1833,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1813
1833
  const formDataCopy = cloneDeep(formData);
1814
1834
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1815
1835
  const selectedWeChatAccount = this.getWeChatAccount();
1816
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1817
- const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1836
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1837
+ const deepLinkOptions = selectedWeChatAccount ? _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1818
1838
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
1819
1839
  const fieldIndex = findIndex(inputFields, {identifier: id});
1820
1840
  let newInputFields;
@@ -2030,48 +2050,94 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2030
2050
  }
2031
2051
 
2032
2052
  handleEditSchemaOnPropsChange = (nextProps, selectedWeChatAccount) => {
2053
+ const templateDetails = nextProps.templateDetails || nextProps.templateData;
2033
2054
  const queryType = String(get(this.props, 'location.query.type', ''))?.toLowerCase();
2034
2055
  const isEmbeddedLibrary = queryType === EMBEDDED && !nextProps.isFullMode;
2035
2056
  const canSetAccountFromTemplate =
2036
- !selectedWeChatAccount &&
2037
- nextProps.templateDetails?.definition?.accountId &&
2057
+ (_.isEmpty(selectedWeChatAccount) || !selectedWeChatAccount?.configs) &&
2058
+ templateDetails?.definition?.accountId &&
2038
2059
  nextProps.Edit?.weCrmAccounts?.length > 0;
2060
+ const hasAccountOrEmbedded =
2061
+ !_.isEmpty(selectedWeChatAccount) ||
2062
+ isEmbeddedLibrary ||
2063
+ canSetAccountFromTemplate ||
2064
+ !nextProps.isFullMode ||
2065
+ queryType === EMBEDDED;
2066
+ const hasTemplateDetails = !_.isEmpty(templateDetails);
2067
+ const hasEditData = !_.isEmpty(this.state.editData);
2068
+ const hasFullSchema = !_.isEmpty(this.state.fullSchema);
2069
+ const locationCheck = (this.props.location.query.type !== 'embedded' || this.props.isFullMode === false || queryType === EMBEDDED);
2039
2070
  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);
2071
+ hasTemplateDetails &&
2072
+ !hasEditData &&
2073
+ hasFullSchema &&
2074
+ locationCheck &&
2075
+ hasAccountOrEmbedded;
2045
2076
  if (canPopulateForm) {
2046
2077
  this.props = nextProps;
2047
2078
  if (canSetAccountFromTemplate) {
2048
- this.setMobilePushAccountOptions(nextProps.Edit.weCrmAccounts, nextProps.templateDetails.definition.accountId);
2079
+ this.setMobilePushAccountOptions(nextProps.Edit.weCrmAccounts, templateDetails.definition.accountId);
2049
2080
  }
2050
- const mode = nextProps.templateDetails.definition ? nextProps.templateDetails.definition.mode : nextProps.templateDetails.mode;
2081
+ const mode = templateDetails.definition ? templateDetails.definition.mode : templateDetails.mode;
2051
2082
  const schema = mode === "text" ? this.state.fullSchema?.textSchema : this.state.fullSchema?.imageSchema;
2052
- const isAndroidSupported = get(this, "props.Templates.selectedWeChatAccount.configs.android") === '1';
2053
- const isIosSupported = get(this, "props.Templates.selectedWeChatAccount.configs.ios") === '1';
2083
+ if (!schema) return;
2084
+ const configs = nextProps.Templates?.selectedWeChatAccount?.configs || {};
2085
+ const hasExplicitConfigs = !_.isEmpty(configs) && ('android' in configs || 'ios' in configs);
2086
+ const isAndroidSupported = hasExplicitConfigs ? configs.android === '1' : true;
2087
+ const isIosSupported = hasExplicitConfigs ? configs.ios === '1' : true;
2054
2088
  if (!isAndroidSupported) {
2055
2089
  const androidField = get(schema, "containers[0].panes[0]");
2056
- androidField.isSupported = false;
2057
- this.setState({currentTab: 2});
2058
- set(schema, "containers[0].panes[0]", androidField);
2090
+ if (androidField) {
2091
+ androidField.isSupported = false;
2092
+ this.setState({currentTab: 2});
2093
+ set(schema, "containers[0].panes[0]", androidField);
2094
+ }
2059
2095
  }
2060
2096
  if (!isIosSupported) {
2061
2097
  const iosField = get(schema, "containers[0].panes[1]");
2062
- iosField.isSupported = false;
2063
-
2064
- set(schema, "containers[0].panes[1]", iosField);
2098
+ if (iosField) {
2099
+ iosField.isSupported = false;
2100
+ set(schema, "containers[0].panes[1]", iosField);
2101
+ }
2065
2102
  }
2066
- this.setEditState(nextProps.templateDetails, schema);
2067
- this.getTags();
2103
+ const dataToUse = this.normalizeTemplateDataForEdit(templateDetails);
2104
+ if (dataToUse?.versions?.base) {
2105
+ this.setEditState(dataToUse, schema);
2106
+ this.getTags();
2107
+ } else {
2108
+ console.warn('[MobliPushEdit] Skipped setEditState - dataToUse.versions.base missing', { dataToUse });
2109
+ }
2110
+ }
2111
+ };
2112
+
2113
+ normalizeTemplateDataForEdit = (data) => {
2114
+ if (!data) return null;
2115
+ if (data.versions?.base) return data;
2116
+ if (data.content?.versions?.base) {
2117
+ return { ...data, versions: data.content.versions };
2068
2118
  }
2119
+ const baseContent = data.content || {};
2120
+ if (baseContent.ANDROID || baseContent.IOS) {
2121
+ return {
2122
+ ...data,
2123
+ versions: { base: baseContent },
2124
+ name: data.name || data.templateName || '',
2125
+ definition: data.definition || {},
2126
+ mode: data.mode || data.definition?.mode || 'text',
2127
+ };
2128
+ }
2129
+ return data;
2069
2130
  }
2070
2131
 
2071
2132
  render() {
2133
+ const queryType = String(get(this.props, 'location.query.type', ''))?.toLowerCase();
2134
+ const isEmbeddedOrLibrary = queryType === 'embedded' || this.props.isFullMode === false;
2135
+ const hasFormContent = !_.isEmpty(this.state.formData) && !_.isEmpty(this.state.schema);
2136
+ const showFormBuilder = !this.props.isLoadingMetaEntities || hasFormContent;
2072
2137
  const schema = this.state.schema;
2073
2138
  const loadingContentEdit = this.state.loadingContentEdit || (this.props.location.query.module === 'dvs' && _.isEmpty(this.state.schema));
2074
- const spinning = get(this.props, "Edit.fetchingWeCrmAccounts") || this.props.isLoadingMetaEntities || loadingContentEdit || this.props.Edit.fetchingDefaultTemplates || this.props.Edit.assetUploading || this.state.loading || (this.props.Edit && (("getTemplateDetailsInProgress" in this.props.Edit && this.props.Edit.getTemplateDetailsInProgress) || ("editTemplateInProgress" in this.props.Edit && this.props.Edit.editTemplateInProgress)));
2139
+ const metaEntitiesBlockSpinner = hasFormContent;
2140
+ const spinning = get(this.props, "Edit.fetchingWeCrmAccounts") || (!metaEntitiesBlockSpinner && this.props.isLoadingMetaEntities) || loadingContentEdit || this.props.Edit.fetchingDefaultTemplates || this.props.Edit.assetUploading || this.state.loading || (this.props.Edit && (("getTemplateDetailsInProgress" in this.props.Edit && this.props.Edit.getTemplateDetailsInProgress) || ("editTemplateInProgress" in this.props.Edit && this.props.Edit.editTemplateInProgress)));
2075
2141
  let tags = this.props.metaEntities && this.props.metaEntities.tags ? this.props.metaEntities.tags.standard : [];
2076
2142
  if (this.props.supportedTags) {
2077
2143
  tags = this.props.supportedTags;
@@ -2081,7 +2147,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2081
2147
  <CapSpin spinning={spinning}>
2082
2148
  <CapRow>
2083
2149
  <CapColumn>
2084
- {!this.props.isLoadingMetaEntities && <FormBuilder
2150
+ {(() => {
2151
+ if (isEmbeddedOrLibrary && !showFormBuilder && hasFormContent) {
2152
+ console.warn('[MobliPushEdit] FormBuilder HIDDEN (isLoadingMetaEntities=true) but formData+schema exist');
2153
+ }
2154
+ return showFormBuilder && <FormBuilder
2085
2155
  channel={MOBILE_PUSH}
2086
2156
  schema={schema}
2087
2157
  showLiquidErrorInFooter={this.props.showLiquidErrorInFooter}
@@ -2116,7 +2186,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2116
2186
  hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
2117
2187
  isFullMode={this.props.isFullMode}
2118
2188
  eventContextTags={this.props?.eventContextTags}
2119
- />}
2189
+ />;
2190
+ })()}
2120
2191
  </CapColumn>
2121
2192
  {this.props.iosCtasData && this.state.showIosCtaTable &&
2122
2193
  <CapSlideBox
@@ -2055,11 +2055,11 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2055
2055
  const buttonArray = firstCarouselButtonData.map((button) => {
2056
2056
  switch (button?.buttonType) {
2057
2057
  case PHONE_NUMBER:
2058
- return INITIAL_CAROUSEL_PHONE_NUMBER_DATA;
2058
+ return cloneDeep(INITIAL_CAROUSEL_PHONE_NUMBER_DATA);
2059
2059
  case QUICK_REPLY:
2060
- return INITIAL_CAROUSEL_QUICK_REPLY_DATA;
2060
+ return cloneDeep(INITIAL_CAROUSEL_QUICK_REPLY_DATA);
2061
2061
  default:
2062
- return INITIAL_CAROUSEL_URL_DATA;
2062
+ return cloneDeep(INITIAL_CAROUSEL_URL_DATA);
2063
2063
  }
2064
2064
  });
2065
2065
  const newCard = cloneDeep(CAROUSEL_INITIAL_DATA[0]);