@capillarytech/creatives-library 9.0.11 → 9.0.13-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/constants/unified.js +3 -0
- package/package.json +1 -1
- package/utils/common.js +8 -0
- package/v2Components/FormBuilder/Classic.js +4487 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +369 -0
- package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
- package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
- package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
- package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
- package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
- package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
- package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
- package/v2Components/FormBuilder/Functional/constants.js +39 -0
- package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
- package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
- package/v2Components/FormBuilder/Functional/index.js +26 -0
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +32 -0
- package/v2Components/FormBuilder/Functional/layout/Section.js +118 -0
- package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +265 -0
- package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
- package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
- package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
- package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
- package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
- package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
- package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
- package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
- package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
- package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
- package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
- package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +162 -0
- package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
- package/v2Components/FormBuilder/_formBuilder.scss +8 -0
- package/v2Components/FormBuilder/index.js +41 -4479
- package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
- package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
- package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
- package/v2Components/TemplatePreview/coderabbits_comments +171 -0
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +7 -0
- package/v2Containers/Ebill/index.js +31 -7
- package/v2Containers/Templates/index.js +1 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +96 -51
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
@coderabbitai
|
|
2
|
+
coderabbitai Bot
|
|
3
|
+
2 days ago
|
|
4
|
+
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
|
|
5
|
+
|
|
6
|
+
Guard non-string channel values before calling toUpperCase().
|
|
7
|
+
|
|
8
|
+
Current normalization crashes for truthy non-string channel values instead of returning null.
|
|
9
|
+
|
|
10
|
+
Proposed fix
|
|
11
|
+
-export const getChannelAdapter = (channel) => adapters[(channel || '').toUpperCase()] || null;
|
|
12
|
+
+export const getChannelAdapter = (channel) => {
|
|
13
|
+
+ const key = typeof channel === 'string' ? channel.toUpperCase() : '';
|
|
14
|
+
+ return adapters[key] || null;
|
|
15
|
+
+};
|
|
16
|
+
🤖 Prompt for AI Agents
|
|
17
|
+
Verify each finding against current code. Fix only still-valid issues, skip the
|
|
18
|
+
rest with a brief reason, keep changes minimal, and validate.
|
|
19
|
+
|
|
20
|
+
In `@app/v2Components/FormBuilder/Functional/channels/registry.js` at line 12, The
|
|
21
|
+
getChannelAdapter function does not guard against non-string values for the
|
|
22
|
+
channel parameter. When a truthy non-string value (like a number or object) is
|
|
23
|
+
passed, calling toUpperCase() on it will crash. Add a type check to ensure the
|
|
24
|
+
channel value is a string before attempting to call toUpperCase() on it. If
|
|
25
|
+
channel is not a string, return null directly. This prevents the crash while
|
|
26
|
+
maintaining the existing behavior for null, undefined, and string values.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
coderabbitai Bot
|
|
32
|
+
2 days ago
|
|
33
|
+
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
|
|
34
|
+
|
|
35
|
+
Guard MULTICOLS row dereference to prevent schema-driven crashes.
|
|
36
|
+
|
|
37
|
+
Line 38/39 assumes every row exists. A null row from schema data will throw before form initialization completes.
|
|
38
|
+
|
|
39
|
+
Proposed fix
|
|
40
|
+
- (section.inputFields || []).forEach((row) => (row.cols || []).forEach(visit));
|
|
41
|
+
- (section.actionFields || []).forEach((row) => (row.cols || []).forEach(visit));
|
|
42
|
+
+ (section.inputFields || []).forEach((row) => (row?.cols || []).forEach(visit));
|
|
43
|
+
+ (section.actionFields || []).forEach((row) => (row?.cols || []).forEach(visit));
|
|
44
|
+
🤖 Prompt for AI Agents
|
|
45
|
+
Verify each finding against current code. Fix only still-valid issues, skip the
|
|
46
|
+
rest with a brief reason, keep changes minimal, and validate.
|
|
47
|
+
|
|
48
|
+
In `@app/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js`
|
|
49
|
+
around lines 37 - 40, The MULTICOLS case in the section type handling does not
|
|
50
|
+
guard against null or undefined row values before accessing the cols property,
|
|
51
|
+
which will cause a crash if schema data contains null rows. Add a null check for
|
|
52
|
+
each row within the forEach callbacks for both inputFields and actionFields
|
|
53
|
+
processing to ensure the row exists before attempting to access row.cols,
|
|
54
|
+
preventing dereferencing of null values during form initialization.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
coderabbitai Bot
|
|
60
|
+
2 days ago
|
|
61
|
+
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
|
|
62
|
+
|
|
63
|
+
Preserve original numeric tab indices when decoding legacy formData.
|
|
64
|
+
|
|
65
|
+
Line 52 compacts tab keys into a dense array. For sparse legacy keys (e.g., only 1 present), toLegacy writes them back as 0, changing payload shape and base-tab addressing.
|
|
66
|
+
|
|
67
|
+
Proposed fix
|
|
68
|
+
📝 Committable suggestion
|
|
69
|
+
‼️ IMPORTANT
|
|
70
|
+
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
|
|
71
|
+
|
|
72
|
+
Suggested change
|
|
73
|
+
const orderedIndices = Object.keys(tabsByIndex)
|
|
74
|
+
.map(Number)
|
|
75
|
+
.sort((a, b) => a - b);
|
|
76
|
+
const tabs = orderedIndices.map((index) => tabsByIndex[index]);
|
|
77
|
+
const foundBase = tabs.findIndex((tab) => tab?.base);
|
|
78
|
+
const baseTabIndex = foundBase === -1 ? 0 : foundBase;
|
|
79
|
+
const orderedIndices = Object.keys(tabsByIndex)
|
|
80
|
+
.map(Number)
|
|
81
|
+
.sort((a, b) => a - b);
|
|
82
|
+
const tabs = [];
|
|
83
|
+
orderedIndices.forEach((index) => {
|
|
84
|
+
tabs[index] = tabsByIndex[index];
|
|
85
|
+
});
|
|
86
|
+
const foundBase = orderedIndices.find((index) => tabsByIndex[index]?.base);
|
|
87
|
+
const baseTabIndex = foundBase === undefined ? 0 : foundBase;
|
|
88
|
+
🤖 Prompt for AI Agents
|
|
89
|
+
Verify each finding against current code. Fix only still-valid issues, skip the
|
|
90
|
+
rest with a brief reason, keep changes minimal, and validate.
|
|
91
|
+
|
|
92
|
+
In `@app/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js`
|
|
93
|
+
around lines 49 - 56, The issue is that the code compacts sparse tab indices
|
|
94
|
+
into a dense array by mapping orderedIndices to a new tabs array, which loses
|
|
95
|
+
the original numeric indices and changes the payload shape when converting back
|
|
96
|
+
to legacy format. Instead of creating a compacted tabs array using
|
|
97
|
+
orderedIndices.map, preserve the sparse structure by keeping the original
|
|
98
|
+
numeric keys from tabsByIndex and adjusting the baseTabIndex calculation to work
|
|
99
|
+
with the actual tab indices rather than array positions.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@coderabbitai
|
|
103
|
+
coderabbitai Bot
|
|
104
|
+
2 days ago
|
|
105
|
+
⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
|
|
106
|
+
|
|
107
|
+
Fix TagList prop contract types/defaults in FormBuilderShell.
|
|
108
|
+
|
|
109
|
+
selectedOfferDetails and waitEventContextTags are typed opposite to how app/v2Containers/TagList/index.js consumes them (array and object respectively), which weakens contract checks and can hide wrong-shape regressions.
|
|
110
|
+
|
|
111
|
+
Suggested fix
|
|
112
|
+
FormBuilderShell.propTypes = {
|
|
113
|
+
@@
|
|
114
|
+
- selectedOfferDetails: PropTypes.object,
|
|
115
|
+
+ selectedOfferDetails: PropTypes.array,
|
|
116
|
+
@@
|
|
117
|
+
- waitEventContextTags: PropTypes.array,
|
|
118
|
+
+ waitEventContextTags: PropTypes.object,
|
|
119
|
+
@@
|
|
120
|
+
FormBuilderShell.defaultProps = {
|
|
121
|
+
@@
|
|
122
|
+
- selectedOfferDetails: undefined,
|
|
123
|
+
+ selectedOfferDetails: [],
|
|
124
|
+
@@
|
|
125
|
+
- waitEventContextTags: [],
|
|
126
|
+
+ waitEventContextTags: {},
|
|
127
|
+
Also applies to: 361-364
|
|
128
|
+
|
|
129
|
+
🤖 Prompt for AI Agents
|
|
130
|
+
Verify each finding against current code. Fix only still-valid issues, skip the
|
|
131
|
+
rest with a brief reason, keep changes minimal, and validate.
|
|
132
|
+
|
|
133
|
+
In `@app/v2Components/FormBuilder/Functional/FormBuilderShell.js` around lines 348
|
|
134
|
+
- 350, The PropTypes definitions for selectedOfferDetails and
|
|
135
|
+
waitEventContextTags in the FormBuilderShell propTypes are typed incorrectly
|
|
136
|
+
compared to how they are consumed in the TagList component. Change
|
|
137
|
+
selectedOfferDetails from PropTypes.object to PropTypes.array, and change
|
|
138
|
+
waitEventContextTags from PropTypes.array to PropTypes.object. Apply the same
|
|
139
|
+
type corrections to any other instances of these props mentioned in the file
|
|
140
|
+
(lines 361-364).
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@coderabbitai
|
|
144
|
+
coderabbitai Bot
|
|
145
|
+
2 days ago
|
|
146
|
+
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
|
|
147
|
+
|
|
148
|
+
Characterization can unintentionally run Functional instead of Classic.
|
|
149
|
+
|
|
150
|
+
Line 22 imports the entry-gated FormBuilder (../index), and that gate can route SMS to Functional based on feature access. For a Classic golden suite, this makes the target non-deterministic and can hide Classic regressions.
|
|
151
|
+
|
|
152
|
+
Suggested fix
|
|
153
|
+
-import FormBuilder from '../index';
|
|
154
|
+
+import ClassicFormBuilder from '../Classic';
|
|
155
|
+
@@
|
|
156
|
+
-const ComponentToRender = injectIntl(FormBuilder);
|
|
157
|
+
+const ComponentToRender = injectIntl(ClassicFormBuilder);
|
|
158
|
+
Also applies to: 47-47
|
|
159
|
+
|
|
160
|
+
🤖 Prompt for AI Agents
|
|
161
|
+
Verify each finding against current code. Fix only still-valid issues, skip the
|
|
162
|
+
rest with a brief reason, keep changes minimal, and validate.
|
|
163
|
+
|
|
164
|
+
In `@app/v2Components/FormBuilder/tests/sms.characterization.test.js` around lines
|
|
165
|
+
22 - 23, The imports on line 22 and line 47 use the entry-gated FormBuilder from
|
|
166
|
+
../index which can route SMS to Functional based on feature access. For the
|
|
167
|
+
Classic characterization test to be deterministic and properly test Classic
|
|
168
|
+
behavior, import the Classic FormBuilder implementation directly rather than
|
|
169
|
+
going through the gated entry point. Replace the imports from ../index with
|
|
170
|
+
direct imports to the Classic FormBuilder implementation to ensure the test
|
|
171
|
+
always uses the Classic version.
|
|
@@ -16,6 +16,7 @@ import { isTraiDLTEnable } from '../../utils/common';
|
|
|
16
16
|
import { formatString } from '../../utils/Formatter';
|
|
17
17
|
import {
|
|
18
18
|
CAP_SPACE_12,
|
|
19
|
+
ICON_SIZE_M,
|
|
19
20
|
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
20
21
|
import { WHATSAPP_HELP_DOC_LINK, JOURNEY } from './constants';
|
|
21
22
|
|
|
@@ -23,8 +24,14 @@ const { CapLabelInline } = CapLabel;
|
|
|
23
24
|
const StyledLabel = styled(CapLabelInline)`
|
|
24
25
|
margin-right: ${CAP_SPACE_12};
|
|
25
26
|
`;
|
|
27
|
+
// CapIcons.BackIcon renders an <svg width="1em" height="1em">, so its size follows
|
|
28
|
+
// the inherited font-size. Pin it to the medium icon size (24px) and center it so
|
|
29
|
+
// the back arrow matches the header title instead of collapsing to a tiny glyph.
|
|
26
30
|
const PrefixWrapper = styled.div`
|
|
27
31
|
margin-right: 16px;
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
font-size: ${ICON_SIZE_M};
|
|
28
35
|
`;
|
|
29
36
|
const renderData = (type, value, channel) => (
|
|
30
37
|
<StyledLabel className={channel?.toLowerCase() === ZALO ? 'zalo-template-name-spacing' : ''} type={type}>
|
|
@@ -39,6 +39,7 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
39
39
|
currentTab: 1,
|
|
40
40
|
isEdit: false,
|
|
41
41
|
loading: false,
|
|
42
|
+
isSaving: false,
|
|
42
43
|
isFormValid: true,
|
|
43
44
|
injectedTags: {},
|
|
44
45
|
checkValidation: false,
|
|
@@ -570,26 +571,33 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
570
571
|
},
|
|
571
572
|
};
|
|
572
573
|
this.isTagLoaded = false;
|
|
574
|
+
this.isSubmitting = false;
|
|
573
575
|
}
|
|
574
576
|
|
|
575
577
|
componentWillMount() {
|
|
578
|
+
// NOTE: API dispatches must NOT live here. componentWillMount runs in React's
|
|
579
|
+
// render phase, which React 18 (createRoot/concurrent) may execute more than
|
|
580
|
+
// once per commit — causing every dispatched request to fire twice. All
|
|
581
|
+
// side-effects are in componentDidMount (commit phase, runs exactly once).
|
|
576
582
|
if (this.props.params.id) {
|
|
577
|
-
this.props.actions.getTemplateDetails(this.props.params.id, 'ebill');
|
|
578
583
|
this.setState({isEdit: true});
|
|
579
584
|
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
componentDidMount() {
|
|
588
|
+
if (this.props.params.id) {
|
|
589
|
+
this.props.actions.getTemplateDetails(this.props.params.id, 'ebill');
|
|
590
|
+
}
|
|
580
591
|
|
|
581
592
|
if (!_.isEmpty(this.props.Templates.edmTemplate) && this.props.Templates.edmTemplate.versions.base.is_drag_drop) {
|
|
582
593
|
this.props.actions.getCmsData('edm', this.props.Templates.edmTemplate.versions.base.drag_drop_id);
|
|
583
594
|
}
|
|
584
|
-
}
|
|
585
595
|
|
|
586
|
-
componentDidMount() {
|
|
587
596
|
const query = {
|
|
588
597
|
layout: 'EBILL',
|
|
589
598
|
type: 'LAYOUT',
|
|
590
599
|
};
|
|
591
600
|
this.props.globalActions.fetchSchemaForEntity(query);
|
|
592
|
-
|
|
593
601
|
}
|
|
594
602
|
|
|
595
603
|
componentWillReceiveProps(nextProps) {
|
|
@@ -609,7 +617,7 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
609
617
|
this.setEditState(nextProps.Ebill.templateDetails);
|
|
610
618
|
}
|
|
611
619
|
|
|
612
|
-
if (nextProps.Ebill.createResponse && nextProps.Ebill.createResponse.templateId) {
|
|
620
|
+
if (nextProps.Ebill.createResponse && nextProps.Ebill.createResponse.templateId && !_.isEqual(nextProps.Ebill.createResponse, this.props.Ebill.createResponse)) {
|
|
613
621
|
let message;
|
|
614
622
|
if (this.state.isEdit) {
|
|
615
623
|
message = getMessageObject('success', this.props.intl.formatMessage(messages.ebillEditSuccess), true);
|
|
@@ -618,6 +626,8 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
618
626
|
}
|
|
619
627
|
this.props.globalActions.addMessageToQueue(message);
|
|
620
628
|
this.props.actions.clearCRUDResponse();
|
|
629
|
+
this.isSubmitting = false;
|
|
630
|
+
this.setState({ isSaving: false });
|
|
621
631
|
const module = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
622
632
|
const type = this.props.location.query.type;
|
|
623
633
|
this.props.router.push({
|
|
@@ -629,6 +639,8 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
629
639
|
if (nextProps.Ebill.createTemplateError && !_.isEqual(nextProps.Ebill.createTemplateError, this.props.Ebill.createTemplateError)) {
|
|
630
640
|
const message = getMessageObject('error', (nextProps.Ebill.createTemplateErrorMessage && nextProps.Ebill.createTemplateErrorMessage !== '') ? nextProps.Ebill.createTemplateErrorMessage : this.props.intl.formatMessage(messages.somethingWentWrong), true);
|
|
631
641
|
this.props.globalActions.addMessageToQueue(message);
|
|
642
|
+
this.isSubmitting = false;
|
|
643
|
+
this.setState({ isSaving: false });
|
|
632
644
|
}
|
|
633
645
|
|
|
634
646
|
|
|
@@ -773,11 +785,23 @@ export class Ebill extends React.Component { // eslint-disable-line react/prefer
|
|
|
773
785
|
saveFormData = (formData) => {
|
|
774
786
|
//Logic to save in db etc
|
|
775
787
|
// const isValidated = this.validateFormData(formData);
|
|
776
|
-
//
|
|
788
|
+
//
|
|
777
789
|
// if (!isValidated) {
|
|
778
790
|
// return;
|
|
779
791
|
// }
|
|
780
|
-
|
|
792
|
+
|
|
793
|
+
// Guard against duplicate submissions. Uses a synchronous instance flag
|
|
794
|
+
// (NOT React state) because setState is async — two onSubmit invocations in
|
|
795
|
+
// the same tick would both read a stale `false` and each dispatch a
|
|
796
|
+
// createTemplate, firing POST /templates/EBILL twice. The instance flag
|
|
797
|
+
// flips synchronously, so the second call is dropped immediately.
|
|
798
|
+
// Reset once the create/update response or error is received in componentWillReceiveProps.
|
|
799
|
+
if (this.isSubmitting) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
this.isSubmitting = true;
|
|
803
|
+
this.setState({ isSaving: true });
|
|
804
|
+
|
|
781
805
|
const obj = {};
|
|
782
806
|
obj.versions = {
|
|
783
807
|
base: {},
|
|
@@ -646,7 +646,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
|
|
649
|
-
if (selectedChannel === "ebill" && nextProps.CreateEbill && nextProps.CreateEbill.createResponse && nextProps.CreateEbill.createResponse.templateId && nextProps.CreateEbill.createResponse.templateId !== '') {
|
|
649
|
+
if (selectedChannel === "ebill" && nextProps.CreateEbill && nextProps.CreateEbill.createResponse && nextProps.CreateEbill.createResponse.templateId && nextProps.CreateEbill.createResponse.templateId !== '' && !isEqual(nextProps.CreateEbill.createResponse, this.props.CreateEbill && this.props.CreateEbill.createResponse)) {
|
|
650
650
|
const message = `${this.state.channel} ${this.props.intl.formatMessage(messages.templateDuplicateSuccess)}`;
|
|
651
651
|
CapNotification.success({key: 'duplicateSucess', message});
|
|
652
652
|
this.getAllTemplates({params, resetPage: true});
|