@capillarytech/creatives-library 8.0.114 → 8.0.115

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 (42) hide show
  1. package/package.json +1 -1
  2. package/utils/commonUtils.js +353 -3
  3. package/utils/tagValidations.js +22 -5
  4. package/utils/tests/commonUtil.test.js +563 -169
  5. package/utils/tests/tagValidations.test.js +129 -3
  6. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.js +125 -0
  7. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.test.js +147 -0
  8. package/v2Components/ErrorInfoNote/index.js +114 -47
  9. package/v2Components/ErrorInfoNote/messages.js +25 -0
  10. package/v2Components/ErrorInfoNote/style.scss +14 -1
  11. package/v2Components/ErrorInfoNote/utils.js +50 -0
  12. package/v2Components/ErrorInfoNote/utils.test.js +189 -0
  13. package/v2Components/FormBuilder/index.js +203 -127
  14. package/v2Components/FormBuilder/messages.js +1 -1
  15. package/v2Containers/Cap/reducer.js +4 -4
  16. package/v2Containers/Cap/sagas.js +9 -3
  17. package/v2Containers/Cap/tests/saga.test.js +12 -0
  18. package/v2Containers/CreativesContainer/SlideBoxContent.js +26 -3
  19. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  20. package/v2Containers/CreativesContainer/constants.js +4 -1
  21. package/v2Containers/CreativesContainer/index.js +46 -19
  22. package/v2Containers/CreativesContainer/messages.js +4 -0
  23. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -3
  24. package/v2Containers/CreativesContainer/tests/index.test.js +1 -0
  25. package/v2Containers/Ebill/index.js +3 -3
  26. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +1 -1
  27. package/v2Containers/InApp/index.js +126 -50
  28. package/v2Containers/InApp/tests/index.test.js +1 -1
  29. package/v2Containers/InApp/tests/sagas.test.js +1 -1
  30. package/v2Containers/InApp/tests/utils.test.js +85 -0
  31. package/v2Containers/InApp/utils.js +57 -0
  32. package/v2Containers/InApp/utils.test.js +70 -0
  33. package/v2Containers/MobilePush/Create/index.js +24 -20
  34. package/v2Containers/MobilePush/Edit/index.js +6 -2
  35. package/v2Containers/MobilepushWrapper/index.js +2 -0
  36. package/v2Containers/Sms/Create/index.js +1 -0
  37. package/v2Containers/Sms/Edit/index.js +2 -0
  38. package/v2Containers/SmsTrai/Edit/index.js +49 -10
  39. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +112 -36
  40. package/v2Containers/SmsTrai/Edit/tests/index.test.js +1 -3
  41. package/v2Containers/SmsWrapper/index.js +5 -1
  42. package/v2Containers/Templates/sagas.js +1 -1
@@ -1,6 +1,15 @@
1
- import React from 'react';
2
1
  import '@testing-library/jest-dom';
3
- import { checkSupport, extractNames, getTagMapValue,getForwardedMapValues, preprocessHtml, validateIfTagClosed,validateTags, skipTags } from '../tagValidations';
2
+ import {
3
+ checkSupport,
4
+ extractNames,
5
+ getTagMapValue,
6
+ getForwardedMapValues,
7
+ preprocessHtml,
8
+ validateIfTagClosed,
9
+ validateTags,
10
+ skipTags,
11
+ isInsideLiquidBlock,
12
+ } from '../tagValidations';
4
13
  import { eventContextTags } from '../../v2Containers/TagList/tests/mockdata';
5
14
 
6
15
  describe("check if curly brackets are balanced", () => {
@@ -170,6 +179,45 @@ describe("validateTags", () => {
170
179
  expect(result.unsupportedTags).toEqual([]);
171
180
  expect(result.isBraceError).toEqual(true);
172
181
  });
182
+
183
+ it("should remove 'unsubscribe' from missingTags if skipTags logic is triggered", () => {
184
+ const tagsParam = [
185
+ {
186
+ definition: {
187
+ supportedModules: [{ context: "DEFAULT", mandatory: true }],
188
+ value: "unsubscribe",
189
+ },
190
+ },
191
+ ];
192
+ // Content does not include {{unsubscribe}}, so it would be missing
193
+ const contentMissing = "Hello {{tag1}}";
194
+ const injectedTagsParams = [];
195
+ const location = { query: { module: "DEFAULT" } };
196
+ const tagModule = null;
197
+
198
+ // First, verify unsubscribe is missing if not present
199
+ const resultMissing = validateTags({
200
+ content: contentMissing,
201
+ tagsParam,
202
+ injectedTagsParams,
203
+ location,
204
+ tagModule,
205
+ });
206
+ expect(resultMissing.missingTags).toContain("unsubscribe");
207
+
208
+ // Now, content includes a tag that triggers skipTags logic for unsubscribe
209
+ // e.g., {{unsubscribe(#123456)}} matches the skipTags regex
210
+ const contentWithSkippedUnsubscribe = "Hello {{unsubscribe(#123456)}}";
211
+ const resultSkipped = validateTags({
212
+ content: contentWithSkippedUnsubscribe,
213
+ tagsParam,
214
+ injectedTagsParams,
215
+ location,
216
+ tagModule,
217
+ });
218
+ expect(resultSkipped.missingTags).not.toContain("unsubscribe");
219
+ expect(resultSkipped.valid).toBe(true);
220
+ });
173
221
  });
174
222
 
175
223
  describe("extractNames", () => {
@@ -806,4 +854,82 @@ describe('getForwardedMapValues', () => {
806
854
 
807
855
  expect(input).toEqual(inputCopy);
808
856
  });
809
- });
857
+ });
858
+
859
+ describe('isInsideLiquidBlock', () => {
860
+ it('returns true for index inside a single block', () => {
861
+ const content = 'Hello {% assign foo = 1 %} World';
862
+ // Index of 'a' in 'assign' inside the block
863
+ const tagIndex = content.indexOf('assign');
864
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
865
+ });
866
+
867
+ it('returns false for index outside any block', () => {
868
+ const content = 'Hello {% assign foo = 1 %} World';
869
+ // Index of 'H' in 'Hello'
870
+ expect(isInsideLiquidBlock(content, 0)).toBe(false);
871
+ // Index of 'W' in 'World'
872
+ expect(isInsideLiquidBlock(content, content.indexOf('World'))).toBe(false);
873
+ });
874
+
875
+ it('returns true for index at the start of a block', () => {
876
+ const content = 'Hello {% assign foo = 1 %} World';
877
+ // Index of '{' in '{%'
878
+ const tagIndex = content.indexOf('{%');
879
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
880
+ });
881
+
882
+ it('returns false for index at the end of a block (exclusive)', () => {
883
+ const content = 'Hello {% assign foo = 1 %} World';
884
+ // Index just after the closing '%}'
885
+ const blockEnd = content.indexOf('%}') + 2;
886
+ expect(isInsideLiquidBlock(content, blockEnd)).toBe(false);
887
+ });
888
+
889
+ it('returns true for index inside the second of multiple blocks', () => {
890
+ const content = 'A {% first %} B {% second %} C';
891
+ const tagIndex = content.indexOf('second');
892
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
893
+ });
894
+
895
+ it('returns false for index between blocks', () => {
896
+ const content = 'A {% first %} B {% second %} C';
897
+ // Index of 'B' (between blocks)
898
+ const tagIndex = content.indexOf('B');
899
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(false);
900
+ });
901
+
902
+ it('returns false for empty string', () => {
903
+ expect(isInsideLiquidBlock('', 0)).toBe(false);
904
+ });
905
+
906
+ it('returns false if there are no blocks', () => {
907
+ const content = 'Just some text with no blocks';
908
+ expect(isInsideLiquidBlock(content, 5)).toBe(false);
909
+ });
910
+
911
+ it('returns false for negative index', () => {
912
+ const content = 'Hello {% assign foo = 1 %} World';
913
+ expect(isInsideLiquidBlock(content, -1)).toBe(false);
914
+ });
915
+
916
+ it('returns false for index beyond string length', () => {
917
+ const content = 'Hello {% assign foo = 1 %} World';
918
+ expect(isInsideLiquidBlock(content, 100)).toBe(false);
919
+ });
920
+
921
+ it('works for nested-like blocks (not truly nested)', () => {
922
+ const content = 'A {% outer {% inner %} outer %} B';
923
+ // Index of 'inner' (should be inside the first block)
924
+ const tagIndex = content.indexOf('inner');
925
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
926
+ });
927
+
928
+ it('returns true for index at last char inside block', () => {
929
+ const content = 'A {% foo %} B';
930
+ // Index of last char inside block (just before %})
931
+ const blockStart = content.indexOf('{%');
932
+ const blockEnd = content.indexOf('%}');
933
+ expect(isInsideLiquidBlock(content, blockEnd - 1)).toBe(true);
934
+ });
935
+ });
@@ -0,0 +1,125 @@
1
+ // app/v2Components/ErrorInfoNote/ErrorTypeRenderer.js
2
+ import React from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import CapDivider from "@capillarytech/cap-ui-library/CapDivider";
5
+ import { getLiquidProps, hasPlatformErrors } from './utils';
6
+
7
+ /**
8
+ * Renders error sections for either generic (non-platform) or platform-specific (Android/iOS) errors.
9
+ * - If `genericErrors` is provided, renders standard and liquid errors (if present) without platform labels.
10
+ * - If `androidErrors`/`iosErrors` are provided, renders merged errors for each platform with platform labels and dividers.
11
+ */
12
+ const ErrorTypeRenderer = ({
13
+ genericErrors,
14
+ androidErrors,
15
+ iosErrors,
16
+ ErrorSectionComponent,
17
+ }) => {
18
+ // Render generic (non-platform) errors
19
+ if (genericErrors) {
20
+ const hasStandard = genericErrors?.standard?.errorsToShow?.length > 0;
21
+ const hasLiquid = genericErrors?.liquid?.errorsToShow?.length > 0;
22
+ if (!hasStandard && !hasLiquid) return null;
23
+
24
+ const liquidGenericProps = {
25
+ title: genericErrors?.liquid?.title,
26
+ errors: genericErrors?.liquid?.errorsToShow,
27
+ platformLabel: null,
28
+ };
29
+
30
+ return (
31
+ <div className="error-container">
32
+ {hasStandard && (
33
+ <ErrorSectionComponent
34
+ title={genericErrors?.standard?.title}
35
+ errors={genericErrors?.standard?.errorsToShow}
36
+ liquidError={false}
37
+ platformLabel={null}
38
+ />
39
+ )}
40
+ {hasStandard && hasLiquid && <CapDivider className="liquid-divider" />}
41
+ {hasLiquid && (
42
+ <ErrorSectionComponent
43
+ {...liquidGenericProps}
44
+ liquidError
45
+ />
46
+ )}
47
+ </div>
48
+ );
49
+ }
50
+
51
+ // Render platform-specific errors (Android/iOS)
52
+ const hasAndroid = hasPlatformErrors(androidErrors);
53
+ const hasIos = hasPlatformErrors(iosErrors);
54
+
55
+ if (!hasAndroid && !hasIos) return null;
56
+
57
+ const showPlatformLabels = !!androidErrors?.standard?.platformLabel || !!iosErrors?.standard?.platformLabel;
58
+ const showTitle = hasAndroid && hasIos && showPlatformLabels;
59
+ const liquidAndroidProps = getLiquidProps(androidErrors, true, showTitle, showPlatformLabels);
60
+ const liquidIosProps = getLiquidProps(iosErrors, false, showTitle, showPlatformLabels);
61
+
62
+ return (
63
+ <div className="error-container">
64
+ {hasAndroid && (
65
+ <ErrorSectionComponent
66
+ {...liquidAndroidProps}
67
+ />
68
+ )}
69
+ {hasAndroid && hasIos && showPlatformLabels && (
70
+ <CapDivider className="platform-divider" />
71
+ )}
72
+ {hasIos && (
73
+ <ErrorSectionComponent
74
+ {...liquidIosProps}
75
+ />
76
+ )}
77
+ </div>
78
+ );
79
+ };
80
+
81
+ ErrorTypeRenderer.propTypes = {
82
+ genericErrors: PropTypes.shape({
83
+ standard: PropTypes.shape({
84
+ errorsToShow: PropTypes.array.isRequired,
85
+ title: PropTypes.node.isRequired,
86
+ }),
87
+ liquid: PropTypes.shape({
88
+ errorsToShow: PropTypes.array.isRequired,
89
+ title: PropTypes.node.isRequired,
90
+ }),
91
+ }),
92
+ androidErrors: PropTypes.shape({
93
+ standard: PropTypes.shape({
94
+ errorsToShow: PropTypes.array.isRequired,
95
+ title: PropTypes.node.isRequired,
96
+ platformLabel: PropTypes.string,
97
+ }),
98
+ liquid: PropTypes.shape({
99
+ errorsToShow: PropTypes.array.isRequired,
100
+ title: PropTypes.node.isRequired,
101
+ platformLabel: PropTypes.string,
102
+ }),
103
+ }),
104
+ iosErrors: PropTypes.shape({
105
+ standard: PropTypes.shape({
106
+ errorsToShow: PropTypes.array.isRequired,
107
+ title: PropTypes.node.isRequired,
108
+ platformLabel: PropTypes.string,
109
+ }),
110
+ liquid: PropTypes.shape({
111
+ errorsToShow: PropTypes.array.isRequired,
112
+ title: PropTypes.node.isRequired,
113
+ platformLabel: PropTypes.string,
114
+ }),
115
+ }),
116
+ ErrorSectionComponent: PropTypes.elementType.isRequired,
117
+ };
118
+
119
+ ErrorTypeRenderer.defaultProps = {
120
+ genericErrors: null,
121
+ androidErrors: null,
122
+ iosErrors: null,
123
+ };
124
+
125
+ export default ErrorTypeRenderer;
@@ -0,0 +1,147 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import ErrorTypeRenderer from './ErrorTypeRenderer';
4
+ import { ANDROID, IOS } from '../../v2Containers/CreativesContainer/constants';
5
+
6
+ describe('ErrorTypeRenderer', () => { const ErrorSectionComponent = jest.fn(({
7
+ title,
8
+ errors,
9
+ liquidError,
10
+ platformLabel,
11
+ }) => (
12
+ <div data-testid="error-section">
13
+ <span>{title}</span>
14
+ <span>{errors && errors.join(',')}</span>
15
+ <span>{liquidError ? 'liquid' : 'standard'}</span>
16
+ <span>{platformLabel}</span>
17
+ </div>
18
+ ));
19
+
20
+ afterEach(() => {
21
+ ErrorSectionComponent.mockClear();
22
+ });
23
+
24
+ it('renders nothing if all errors are empty', () => {
25
+ const { container } = render(
26
+ <ErrorTypeRenderer
27
+ genericErrors={{ standard: { errorsToShow: [], title: 'Standard' }, liquid: { errorsToShow: [], title: 'Liquid' } }}
28
+ ErrorSectionComponent={ErrorSectionComponent}
29
+ />
30
+ );
31
+ expect(container.firstChild).toBeNull();
32
+ expect(ErrorSectionComponent).not.toHaveBeenCalled();
33
+ });
34
+
35
+ it('renders generic standard errors only', () => {
36
+ render(
37
+ <ErrorTypeRenderer
38
+ genericErrors={{ standard: { errorsToShow: ['err1', 'err2'], title: 'Standard' }, liquid: { errorsToShow: [], title: 'Liquid' } }}
39
+ ErrorSectionComponent={ErrorSectionComponent}
40
+ />
41
+ );
42
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
43
+ expect.objectContaining({
44
+ title: 'Standard',
45
+ errors: ['err1', 'err2'],
46
+ liquidError: false,
47
+ platformLabel: null,
48
+ }),
49
+ expect.anything()
50
+ );
51
+ });
52
+
53
+ it('renders generic liquid errors only', () => {
54
+ render(
55
+ <ErrorTypeRenderer
56
+ genericErrors={{ standard: { errorsToShow: [], title: 'Standard' }, liquid: { errorsToShow: ['l1'], title: 'Liquid' } }}
57
+ ErrorSectionComponent={ErrorSectionComponent}
58
+ />
59
+ );
60
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
61
+ expect.objectContaining({
62
+ title: 'Liquid',
63
+ errors: ['l1'],
64
+ liquidError: true,
65
+ platformLabel: null,
66
+ }),
67
+ expect.anything()
68
+ );
69
+ });
70
+
71
+ it('renders both generic standard and liquid errors', () => {
72
+ render(
73
+ <ErrorTypeRenderer
74
+ genericErrors={{ standard: { errorsToShow: ['err1'], title: 'Standard' }, liquid: { errorsToShow: ['l1'], title: 'Liquid' } }}
75
+ ErrorSectionComponent={ErrorSectionComponent}
76
+ />
77
+ );
78
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
79
+ expect.objectContaining({
80
+ title: 'Standard',
81
+ errors: ['err1'],
82
+ liquidError: false,
83
+ platformLabel: null,
84
+ }),
85
+ expect.anything()
86
+ );
87
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
88
+ expect.objectContaining({
89
+ title: 'Liquid',
90
+ errors: ['l1'],
91
+ liquidError: true,
92
+ platformLabel: null,
93
+ }),
94
+ expect.anything()
95
+ );
96
+ });
97
+
98
+ it('renders merged android errors with platform label', () => {
99
+ render(
100
+ <ErrorTypeRenderer
101
+ androidErrors={{
102
+ standard: { errorsToShow: ['a1'], title: 'Android Standard', platformLabel: ANDROID },
103
+ liquid: { errorsToShow: ['al1'], title: 'Android Liquid', platformLabel: ANDROID },
104
+ }}
105
+ iosErrors={{
106
+ standard: { errorsToShow: [], title: 'iOS Standard', platformLabel: IOS },
107
+ liquid: { errorsToShow: [], title: 'iOS Liquid', platformLabel: IOS },
108
+ }}
109
+ ErrorSectionComponent={ErrorSectionComponent}
110
+ />
111
+ );
112
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
113
+ expect.objectContaining({
114
+ title: false,
115
+ errors: ['a1', 'al1'],
116
+ liquidError: true,
117
+ platformLabel: ANDROID,
118
+ }),
119
+ expect.anything()
120
+ );
121
+ });
122
+
123
+ it('renders merged ios errors with platform label', () => {
124
+ render(
125
+ <ErrorTypeRenderer
126
+ androidErrors={{
127
+ standard: { errorsToShow: [], title: 'Android Standard', platformLabel: ANDROID },
128
+ liquid: { errorsToShow: [], title: 'Android Liquid', platformLabel: ANDROID },
129
+ }}
130
+ iosErrors={{
131
+ standard: { errorsToShow: ['i1'], title: 'iOS Standard', platformLabel: IOS },
132
+ liquid: { errorsToShow: ['il1'], title: 'iOS Liquid', platformLabel: IOS },
133
+ }}
134
+ ErrorSectionComponent={ErrorSectionComponent}
135
+ />
136
+ );
137
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
138
+ expect.objectContaining({
139
+ title: 'iOS Liquid',
140
+ errors: ['i1', 'il1'],
141
+ liquidError: true,
142
+ platformLabel: IOS,
143
+ }),
144
+ expect.anything()
145
+ );
146
+ });
147
+ });
@@ -9,19 +9,25 @@ import {
9
9
  FormattedMessage,
10
10
  FormattedNumber,
11
11
  injectIntl,
12
- intlShape,
13
12
  } from "react-intl";
14
13
  import "./style.scss";
15
14
  import messages from "./messages";
15
+ import { processErrors } from "./utils";
16
+ import ErrorTypeRenderer from "./ErrorTypeRenderer";
17
+ import { ANDROID, GENERIC, IOS } from "../../v2Containers/CreativesContainer/constants";
16
18
 
17
- export const ErrorInfoNote = (props) => {
18
-
19
- const { LIQUID_ERROR_MSG = [], STANDARD_ERROR_MSG = [] } = props?.errorMessages || {};
20
-
21
- const ErrorSection = ({ title = "", errors = [], liquidError = false }) => (
22
- <>
19
+ const ErrorSection = ({
20
+ title,
21
+ errors,
22
+ liquidError,
23
+ platformLabel,
24
+ }) => (
25
+ <>
26
+ {title && (
23
27
  <CapRow
24
- className={`error-header ${!liquidError ? "standard-error-header": ""}`}
28
+ className={`error-header ${
29
+ !liquidError ? "standard-error-header" : ""
30
+ }`}
25
31
  >
26
32
  <>
27
33
  <CapIcon size="s" type="warning" className="warning-icon" />
@@ -32,8 +38,7 @@ export const ErrorInfoNote = (props) => {
32
38
  <CapButton
33
39
  type="flat"
34
40
  className="add-btn"
35
- onClick={() =>
36
- window.open(`https://docs.capillarytech.com/docs/liquid-language-in-messages`, "_blank")
41
+ onClick={() => window.open("https://docs.capillarytech.com/docs/liquid-language-in-messages", "_blank")
37
42
  }
38
43
  >
39
44
  <FormattedMessage {...messages.liquidDoc} />
@@ -42,48 +47,110 @@ export const ErrorInfoNote = (props) => {
42
47
  )}
43
48
  </>
44
49
  </CapRow>
45
- <CapList
46
- className="error-list"
47
- size="small"
48
- dataSource={errors}
49
- renderItem={(error, index) => (
50
- <CapList.Item>
51
- <CapLabel type="label2" className="cap-list-v2-error-item">
52
- <CapLabel type="label2"><FormattedNumber value={index + 1} />.</CapLabel>
53
- <CapLabel type="label2">{error}</CapLabel>
50
+ )}
51
+ {platformLabel && (
52
+ <CapRow className="error-header-sub-title">
53
+ <CapLabel type="label2">{platformLabel}</CapLabel>
54
+ </CapRow>
55
+ )}
56
+ <CapList
57
+ className="error-list"
58
+ size="small"
59
+ dataSource={errors}
60
+ renderItem={(error, index) => (
61
+ <CapList.Item>
62
+ <CapLabel type="label2" className="cap-list-v2-error-item">
63
+ <CapLabel type="label2">
64
+ <FormattedNumber value={index + 1} />.
54
65
  </CapLabel>
55
- </CapList.Item>
56
- )}
66
+ <CapLabel type="label2">{error}</CapLabel>
67
+ </CapLabel>
68
+ </CapList.Item>
69
+ )}
70
+ />
71
+ </>
72
+ );
73
+
74
+ ErrorSection.propTypes = {
75
+ title: PropTypes.node.isRequired,
76
+ errors: PropTypes.array,
77
+ liquidError: PropTypes.bool,
78
+ platformLabel: PropTypes.string,
79
+ };
80
+
81
+ ErrorSection.defaultProps = {
82
+ errors: [],
83
+ liquidError: false,
84
+ platformLabel: null,
85
+ };
86
+
87
+ export const ErrorInfoNote = (props) => {
88
+ const { errorMessages } = props;
89
+
90
+ const {
91
+ LIQUID_ERROR_MSG: rawLiquidErrors = [],
92
+ STANDARD_ERROR_MSG: rawStandardErrors = [],
93
+ } = errorMessages || {};
94
+
95
+ // Detect if platform-specific (ANDROID/IOS) or GENERIC
96
+ const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
97
+ const isNotArray = !Array.isArray(rawStandardErrors);
98
+ const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawStandardErrors);
99
+
100
+ if (hasPlatformKeys) {
101
+ // Platform-specific
102
+ const androidErrors = {
103
+ standard: processErrors(rawStandardErrors, 'standard', ANDROID, messages),
104
+ liquid: processErrors(rawLiquidErrors, 'liquid', ANDROID, messages),
105
+ };
106
+ const iosErrors = {
107
+ standard: processErrors(rawStandardErrors, 'standard', IOS, messages),
108
+ liquid: processErrors(rawLiquidErrors, 'liquid', IOS, messages),
109
+ };
110
+ return (
111
+ <ErrorTypeRenderer
112
+ androidErrors={androidErrors}
113
+ iosErrors={iosErrors}
114
+ ErrorSectionComponent={ErrorSection}
57
115
  />
58
- </>
59
- );
60
- const liquidErrors = LIQUID_ERROR_MSG?.length;
116
+ );
117
+ }
118
+ // GENERIC (not platform-specific)
119
+ const genericStandard = processErrors(rawStandardErrors, 'standard', null, messages);
120
+ const genericLiquid = processErrors(rawLiquidErrors, 'liquid', null, messages);
61
121
  return (
62
- <div className="error-container">
63
- {STANDARD_ERROR_MSG?.length > 0 && (
64
- <>
65
- <ErrorSection
66
- title={
67
- <FormattedMessage {...messages.standardErrorHeader} />
68
- }
69
- errors={STANDARD_ERROR_MSG}
70
- />
71
- </>
72
- )}
73
- {liquidErrors > 0 && (
74
- <ErrorSection
75
- title={<FormattedMessage {...messages.dynamicErrorHeader} />}
76
- errors={LIQUID_ERROR_MSG}
77
- liquidError={true}
78
- />
79
- )}
80
- </div>
122
+ <ErrorTypeRenderer
123
+ genericErrors={{ standard: genericStandard, liquid: genericLiquid }}
124
+ ErrorSectionComponent={ErrorSection}
125
+ />
81
126
  );
82
127
  };
83
- ErrorInfoNote.propTypes = {
84
- errorMessage: PropTypes.string,
85
- errorType: PropTypes.string,
86
- intl: intlShape.isRequired,
128
+
129
+ ErrorInfoNote.defaultProps = {
130
+ errorMessages: {
131
+ LIQUID_ERROR_MSG: [],
132
+ STANDARD_ERROR_MSG: [],
133
+ },
87
134
  };
88
135
 
136
+ ErrorInfoNote.propTypes = {
137
+ errorMessages: PropTypes.shape({
138
+ LIQUID_ERROR_MSG: PropTypes.oneOfType([
139
+ PropTypes.array,
140
+ PropTypes.shape({
141
+ ANDROID: PropTypes.array,
142
+ IOS: PropTypes.array,
143
+ GENERIC: PropTypes.array,
144
+ }),
145
+ ]),
146
+ STANDARD_ERROR_MSG: PropTypes.oneOfType([
147
+ PropTypes.array,
148
+ PropTypes.shape({
149
+ ANDROID: PropTypes.array,
150
+ IOS: PropTypes.array,
151
+ GENERIC: PropTypes.array,
152
+ }),
153
+ ]),
154
+ }),
155
+ };
89
156
  export default injectIntl(ErrorInfoNote);
@@ -26,4 +26,29 @@ export default defineMessages({
26
26
  defaultMessage:
27
27
  "Aira can make mistakes. Please verify the suggestions before applying them"
28
28
  },
29
+ liquidDocLink: {
30
+ id: `${scope}.liquidDocLink`,
31
+ defaultMessage:
32
+ "https://docs.capillarytech.com/docs/liquid-language-in-messages"
33
+ },
34
+ androidDynamicErrorHeader: {
35
+ id: `${scope}.androidDynamicErrorHeader`,
36
+ defaultMessage:
37
+ "Errors found in Android Dynamic Tags"
38
+ },
39
+ iosDynamicErrorHeader: {
40
+ id: `${scope}.iosDynamicErrorHeader`,
41
+ defaultMessage:
42
+ "Errors found in iOS Dynamic Tags"
43
+ },
44
+ androidStandardErrorHeader: {
45
+ id: `${scope}.androidStandardErrorHeader`,
46
+ defaultMessage:
47
+ "Errors found in Android Standard Tags"
48
+ },
49
+ iosStandardErrorHeader: {
50
+ id: `${scope}.iosStandardErrorHeader`,
51
+ defaultMessage:
52
+ "Errors found in iOS Standard Tags"
53
+ },
29
54
  });
@@ -58,15 +58,28 @@
58
58
  gap: $CAP_SPACE_04;
59
59
  }
60
60
  }
61
+
61
62
  .liquid-divider {
62
- &.ant-divider.cap-divider-v2{
63
+ &.ant-divider.cap-divider-v2 {
63
64
  background: $FONT_COLOR_04;
64
65
  }
65
66
  margin: $CAP_SPACE_08 0;
66
67
  }
67
68
 
69
+ .platform-divider {
70
+ &.ant-divider.cap-divider-v2 {
71
+ background: $FONT_COLOR_04;
72
+ margin: $CAP_SPACE_12 0;
73
+ }
74
+ }
75
+
68
76
  .aria-error-footer {
69
77
  display: flex;
70
78
  align-items: center;
71
79
  gap: $CAP_SPACE_04;
80
+ }
81
+
82
+ .error-header-sub-title {
83
+ margin-left: $CAP_SPACE_04;
84
+ font-weight: 500;
72
85
  }