@capillarytech/creatives-library 8.0.204 → 8.0.205

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.204",
4
+ "version": "8.0.205",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -1,4 +1,8 @@
1
1
  export const OUTBOUND = 'OUTBOUND';
2
+ export const ADD_LANGUAGE = 'addLanguage';
3
+ export const UPLOAD = 'upload';
4
+ export const USE_EDITOR = 'useEditor';
5
+ export const COPY_PRIMARY_LANGUAGE = 'copyPrimaryLanguage';
2
6
  export const GLOBAL_CONVERT_OPTIONS = {
3
7
  selectors: [
4
8
  ...[1, 2, 3, 4, 5, 6].map(level => ({
@@ -54,7 +54,7 @@ import { containsBase64Images } from '../../utils/content';
54
54
  import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
55
55
  import globalMessages from '../../v2Containers/Cap/messages';
56
56
  import { convert } from 'html-to-text';
57
- import { OUTBOUND } from './constants';
57
+ import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE } from './constants';
58
58
  import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
59
59
  import moment from 'moment';
60
60
  import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX} from '../../constants/unified';
@@ -139,8 +139,196 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
139
139
  // Check if the liquid flow feature is supported and the channel is in the supported list.
140
140
  this.liquidFlow = this.isLiquidFlowSupported.bind(this);
141
141
  this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
142
+
143
+ // Performance optimization: Debounced functions for high-frequency updates
144
+ this.debouncedUpdateFormData = _.debounce((data, val, event, skipStateUpdate) => {
145
+ this.performFormDataUpdate(data, val, event, skipStateUpdate);
146
+ }, 300);
147
+ this.debouncedValidation = _.debounce(this.validateForm.bind(this), 500);
148
+
149
+ // Memoized validation cache
150
+ this.validationCache = new Map();
151
+
152
+ }
153
+
154
+ // Helper function to generate unique tab ID
155
+ generateUniqueTabId(formData, tabIndex) {
156
+ let id = _.uniqueId();
157
+ let validId = false;
158
+
159
+ while (!validId) {
160
+ validId = true;
161
+ for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
162
+ if (!formData[tabIndex]) {
163
+ continue;
164
+ }
165
+ if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
166
+ validId = false;
167
+ break;
168
+ }
169
+ }
170
+ if (!validId) {
171
+ id = _.uniqueId();
172
+ }
173
+ }
174
+
175
+ return id;
176
+ }
177
+
178
+ // Performance optimized form data update function
179
+ performFormDataUpdate(data, val, event, skipStateUpdate = false) {
180
+
181
+ // Use optimized state update instead of deep cloning
182
+ const formData = this.optimizedFormDataUpdate(data, val, event);
183
+
184
+ const tabIndex = this.state.currentTab - 1;
185
+ let currentTab = this.state.currentTab;
186
+
187
+ if (this.state.usingTabContainer && !val.standalone) {
188
+ const data1 = data;
189
+ if (event === ADD_LANGUAGE) {
190
+ const addLanguageType = this.props.addLanguageType;
191
+ if (addLanguageType === '') {
192
+ return;
193
+ }
194
+ const currentLang = formData[tabIndex].activeTab;
195
+ let baseTab;
196
+
197
+ switch (addLanguageType) {
198
+ case UPLOAD:
199
+ baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
200
+
201
+ baseTab.iso_code = data.iso_code;
202
+ baseTab.lang_id = data.lang_id;
203
+ baseTab.language = data.language;
204
+ baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
205
+
206
+ formData[tabIndex][data.iso_code] = baseTab;
207
+ formData[tabIndex].activeTab = data.iso_code;
208
+ formData[tabIndex].tabKey = baseTab.tabKey;
209
+ break;
210
+ case COPY_PRIMARY_LANGUAGE:
211
+ case USE_EDITOR:
212
+ baseTab = _.cloneDeep(formData[tabIndex][this.props.baseLanguage]);
213
+
214
+ baseTab.iso_code = data.iso_code;
215
+ baseTab.lang_id = data.lang_id;
216
+ baseTab.language = data.language;
217
+ baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
218
+
219
+ formData[tabIndex].selectedLanguages.push(data.iso_code);
220
+ formData[tabIndex][data.iso_code] = baseTab;
221
+ formData[tabIndex].activeTab = data.iso_code;
222
+ formData[tabIndex].tabKey = baseTab.tabKey;
223
+ break;
224
+ case '':
225
+ return;
226
+ default:
227
+ break;
228
+ }
229
+ const that = this;
230
+ setTimeout(() => {
231
+ that.setState({tabKey: baseTab.tabKey});
232
+ }, 0);
233
+ }
234
+
235
+ if (!this.props.isNewVersionFlow) {
236
+ formData[tabIndex][val.id] = data1;
237
+ } else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
238
+ formData[tabIndex][this.props.baseLanguage][val.id] = data1;
239
+ }
240
+
241
+ if (formData[tabIndex].base) {
242
+ if (!this.props.isNewVersionFlow) {
243
+ formData.base[val.id] = data1;
244
+ } else {
245
+ formData.base[data1.iso_code] = formData[tabIndex][data1.iso_code];
246
+ formData.base.tabKey = formData[tabIndex].tabKey;
247
+ formData.base.activeTab = formData[tabIndex].activeTab;
248
+ formData.base.selectedLanguages = formData[tabIndex].selectedLanguages;
249
+ }
250
+ }
251
+ } else {
252
+ formData[val.id] = data;
253
+ }
254
+
255
+ if (this.props.isNewVersionFlow) {
256
+ if (event === 'onSelect' && data === 'New Version') {
257
+ this.callChildEvent(data, val, 'addVersion', event);
258
+ } else if (event === 'onSelect' && data !== 'New Version') {
259
+ currentTab = _.findIndex(this.state.formData['template-version-options'], { key: data}) + 1;
260
+ this.setState({currentTab, tabKey: formData[`${currentTab - 1}`].tabKey}, () => {
261
+ val.injectedEvents[event].call(this, this.state.formData['template-version-options'][currentTab - 1].key, formData, val);
262
+ });
263
+ }
264
+
265
+ if (event === 'onContentChange') {
266
+ // Content change handling
267
+ }
268
+ }
269
+
270
+ // Only update state if not already updated (for immediate UI feedback)
271
+ if (!skipStateUpdate) {
272
+ this.setState({formData}, () => {
273
+ if (this.props.startValidation) {
274
+ this.debouncedValidation();
275
+ }
276
+ });
277
+ } else {
278
+ // Just trigger validation if state was already updated
279
+ if (this.props.startValidation) {
280
+ this.debouncedValidation();
281
+ }
282
+ }
283
+
284
+ if (event && val.injectedEvents[event]) {
285
+ if (event === "onRowClick") {
286
+ val.injectedEvents[event].call(this, data);
287
+ } else if (this.props.isNewVersionFlow && event !== 'onSelect') {
288
+ if (event === ADD_LANGUAGE) {
289
+ val.injectedEvents[event].call(this, data, formData, val);
290
+ this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
291
+ } else {
292
+ val.injectedEvents[event].call(this, true, formData, val);
293
+ }
294
+ } else if (!this.props.isNewVersionFlow) {
295
+ val.injectedEvents[event].call(this, true, formData, val);
296
+ }
297
+ } else if (val.injectedEvents && val.injectedEvents.onChange) {
298
+ val.injectedEvents.onChange.call(this, true, formData, val);
299
+ }
142
300
 
301
+ if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
302
+ this.props.onChange(formData, this.state.tabCount, currentTab, val);
303
+ }
143
304
  }
305
+
306
+ // Optimized form data update - only clone what's necessary
307
+ optimizedFormDataUpdate(data, val, event) {
308
+ const currentFormData = this.state.formData;
309
+
310
+ // For simple field updates, use spread operator instead of deep clone
311
+ if (!this.state.usingTabContainer || val.standalone) {
312
+ return {
313
+ ...currentFormData,
314
+ [val.id]: data
315
+ };
316
+ }
317
+
318
+ // For tab container updates, only clone the affected tab
319
+ const tabIndex = this.state.currentTab - 1;
320
+ const updatedFormData = { ...currentFormData };
321
+
322
+ if (updatedFormData[tabIndex]) {
323
+ updatedFormData[tabIndex] = {
324
+ ...updatedFormData[tabIndex],
325
+ [val.id]: data
326
+ };
327
+ }
328
+
329
+ return updatedFormData;
330
+ }
331
+
144
332
  isLiquidFlowSupported = () => {
145
333
  return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
146
334
  }
@@ -269,7 +457,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
269
457
  }
270
458
  this.setState({formData: nextProps.formData, currentLangTab}, () => {
271
459
  if (nextProps?.isNewVersionFlow && !this.state?.formData[this.state?.currentTab - 1][this.state?.formData[this.state?.currentTab - 1]?.activeTab]?.tabKey) {
272
- this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
460
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
273
461
  }
274
462
  if (type === 'embedded' || ( this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL')) {
275
463
  // Don't run validation if we're in Test & Preview mode
@@ -401,7 +589,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
401
589
  const event = this.state.currentEvent;
402
590
  if (!_.isEmpty(this.state.currentEvent)) {
403
591
  switch (event) {
404
- case "addLanguage":
592
+ case ADD_LANGUAGE:
405
593
  this.updateFormData(this.state.currentEventData, this.state.currentEventVal, this.state.currentEvent);
406
594
  break;
407
595
  default:
@@ -916,7 +1104,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
916
1104
  }
917
1105
  }
918
1106
  if(_.isEmpty(androidData) && this.state.currentTab == 2){
919
- this.setState({androidValid, iosValid}, () => {
1107
+ this.setState({androidValid, iosValid, errorData}, () => {
920
1108
  this.props.setModalContent('ios');
921
1109
  });
922
1110
  // modal.body = "Android template is not configured. Save without Android template";
@@ -931,7 +1119,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
931
1119
  }
932
1120
  }
933
1121
  if(_.isEmpty(iosData) && this.state.currentTab == 1){
934
- this.setState({androidValid, iosValid}, () => {
1122
+ this.setState({androidValid, iosValid, errorData}, () => {
935
1123
  this.props.setModalContent('android');
936
1124
  });
937
1125
  // modal.body = "IOS template is not configured, Save without IOS template";
@@ -2027,6 +2215,62 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2027
2215
  }
2028
2216
 
2029
2217
  updateFormData(data, val, event) {
2218
+
2219
+ // Check if this is a high-frequency input field that should be optimized
2220
+ const isHighFrequencyField = val && (
2221
+ val.id === 'template-name' ||
2222
+ val.id === 'template-subject'
2223
+ );
2224
+
2225
+ if (isHighFrequencyField && !event) {
2226
+ // For high-frequency fields: immediate UI update + debounced expensive operations
2227
+ this.updateFormDataOptimized(data, val, event);
2228
+ return;
2229
+ }
2230
+
2231
+ // For non-high-frequency fields or special events, use immediate update
2232
+ this.performFormDataUpdate(data, val, event);
2233
+ }
2234
+
2235
+ // Optimized update for high-frequency fields
2236
+ updateFormDataOptimized(data, val, event) {
2237
+ // 1. Immediate UI update - update the field value instantly
2238
+ this.updateFieldValueImmediately(data, val);
2239
+
2240
+ // 2. Debounce expensive operations (validation, parent updates) - skip state update since we already did it
2241
+ this.debouncedUpdateFormData(data, val, event, true);
2242
+ }
2243
+
2244
+ // Update field value immediately for UI feedback
2245
+ updateFieldValueImmediately(data, val) {
2246
+ const currentFormData = this.state.formData;
2247
+ let updatedFormData;
2248
+
2249
+ if (!this.state.usingTabContainer || val.standalone) {
2250
+ // Simple field update
2251
+ updatedFormData = {
2252
+ ...currentFormData,
2253
+ [val.id]: data
2254
+ };
2255
+ } else {
2256
+ // Tab container update
2257
+ const tabIndex = this.state.currentTab - 1;
2258
+ updatedFormData = { ...currentFormData };
2259
+
2260
+ if (updatedFormData[tabIndex]) {
2261
+ updatedFormData[tabIndex] = {
2262
+ ...updatedFormData[tabIndex],
2263
+ [val.id]: data
2264
+ };
2265
+ }
2266
+ }
2267
+
2268
+ // Update state immediately for UI feedback
2269
+ this.setState({ formData: updatedFormData });
2270
+ }
2271
+
2272
+ // Legacy updateFormData function - kept for backward compatibility
2273
+ updateFormDataLegacy(data, val, event) {
2030
2274
  const formData = _.cloneDeep(this.state.formData);
2031
2275
 
2032
2276
  const tabIndex = this.state.currentTab - 1;
@@ -2034,7 +2278,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2034
2278
 
2035
2279
  if (this.state.usingTabContainer && !val.standalone) {
2036
2280
  const data1 = data;
2037
- if (event === "addLanguage") {
2281
+ if (event === ADD_LANGUAGE) {
2038
2282
  const addLanguageType = this.props.addLanguageType;
2039
2283
  if (addLanguageType === '') {
2040
2284
  return;
@@ -2117,7 +2361,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2117
2361
 
2118
2362
  if (!this.props.isNewVersionFlow) {
2119
2363
  formData[tabIndex][val.id] = data1;
2120
- } else if (this.props.isNewVersionFlow && event !== "addLanguage" && event !== "onContentChange") {
2364
+ } else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
2121
2365
  formData[tabIndex][this.props.baseLanguage][val.id] = data1;
2122
2366
  }
2123
2367
 
@@ -2164,7 +2408,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2164
2408
  if (event === "onRowClick") {
2165
2409
  val.injectedEvents[event].call(this, data);
2166
2410
  } else if (this.props.isNewVersionFlow && event !== 'onSelect') {
2167
- if (event === 'addLanguage') {
2411
+ if (event === ADD_LANGUAGE) {
2168
2412
  val.injectedEvents[event].call(this, data, formData, val);
2169
2413
  this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
2170
2414
  } else {
@@ -2186,6 +2430,57 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2186
2430
  hasClass(element, className) {
2187
2431
  return (` ${element.className} `).indexOf(` ${className} `) > -1;
2188
2432
  }
2433
+
2434
+ // Handle field blur for validation
2435
+ handleFieldBlur = (e, val) => {
2436
+ // Trigger validation on blur for high-frequency fields
2437
+ if (val && (val.id === 'template-name' || val.id === 'template-subject')) {
2438
+ this.debouncedValidation();
2439
+ }
2440
+ }
2441
+
2442
+ // Memoized validation for specific fields
2443
+ getMemoizedValidation = (fieldId, fieldValue) => {
2444
+ const cacheKey = `${fieldId}_${fieldValue}`;
2445
+
2446
+ if (this.validationCache.has(cacheKey)) {
2447
+ return this.validationCache.get(cacheKey);
2448
+ }
2449
+
2450
+ // Perform validation logic here
2451
+ const isValid = this.performFieldValidation(fieldId, fieldValue);
2452
+ this.validationCache.set(cacheKey, isValid);
2453
+
2454
+ // Clear cache if it gets too large
2455
+ if (this.validationCache.size > 100) {
2456
+ this.validationCache.clear();
2457
+ }
2458
+
2459
+ return isValid;
2460
+ }
2461
+
2462
+ // Perform validation for a specific field
2463
+ performFieldValidation = (fieldId, fieldValue) => {
2464
+ // Basic validation logic for template-name and template-subject
2465
+ if (fieldId === 'template-name' || fieldId === 'template-subject') {
2466
+ return fieldValue && fieldValue.trim().length > 0;
2467
+ }
2468
+ return true;
2469
+ }
2470
+
2471
+ // Cleanup debounced functions on component unmount
2472
+ componentWillUnmount() {
2473
+ if (this.debouncedUpdateFormData) {
2474
+ this.debouncedUpdateFormData.cancel();
2475
+ }
2476
+ if (this.debouncedValidation) {
2477
+ this.debouncedValidation.cancel();
2478
+ }
2479
+ // Clear validation cache
2480
+ if (this.validationCache) {
2481
+ this.validationCache.clear();
2482
+ }
2483
+ }
2189
2484
  allowAddSecondaryCta = (val) => {
2190
2485
  if (val.fieldsCount > 0) {
2191
2486
  const errorData = _.cloneDeep(this.state.errorData);
@@ -2601,6 +2896,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2601
2896
  label={val.label}
2602
2897
  autosize={val.autosize ? val.autosizeParams : false}
2603
2898
  onChange={(e) => this.updateFormData(e.target.value, val)}
2899
+ onBlur={(e) => this.handleFieldBlur(e, val)}
2604
2900
  style={val.style ? val.style : {}}
2605
2901
  defaultValue={messageContent || ''}
2606
2902
  value={messageContent || ""}
@@ -2682,6 +2978,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2682
2978
  style={val.style ? val.style : {}}
2683
2979
  placeholder={val.placeholder}
2684
2980
  onChange={(e) => this.updateFormData(e.target.value, val)}
2981
+ onBlur={(e) => this.handleFieldBlur(e, val)}
2685
2982
  defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
2686
2983
  value={value || ""}
2687
2984
  disabled={val.disabled}
@@ -3166,6 +3463,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3166
3463
  style={val.style ? val.style : {}}
3167
3464
  placeholder={val.placeholder}
3168
3465
  onChange={(e) => this.updateFormData(e.target.value, val)}
3466
+ onBlur={(e) => this.handleFieldBlur(e, val)}
3169
3467
  value={value || ""}
3170
3468
  defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3171
3469
  disabled={val.disabled}
@@ -3743,7 +4041,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3743
4041
  id={val.id}
3744
4042
  popOverList={this.props.supportedLanguages}
3745
4043
  excludeList={this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages}
3746
- handlePopOverClick={(data) => this.handleAddLanguageFlow(data, val, "addLanguage")}
4044
+ handlePopOverClick={(data) => this.handleAddLanguageFlow(data, val, ADD_LANGUAGE)}
3747
4045
  visible={this.state.customPopoverVisible}
3748
4046
  onVisibleChange={this.handleCustomPopoverVisibleChange}
3749
4047
  />
@@ -349,4 +349,4 @@ const withConnect = connect(mapStateToProps, mapDispatchToProps);
349
349
  const withSaga = injectSaga({ key: 'beeEditor', saga: v2BeeEditionSagas });
350
350
  const withReducer = injectReducer({ key: 'beeEditor', reducer: v2BeeEditionReducer });
351
351
 
352
- export default compose(withReducer, withSaga, withConnect)(injectIntl(BeeEditor));
352
+ export default compose(withReducer, withSaga, withConnect)(injectIntl(React.memo(BeeEditor)));
@@ -11,7 +11,7 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
11
11
  import { injectIntl, FormattedMessage } from 'react-intl';
12
12
  import classnames from 'classnames';
13
13
  import {
14
- isEmpty, get, forEach, cloneDeep,
14
+ isEmpty, get, forEach, cloneDeep, debounce,
15
15
  } from 'lodash';
16
16
  import { connect } from 'react-redux';
17
17
  import { createStructuredSelector } from 'reselect';
@@ -105,6 +105,8 @@ export class Creatives extends React.Component {
105
105
  // NEW: Test and Preview feature state
106
106
  showTestAndPreviewSlidebox: false,
107
107
  isTestAndPreviewMode: false, // Add flag to track Test & Preview mode
108
+ // Performance optimization: Local template name for immediate UI feedback
109
+ localTemplateName: '',
108
110
  };
109
111
  this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
110
112
  this.creativesTemplateSteps = {
@@ -112,6 +114,9 @@ export class Creatives extends React.Component {
112
114
  2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
113
115
  3: 'createTemplateContent',
114
116
  };
117
+
118
+ // Performance optimization: Debounced template name update
119
+ this.debouncedTemplateNameUpdate = debounce(this.performTemplateNameUpdate.bind(this), 300);
115
120
  }
116
121
 
117
122
  componentWillUnmount() {
@@ -119,6 +124,11 @@ export class Creatives extends React.Component {
119
124
  this.props.templateActions.resetTemplateStoreData();
120
125
  }
121
126
  this.props.globalActions.clearMetaEntities();
127
+
128
+ // Cleanup debounced function
129
+ if (this.debouncedTemplateNameUpdate) {
130
+ this.debouncedTemplateNameUpdate.cancel();
131
+ }
122
132
  }
123
133
 
124
134
  componentDidMount() {
@@ -136,6 +146,29 @@ export class Creatives extends React.Component {
136
146
 
137
147
  onEnterTemplateName = () => {
138
148
  this.setState({ templateNameExists: true });
149
+ }
150
+
151
+ // Performance optimized template name update
152
+ performTemplateNameUpdate = (value, formData, onFormDataChange) => {
153
+ const isEmptyTemplateName = !value.trim();
154
+ const newFormData = { ...formData, 'template-name': value, 'isTemplateNameEdited': true };
155
+
156
+ this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
157
+ onFormDataChange(newFormData);
158
+ }
159
+
160
+ // Update template name immediately for UI feedback
161
+ updateTemplateNameImmediately = (value, formData, onFormDataChange) => {
162
+ const isEmptyTemplateName = !value.trim();
163
+
164
+ // 1. IMMEDIATE: Update local state for instant UI feedback
165
+ this.setState({
166
+ isTemplateNameEmpty: isEmptyTemplateName,
167
+ localTemplateName: value
168
+ });
169
+
170
+ // 2. DEBOUNCED: Only debounce the expensive onFormDataChange call
171
+ this.debouncedTemplateNameUpdate(value, formData, onFormDataChange);
139
172
  };
140
173
 
141
174
  onRemoveTemplateName = () => {
@@ -1395,21 +1428,30 @@ export class Creatives extends React.Component {
1395
1428
  } />
1396
1429
  )
1397
1430
 
1398
- templateNameComponentInput = ({ formData, onFormDataChange, name }) => (
1399
- <CapInput
1400
- value={name}
1401
- suffix={<span />}
1402
- onBlur={() => { this.setState({ isEditName: false }, () => { this.showTemplateName({ formData, onFormDataChange }); }); }}
1403
- onChange={(ev) => {
1404
- const { value } = ev.currentTarget;
1405
- const isEmptyTemplateName = !value.trim();
1406
-
1407
- const newFormData = { ...formData, 'template-name': value, 'isTemplateNameEdited': true };
1408
- this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
1409
- onFormDataChange(newFormData);
1410
- }}
1411
- />
1412
- )
1431
+ templateNameComponentInput = ({ formData, onFormDataChange, name }) => {
1432
+ // Use local state for immediate UI feedback, fallback to prop value
1433
+ const displayValue = this.state.localTemplateName !== '' ? this.state.localTemplateName : name;
1434
+
1435
+ return (
1436
+ <CapInput
1437
+ value={displayValue}
1438
+ suffix={<span />}
1439
+ onBlur={() => {
1440
+ this.setState({
1441
+ isEditName: false,
1442
+ localTemplateName: '' // Clear local state on blur
1443
+ }, () => {
1444
+ this.showTemplateName({ formData, onFormDataChange });
1445
+ });
1446
+ }}
1447
+ onChange={(ev) => {
1448
+ const { value } = ev.currentTarget;
1449
+ // Use optimized update for better performance
1450
+ this.updateTemplateNameImmediately(value, formData, onFormDataChange);
1451
+ }}
1452
+ />
1453
+ );
1454
+ }
1413
1455
 
1414
1456
  showTemplateName = ({ formData, onFormDataChange }) => { //gets called from email/index after template data is fetched
1415
1457
  const { slidBoxContent, currentChannel, isEditName } = this.state;
@@ -1423,7 +1465,10 @@ export class Creatives extends React.Component {
1423
1465
  if (name && !isEditName) {
1424
1466
  this.setState({ showTemplateNameComponentEdit: false });
1425
1467
  } else if (isEditName) {
1426
- this.setState({ showTemplateNameComponentEdit: true });
1468
+ this.setState({
1469
+ showTemplateNameComponentEdit: true,
1470
+ localTemplateName: name || '' // Initialize local state with current value
1471
+ });
1427
1472
  }
1428
1473
  }
1429
1474
  }
@@ -2516,6 +2516,9 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2516
2516
  });
2517
2517
  obj.versions.history.push(newdata);
2518
2518
  }
2519
+ if (index === "template-subject" && obj?.versions?.history?.[0]) {
2520
+ obj.versions.history[0].subject = newdata;
2521
+ }
2519
2522
  });
2520
2523
  //const data = formData[`${this.state.currentTab - 1}`];
2521
2524
  obj.name = formData['template-name'];
@@ -207,9 +207,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
207
207
  {
208
208
  formData: newFormData,
209
209
  tabCount,
210
- isSchemaChanged:
211
- compareValue?.toLowerCase() === EXTERNAL_LINK_LOWERCASE ||
212
- !this.state?.isSchemaChanged,
213
210
  },
214
211
  () => {
215
212
  if (isFullMode && showTemplateName) {
@@ -220,6 +217,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
220
217
  }
221
218
  }
222
219
  );
220
+ this.setState({isSchemaChanged:
221
+ compareValue?.toLowerCase() === EXTERNAL_LINK_LOWERCASE ||
222
+ !this.state?.isSchemaChanged});
223
223
  };
224
224
 
225
225
  onTagSelect = (data, currentTab, srcComp) => {