@capillarytech/creatives-library 8.0.213 → 8.0.214-beta.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.
Files changed (37) hide show
  1. package/HOW_BEE_EDITOR_WORKS.md +375 -0
  2. package/constants/unified.js +1 -0
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/utils/common.js +6 -1
  6. package/v2Components/CapTagList/index.js +2 -1
  7. package/v2Components/CapTagListWithInput/index.js +5 -1
  8. package/v2Components/CapTagListWithInput/messages.js +1 -1
  9. package/v2Components/ErrorInfoNote/style.scss +1 -1
  10. package/v2Components/HtmlEditor/HTMLEditor.js +86 -14
  11. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -4
  12. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  13. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +107 -96
  14. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +68 -92
  15. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  16. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  17. package/v2Containers/CreativesContainer/SlideBoxContent.js +85 -35
  18. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  19. package/v2Containers/CreativesContainer/index.js +107 -35
  20. package/v2Containers/CreativesContainer/messages.js +4 -0
  21. package/v2Containers/Email/actions.js +7 -0
  22. package/v2Containers/Email/constants.js +5 -1
  23. package/v2Containers/Email/index.js +13 -0
  24. package/v2Containers/Email/messages.js +32 -0
  25. package/v2Containers/Email/reducer.js +12 -1
  26. package/v2Containers/Email/sagas.js +17 -0
  27. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1005 -0
  28. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  29. package/v2Containers/EmailWrapper/constants.js +2 -0
  30. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +470 -71
  31. package/v2Containers/EmailWrapper/index.js +102 -23
  32. package/v2Containers/EmailWrapper/messages.js +61 -1
  33. package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +177 -0
  34. package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +90 -0
  35. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
  36. package/v2Containers/TagList/index.js +2 -0
  37. package/v2Containers/Templates/index.js +5 -0
@@ -0,0 +1,375 @@
1
+ Tracing how the BEE editor is loaded in the Email create and edit flows. Searching the codebase for relevant code.
2
+
3
+
4
+ [5 tools called]
5
+
6
+
7
+ Reviewing key files to understand the flow:
8
+
9
+
10
+ [4 tools called]
11
+
12
+
13
+ Reviewing how BEE editor gets initialized with token and JSON data:
14
+
15
+
16
+ [4 tools called]
17
+
18
+
19
+ Reviewing the edit flow initialization:
20
+
21
+
22
+ [1 tool called]
23
+
24
+
25
+ ## How BEE Editor is Loaded in Email Create and Edit Flows
26
+
27
+ ### Overview
28
+ The BEE editor loads via a multi-step process that fetches CMS settings (token and JSON) and renders the editor component when conditions are met.
29
+
30
+ ---
31
+
32
+ ## CREATE FLOW
33
+
34
+ ### 1. Initial Setup (EmailWrapper)
35
+ Location: `app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js`
36
+
37
+ - User selects "Use Editor" mode
38
+ - When step is `TEMPLATE_SELECTION` and templates are needed:
39
+ ```223:230:app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js
40
+ case STEPS.TEMPLATE_SELECTION:
41
+ const needsTemplates = emailCreateMode === EMAIL_CREATE_MODES.EDITOR
42
+ && !CmsTemplates
43
+ && !getCmsTemplatesInProgress;
44
+
45
+ if (needsTemplates) {
46
+ templatesActions.getDefaultBeeTemplates();
47
+ }
48
+ ```
49
+ - User selects a template → `handleEdmDefaultTemplateSelection` is called
50
+ - Sets `selectedCreateMode` to `EDITOR` and stores the selected template
51
+
52
+ ### 2. Email Component Initialization
53
+ Location: `app/v2Containers/Email/index.js`
54
+
55
+ In `componentWillMount`:
56
+ ```208:219:app/v2Containers/Email/index.js
57
+ const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
58
+ const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
59
+ if (!_.isEmpty(this.props.Templates.BEETemplate)) {
60
+ if (this.props.Templates.BEETemplate.versions.base.is_drag_drop && isBEEAppEnable ) {
61
+ this.setState({isDragDrop: true});
62
+ }
63
+ if (this.props.params.id) {
64
+ this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'open', undefined, isBEESupport, isBEEAppEnable);
65
+ } else if (this.props.location.query.module !== "library" || (this.props.location.query.module === "library" && !this.props.templateData)) {
66
+ this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'create', undefined, isBEESupport, isBEEAppEnable);
67
+ }
68
+ }
69
+ ```
70
+
71
+ - For create flow: calls `getCmsSetting` with `cmsMode: 'create'`
72
+ - This fetches the BEE token and settings from the API
73
+
74
+ ### 3. Receiving CMS Settings
75
+ Location: `app/v2Containers/Email/index.js` - `componentWillReceiveProps`
76
+
77
+ When `CmsSettings` are received:
78
+ ```379:419:app/v2Containers/Email/index.js
79
+ if (!_.isEmpty(nextProps.Email.CmsSettings) && !_.isEqual(this.props.Email.CmsSettings, nextProps.Email.CmsSettings) && !_.isEmpty(this.state.schema)) {
80
+ const apiLangId = nextProps.Email.CmsSettings.langId;
81
+ const langId = nextProps.Email.CmsSettings.langId !== "undefined" ? nextProps.Email.CmsSettings.langId : nextProps.currentOrgDetails.basic_details.base_language;
82
+
83
+ const formData = _.cloneDeep(this.state.formData);
84
+
85
+ const schema = _.cloneDeep(this.state.schema);
86
+ const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
87
+
88
+ const temp = (schema.containers || {})[this.state.currentTab - 1];
89
+
90
+ const { currentTab } = this.state;
91
+ if (nextProps.Email.CmsSettings.isDragDrop && this.checkBeeEditorAllowedForLibrary()) {
92
+ const beeJson = `BEEeditor${currentTab > 1 ? currentTab : ''}json`;
93
+ const beeToken = `BEEeditor${currentTab > 1 ? currentTab : ''}token`;
94
+ let beeJsonValue = _.get(nextProps, 'Templates.BEETemplate.versions.base.json-content', '');
95
+ const selectedId = _.get(this.props, 'Email.templateDetails._id', '') || _.get(this.props, 'Templates.BEETemplate.versions.base.drag_drop_id', '');
96
+ if (this.state.isEdit) {
97
+ if (this.props.location.query.module === "library") {
98
+ beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
99
+ } else {
100
+ beeJsonValue = _.get(nextProps, `Email.templateDetails.versions.base[${langId}].json-content`, '');
101
+ }
102
+ }
103
+ formData[`${currentTab - 1}`][langId] = {
104
+ ...formData[`${currentTab - 1}`][langId],
105
+ is_drag_drop: true,
106
+ [beeJson]: beeJsonValue,
107
+ [beeToken]: nextProps.Email.CmsSettings.tokenData,
108
+ id: selectedId,
109
+ };
110
+ _.forEach(temp.panes, (pane, index) => {
111
+ const tempPane = pane;
112
+ //
113
+ if (parseInt(index, 10) === parseInt(langIndex, 10)) {
114
+ //
115
+ tempPane.sections[0].inputFields[0].cols[1].colStyle = {display: ""};
116
+ tempPane.sections[0].inputFields[0].cols[0].colStyle = {display: "none"};
117
+ }
118
+ });
119
+ this.setState({schema, isSchemaChanged: true, loadingStatus: this.state.loadingStatus + 1});
120
+ ```
121
+
122
+ - Stores `tokenData` and `json-content` in formData
123
+ - Updates schema to show BEE editor (hide CKEditor)
124
+ - Sets `is_drag_drop: true`
125
+
126
+ ### 4. FormBuilder Renders BEE Editor
127
+ Location: `app/v2Components/FormBuilder/index.js`
128
+
129
+ When schema type is `"BEEeditor"`:
130
+ ```3976:4027:app/v2Components/FormBuilder/index.js
131
+ case "BEEeditor": {
132
+ let langTab = 1;
133
+ if (val.id.match(/BEEeditor/g)) {
134
+ if (val.id.length > 9 && val.id.charAt(9)) {
135
+ langTab = val.id.charAt(9);
136
+ }
137
+ }
138
+ const { currentTab, formData } = this.state;
139
+ const supportedLanguages = (formData[currentTab - 1] || {}).selectedLanguages || {};
140
+ const currentLang = supportedLanguages[langTab - 1];
141
+
142
+ let beeJson = '',
143
+ beeToken = '',
144
+ uuid = '';
145
+ const data = formData[`${currentTab - 1}`][currentLang];
146
+ if (data) {
147
+ beeJson = data[`BEEeditor${currentTab > 1 ? currentTab : ''}json`];
148
+ beeToken = data[`BEEeditor${currentTab > 1 ? currentTab : ''}token`];
149
+ uuid = this.props.uuid;
150
+ }
151
+ if (!beeJson || !beeToken) {
152
+ return false;
153
+ }
154
+ let isModuleFilterEnabled = _.get(this.props.location, 'query.type', '') !== 'embedded';
155
+ const schemaChannel = _.get(this.props.schema, 'channel', "");
156
+ if (schemaChannel === 'EMAIL') {
157
+ isModuleFilterEnabled = this.props.isFullMode;
158
+ }
159
+ columns.push(
160
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"}`}>
161
+ <BeeEditor
162
+ uid={uuid}
163
+ tokenData={beeToken}
164
+ id={val.id}
165
+ beeJson={beeJson}
166
+ showTagsPopover={this.showTagsPopover}
167
+ location={this.props.location}
168
+ label={val.label || ''}
169
+ className={val.className || ''}
170
+ userLocale={this.props.userLocale}
171
+ selectedOfferDetails={this.props.selectedOfferDetails}
172
+ tags={this.props.tags || []}
173
+ injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
174
+ saveBeeInstance={this.props.saveBeeInstance}
175
+ saveBeeData={this.props.saveBeeData}
176
+ onContextChange={this.props.onContextChange}
177
+ moduleFilterEnabled={isModuleFilterEnabled}
178
+ eventContextTags={this.props?.eventContextTags}
179
+ />
180
+ </CapColumn>
181
+ );
182
+ }
183
+ ```
184
+
185
+ - Extracts `beeJson` and `beeToken` from formData
186
+ - Renders `<BeeEditor>` with these props
187
+
188
+ ### 5. BeeEditor Component Initialization
189
+ Location: `app/v2Containers/BeeEditor/index.js`
190
+
191
+ In `useEffect`:
192
+ ```107:197:app/v2Containers/BeeEditor/index.js
193
+ useEffect(() => {
194
+ const beeConfig = {
195
+ uid,
196
+ container: 'bee-plugin-container',
197
+ translationsUrl: defaultFormattedUrl,
198
+ rowsConfiguration: {
199
+ emptyRows: true,
200
+ defaultRows: true,
201
+ externalContentURLs: getExternalContentURLs(),
202
+ },
203
+ contentDialog: {
204
+ specialLinks: {
205
+ label: UNSUBSCRIBE,
206
+ handler(resolve) {
207
+ resolve({
208
+ type: 'custom',
209
+ label: UNSUBSCRIBE,
210
+ link: `{{${UNSUBSCRIBE}}}`,
211
+ });
212
+ },
213
+ },
214
+ mergeTags: {
215
+ label: formatMessage(messages.addLabel),
216
+ handler: async (resolve, reject) => {
217
+ // this will open tag modal
218
+ await setVisibleTaglist(true);
219
+ // until tag modal will not open promise will not execute
220
+ // once tag modal is opened it will start 2 sec interval to wait use has selected any tag or cancel the tag selection
221
+ const promise = new Promise((resolveP) => {
222
+ intervalTimer = setInterval(() => {
223
+ // this will execute, if user cancel the tag selection
224
+ if ((savedCallback.current || {}).close === true) {
225
+ reject();
226
+ clearInterval(intervalTimer);
227
+ return;
228
+ }
229
+ // this block is checking use has selected any tag or not
230
+ if (Object.keys(savedCallback.current || {}).length > 0) {
231
+ resolveP(savedCallback.current);
232
+ setSelectedTag({});
233
+ clearInterval(intervalTimer);
234
+ }
235
+ }, 2000);
236
+ });
237
+ // once prmise will resolve , pass the resolve data to handler to show tags in bee edior
238
+ const result = await promise;
239
+ resolve(result);
240
+ },
241
+ },
242
+ saveRow: {
243
+ handler: async (resolve, reject) => {
244
+ await setRowMetaModal(true);
245
+ const promise = new Promise((resolveP) => {
246
+ intervalTimer = setInterval(() => {
247
+ if ((savedCallback.current || {}).close === true) {
248
+ reject();
249
+ clearInterval(intervalTimer);
250
+ setRowMetaInfo({});
251
+ setRowName('');
252
+ setRowCategory('');
253
+ return;
254
+ }
255
+ if (Object.keys(savedCallback.current || {}).length > 0) {
256
+ resolveP(savedCallback.current);
257
+ }
258
+ }, 2000);
259
+ });
260
+ const result = await promise;
261
+ resolve(result); // "done!"
262
+ setRowMetaInfo({});
263
+ setRowName('');
264
+ setRowCategory('');
265
+ clearInterval(intervalTimer);
266
+ },
267
+ },
268
+ },
269
+ onSave: (jsonFile, htmlFile) => {
270
+ saveBeeData(jsonFile, htmlFile);
271
+ },
272
+ onSaveRow: (rowJSON) => {
273
+ actions.createCustomRow(JSON.parse(rowJSON), callbackSaveRow);
274
+ },
275
+ };
276
+ window.BeePlugin.create(tokenData, beeConfig, (instance) => {
277
+ beePluginInstance = instance;
278
+ const parseJson = JSON.parse(beeJson);
279
+ beePluginInstance.start(parseJson);
280
+ saveBeeInstance(beePluginInstance);
281
+ });
282
+ return () => clearInterval(intervalTimer);
283
+ }, []);
284
+ ```
285
+
286
+ - Creates BEE plugin instance with `tokenData`
287
+ - Starts editor with parsed `beeJson`
288
+ - Saves instance reference
289
+
290
+ ---
291
+
292
+ ## EDIT FLOW
293
+
294
+ ### 1. Component Initialization
295
+ Location: `app/v2Containers/Email/index.js`
296
+
297
+ In `componentWillMount`:
298
+ ```193:199:app/v2Containers/Email/index.js
299
+ if (this.props.params.id || (this.props.location.query.module === "library" && this.props.templateData && !_.isEmpty(this.props.templateData))) {
300
+ this.setState({isEdit: true}, () => {
301
+ if (this.props.params.id) {
302
+ this.props.actions.getTemplateDetails(this.props.params.id, 'email');
303
+ }
304
+ });
305
+ }
306
+ ```
307
+
308
+ - Sets `isEdit: true`
309
+ - Fetches template details if `params.id` exists
310
+
311
+ ### 2. Setting Edit Data
312
+ Location: `app/v2Containers/Email/index.js` - `setEditData`
313
+
314
+ After template details load:
315
+ ```992:1003:app/v2Containers/Email/index.js
316
+ const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
317
+ _.forEach((editData.versions.base.selectedLanguages), (language) => {
318
+ // if (formData[this.state.currentTab - 1].tabKey === formData[this.state.currentTab - 1][language].tabKey) {
319
+ // formData[this.state.currentTab - 1].activeTab = language;
320
+ // }
321
+ // if (formData.base.tabKey === formData.base[language].tabKey) {
322
+ // formData.base.activeTab = language;
323
+ // }
324
+ if (language && editData.versions.base[language].is_drag_drop && isBEEAppEnable) {
325
+ this.props.actions.getCmsSetting(BEE_PLUGIN, editData._id, 'open', language, isBEESupport, isBEEAppEnable);
326
+ }
327
+ });
328
+ ```
329
+
330
+ - For each language with `is_drag_drop: true`, calls `getCmsSetting` with `cmsMode: 'open'` and the template `_id`
331
+
332
+ ### 3. Receiving CMS Settings (Same as Create)
333
+ When `CmsSettings` are received, the same logic applies:
334
+ - Stores token and JSON in formData
335
+ - Updates schema to show BEE editor
336
+ - For edit, uses existing `json-content` from template data:
337
+ ```396:401:app/v2Containers/Email/index.js
338
+ if (this.state.isEdit) {
339
+ if (this.props.location.query.module === "library") {
340
+ beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
341
+ } else {
342
+ beeJsonValue = _.get(nextProps, `Email.templateDetails.versions.base[${langId}].json-content`, '');
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### 4. FormBuilder Renders BEE Editor (Same as Create)
348
+ Same rendering logic as create flow.
349
+
350
+ ### 5. BeeEditor Component Initialization (Same as Create)
351
+ Same initialization logic as create flow.
352
+
353
+ ---
354
+
355
+ ## Key Differences: Create vs Edit
356
+
357
+ | Aspect | Create Flow | Edit Flow |
358
+ |--------|-------------|-----------|
359
+ | CMS Mode | `'create'` | `'open'` |
360
+ | Template ID | From `BEETemplate.versions.base.drag_drop_id` | From `editData._id` |
361
+ | JSON Content | From `BEETemplate.versions.base.json-content` (template default) | From `templateDetails.versions.base[langId].json-content` (saved template) |
362
+ | Trigger | User selects template in EmailWrapper | Template details loaded in `setEditData` |
363
+
364
+ ---
365
+
366
+ ## API Flow
367
+
368
+ 1. `getCmsSetting` action → calls `getCmsTemplateSettingsV2` API
369
+ 2. API returns: `tokenData`, `isDragDrop`, `uuid`, `langId`
370
+ 3. Redux stores result in `Email.CmsSettings`
371
+ 4. Component receives props → updates formData and schema
372
+ 5. FormBuilder renders BeeEditor with token and JSON
373
+ 6. BeeEditor initializes BEE plugin instance
374
+
375
+ This flow ensures the BEE editor loads with the correct authentication token and template content for both create and edit scenarios.
@@ -45,6 +45,7 @@ export const GIFT_CARDS = 'GIFT_CARDS';
45
45
  export const PROMO_ENGINE = 'PROMO_ENGINE';
46
46
  export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
47
47
  export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
48
+ export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
48
49
  export const CUSTOM_TAG = 'CustomTagMessage';
49
50
  export const CUSTOMER_EXTENDED_FIELD = 'Customer extended fields';
50
51
  export const EXTENDED_TAG = 'ExtendedTagMessage';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.213",
4
+ "version": "8.0.214-beta.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -465,6 +465,11 @@ export const getCmsTemplateSettingsV2 = (cmsType, projectId, cmsMode, langId, is
465
465
  return API.get(url);
466
466
  };
467
467
 
468
+ export const getCmsAccounts = (cmsType) => {
469
+ const url = `${API_ENDPOINT}/cms/accounts?type=${cmsType}`;
470
+ return API.get(url);
471
+ };
472
+
468
473
  export const getCmsTemplateData = (cmsType, projectId, langId) => {
469
474
  const url = `${API_ENDPOINT}/cms/getContent?type=${cmsType}&projectId=${projectId}&langId=${langId}`;
470
475
  return API.get(url);
package/utils/common.js CHANGED
@@ -22,7 +22,8 @@ import {
22
22
  BADGES_ISSUE,
23
23
  ENABLE_WECHAT,
24
24
  LIQUID_SUPPORT,
25
- ENABLE_NEW_MPUSH
25
+ ENABLE_NEW_MPUSH,
26
+ SUPPORT_CK_EDITOR
26
27
  } from '../constants/unified';
27
28
  import { apiMessageFormatHandler } from './commonUtils';
28
29
 
@@ -95,6 +96,10 @@ export const hasLiquidSupportFeature = Auth.hasFeatureAccess.bind(
95
96
  LIQUID_SUPPORT,
96
97
  );
97
98
 
99
+ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
100
+ null,
101
+ SUPPORT_CK_EDITOR,
102
+ );
98
103
 
99
104
  export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
100
105
  null,
@@ -468,7 +468,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
468
468
  onVisibleChange={this.togglePopoverVisibility}
469
469
  content={contentSection}
470
470
  trigger="click"
471
- placement={channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop"}
471
+ placement={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
472
472
  >
473
473
  <CapTooltip
474
474
  title={
@@ -535,6 +535,7 @@ CapTagList.propTypes = {
535
535
  channel: PropTypes.string,
536
536
  disabled: PropTypes.bool,
537
537
  fetchingSchemaError: PropTypes.bool,
538
+ popoverPlacement: PropTypes.string,
538
539
  };
539
540
 
540
541
  CapTagList.defaultValue = {
@@ -48,13 +48,14 @@ export const CapTagListWithInput = (props) => {
48
48
  showTagList = true,
49
49
  showInput = true,
50
50
  inputProps = {},
51
+ popoverPlacement,
51
52
  } = props;
52
53
 
53
54
  const { formatMessage } = intl;
54
55
 
55
56
  return (
56
57
  <CapColumn style={containerStyle}>
57
- <CapRow style={{display: 'flex', flexDirection: 'row'}}>
58
+ <CapRow align="middle" type="flex">
58
59
  {showHeading && headingText && (
59
60
  <CapHeading type={headingType} style={headingStyle}>
60
61
  {headingText}
@@ -76,6 +77,7 @@ export const CapTagListWithInput = (props) => {
76
77
  selectedOfferDetails={selectedOfferDetails}
77
78
  eventContextTags={eventContextTags}
78
79
  style={tagListStyle}
80
+ popoverPlacement={popoverPlacement}
79
81
  />
80
82
  )}
81
83
  </CapRow>
@@ -136,6 +138,7 @@ CapTagListWithInput.propTypes = {
136
138
  showHeading: PropTypes.bool,
137
139
  showTagList: PropTypes.bool,
138
140
  showInput: PropTypes.bool,
141
+ popoverPlacement: PropTypes.string,
139
142
  };
140
143
 
141
144
  CapTagListWithInput.defaultProps = {
@@ -164,6 +167,7 @@ CapTagListWithInput.defaultProps = {
164
167
  showTagList: true,
165
168
  showInput: true,
166
169
  inputProps: {},
170
+ popoverPlacement: undefined,
167
171
  };
168
172
 
169
173
  export default injectIntl(CapTagListWithInput);
@@ -5,6 +5,6 @@ const prefix = 'creatives.componentsV2.CapTagListWithInput';
5
5
  export default defineMessages({
6
6
  addLabels: {
7
7
  id: `${prefix}.addLabels`,
8
- defaultMessage: 'Add labels',
8
+ defaultMessage: 'Add label',
9
9
  },
10
10
  });
@@ -1,7 +1,7 @@
1
1
  @import "~@capillarytech/cap-ui-library/styles/_variables.scss";
2
2
 
3
3
  .error-container {
4
- width: max-content;
4
+ width: 100%;
5
5
  margin-bottom: $CAP_SPACE_08;
6
6
  margin-top: $CAP_SPACE_12;
7
7
  background-color: $CAP_COLOR_05;
@@ -12,7 +12,7 @@
12
12
  * Note: Uses injectIntl with forwardRef to provide direct access to CodeEditorPane via ref
13
13
  */
14
14
 
15
- import React, { useRef, useCallback, useMemo, useState } from 'react';
15
+ import React, { useRef, useCallback, useMemo, useState, useEffect } from 'react';
16
16
  import PropTypes from 'prop-types';
17
17
  import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
18
18
  import { injectIntl, intlShape } from 'react-intl';
@@ -61,6 +61,16 @@ const HTMLEditor = ({
61
61
  showFullscreenButton = true,
62
62
  autoSave = true,
63
63
  autoSaveInterval = 30000, // 30 seconds
64
+ // Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
65
+ tags = [],
66
+ injectedTags = {},
67
+ location,
68
+ eventContextTags = [],
69
+ selectedOfferDetails = [],
70
+ channel,
71
+ userLocale = 'en',
72
+ moduleFilterEnabled = true,
73
+ onTagContextChange, // Parent component handles tag fetching
64
74
  ...props
65
75
  }) => {
66
76
  // Separate refs for main and modal editors to avoid conflicts
@@ -117,6 +127,29 @@ const HTMLEditor = ({
117
127
  // Use appropriate content hook based on variant
118
128
  const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
119
129
 
130
+ // Update content when initialContent prop changes (for edit mode)
131
+ // This ensures the editor updates when template data loads
132
+ useEffect(() => {
133
+ if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
134
+ // Only update if content is different to avoid unnecessary updates
135
+ if (emailContent.content !== initialContent) {
136
+ emailContent.updateContent(initialContent, true); // immediate update
137
+ }
138
+ } else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
139
+ // Handle InApp variant updates
140
+ const contentToUpdate = typeof initialContent === 'string'
141
+ ? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
142
+ : initialContent;
143
+ if (inAppContent.updateContent) {
144
+ const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
145
+ const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
146
+ if (currentContent !== newContent) {
147
+ inAppContent.updateContent(newContent, true);
148
+ }
149
+ }
150
+ }
151
+ }, [initialContent, isEmailVariant, isInAppVariant]);
152
+
120
153
  // Destructure content properties for cleaner access throughout component
121
154
  const {
122
155
  activeDevice,
@@ -387,6 +420,15 @@ const HTMLEditor = ({
387
420
  ref={mainEditorRef}
388
421
  readOnly={readOnly}
389
422
  onLabelInsert={handleLabelInsert}
423
+ tags={tags}
424
+ injectedTags={injectedTags}
425
+ location={location}
426
+ eventContextTags={eventContextTags}
427
+ selectedOfferDetails={selectedOfferDetails}
428
+ channel={channel}
429
+ userLocale={userLocale}
430
+ moduleFilterEnabled={moduleFilterEnabled}
431
+ onTagContextChange={onTagContextChange}
390
432
  />
391
433
 
392
434
  {/* Preview Pane */}
@@ -454,19 +496,28 @@ const HTMLEditor = ({
454
496
  readOnly={readOnly}
455
497
  isFullscreenMode={true}
456
498
  onLabelInsert={handleLabelInsert}
499
+ tags={tags}
500
+ injectedTags={injectedTags}
501
+ location={location}
502
+ eventContextTags={eventContextTags}
503
+ selectedOfferDetails={selectedOfferDetails}
504
+ channel={channel}
505
+ userLocale={userLocale}
506
+ moduleFilterEnabled={moduleFilterEnabled}
507
+ onTagContextChange={onTagContextChange}
457
508
  />
458
509
 
459
- {/* Preview Pane */}
460
- <PreviewPane isFullscreenMode={true} isModalContext={true} />
461
- </SplitContainer>
510
+ {/* Preview Pane */}
511
+ <PreviewPane isFullscreenMode={true} isModalContext={true} />
512
+ </SplitContainer>
462
513
 
463
- {/* Validation Display in Modal */}
464
- <ValidationErrorDisplay
465
- validation={validation}
466
- onErrorClick={handleValidationErrorClick}
467
- variant={variant}
468
- className="html-editor-validation"
469
- />
514
+ {/* Validation Display in Modal */}
515
+ <ValidationErrorDisplay
516
+ validation={validation}
517
+ onErrorClick={handleValidationErrorClick}
518
+ variant={variant}
519
+ className="html-editor-validation"
520
+ />
470
521
  </CapRow>
471
522
  </CapRow>
472
523
  </CapModal>
@@ -481,7 +532,7 @@ HTMLEditor.propTypes = {
481
532
  layoutType: PropTypes.string, // Layout type for InApp variant
482
533
  initialContent: PropTypes.oneOfType([
483
534
  PropTypes.string,
484
- PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
535
+ PropTypes.objectOf(PropTypes.string), // Per-device content for INAPP variant
485
536
  ]),
486
537
  onSave: PropTypes.func,
487
538
  onContentChange: PropTypes.func,
@@ -489,11 +540,22 @@ HTMLEditor.propTypes = {
489
540
  readOnly: PropTypes.bool,
490
541
  showFullscreenButton: PropTypes.bool,
491
542
  autoSave: PropTypes.bool,
492
- autoSaveInterval: PropTypes.number
543
+ autoSaveInterval: PropTypes.number,
544
+ // Tag-related props - tags are fetched and managed by parent component
545
+ tags: PropTypes.array,
546
+ injectedTags: PropTypes.object,
547
+ location: PropTypes.object,
548
+ eventContextTags: PropTypes.array,
549
+ selectedOfferDetails: PropTypes.array,
550
+ channel: PropTypes.string,
551
+ userLocale: PropTypes.string,
552
+ moduleFilterEnabled: PropTypes.bool,
553
+ onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
493
554
  };
494
555
 
495
556
  HTMLEditor.defaultProps = {
496
557
  variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
558
+ layoutType: null,
497
559
  initialContent: null, // Will use default from useEditorContent hook
498
560
  onSave: null,
499
561
  onContentChange: null,
@@ -501,7 +563,17 @@ HTMLEditor.defaultProps = {
501
563
  readOnly: false,
502
564
  showFullscreenButton: true,
503
565
  autoSave: true,
504
- autoSaveInterval: 30000
566
+ autoSaveInterval: 30000,
567
+ // Tag-related defaults - tags are fetched and managed by parent component
568
+ tags: [],
569
+ injectedTags: {},
570
+ location: null,
571
+ eventContextTags: [],
572
+ selectedOfferDetails: [],
573
+ channel: null,
574
+ userLocale: 'en',
575
+ moduleFilterEnabled: true,
576
+ onTagContextChange: null, // Parent component should provide this
505
577
  };
506
578
 
507
579
  // Export with forwardRef to allow direct access to CodeEditorPane via ref
@@ -267,10 +267,10 @@
267
267
 
268
268
  // Focus states and accessibility
269
269
  .html-editor {
270
- &:focus-within {
271
- outline: 0.125rem solid map-get($CAP_PRIMARY, base); // 2px = 0.125rem
272
- outline-offset: -0.125rem; // -2px = -0.125rem
273
- }
270
+ // &:focus-within {
271
+ // outline: 0.125rem solid map-get($CAP_PRIMARY, base); // 2px = 0.125rem
272
+ // outline-offset: -0.125rem; // -2px = -0.125rem
273
+ // }
274
274
 
275
275
  // High contrast mode support
276
276
  @media (prefers-contrast: high) {