@capillarytech/creatives-library 8.0.113 → 8.0.114-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.
Files changed (106) hide show
  1. package/containers/App/test/saga.test.js +1 -1
  2. package/containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  3. package/containers/Assets/Gallery/tests/actions.test.js +2 -3
  4. package/containers/Assets/Gallery/tests/reducer.test.js +7 -7
  5. package/containers/Assets/Gallery/tests/saga.test.js +9 -9
  6. package/containers/Dashboard/test/saga.test.js +1 -1
  7. package/containers/Ebill/test/saga.test.js +1 -1
  8. package/containers/Email/test/saga.test.js +33 -33
  9. package/containers/LanguageProvider/tests/actions.test.js +1 -1
  10. package/containers/LanguageProvider/tests/reducer.test.js +2 -2
  11. package/containers/LanguageProvider/tests/selectors.test.js +1 -1
  12. package/containers/Line/Create/tests/saga.test.js +2 -9
  13. package/containers/Line/Edit/test/saga.test.js +10 -14
  14. package/containers/MobilePush/Create/test/saga.test.js +2 -2
  15. package/containers/MobilePush/Edit/tests/saga.test.js +14 -14
  16. package/containers/Sms/Create/test/saga.test.js +4 -5
  17. package/containers/Sms/Edit/test/saga.test.js +1 -1
  18. package/containers/Templates/test/saga.test.js +14 -17
  19. package/containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  20. package/containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  21. package/containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  22. package/package.json +1 -1
  23. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -9
  24. package/utils/commonUtils.js +389 -3
  25. package/utils/tagValidations.js +20 -5
  26. package/utils/tests/authWrapper.test.js +2 -2
  27. package/utils/tests/cdnTransformation.test.js +16 -15
  28. package/utils/tests/commonUtil.test.js +324 -178
  29. package/v2Components/CapVideoUpload/tests/index.test.js +1 -1
  30. package/v2Components/CapWhatsappCTA/tests/index.test.js +1 -2
  31. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.js +130 -0
  32. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.test.js +146 -0
  33. package/v2Components/ErrorInfoNote/index.js +114 -46
  34. package/v2Components/ErrorInfoNote/messages.js +25 -0
  35. package/v2Components/ErrorInfoNote/style.scss +14 -1
  36. package/v2Components/ErrorInfoNote/utils.js +28 -0
  37. package/v2Components/ErrorInfoNote/utils.test.js +93 -0
  38. package/v2Components/FormBuilder/index.js +200 -127
  39. package/v2Components/FormBuilder/messages.js +1 -1
  40. package/v2Components/MarketingObjective/test/index.test.js +1 -1
  41. package/v2Components/NavigationBar/tests/saga.test.js +2 -3
  42. package/v2Containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  43. package/v2Containers/Assets/Gallery/tests/actions.test.js +2 -3
  44. package/v2Containers/Assets/Gallery/tests/reducer.test.js +7 -7
  45. package/v2Containers/Assets/Gallery/tests/saga.test.js +2 -2
  46. package/v2Containers/BeeEditor/test/saga.test.js +1 -1
  47. package/v2Containers/CallTask/test/saga.test.js +1 -1
  48. package/v2Containers/Cap/reducer.js +4 -4
  49. package/v2Containers/Cap/tests/actions.test.js +1 -1
  50. package/v2Containers/Cap/tests/reducer.test.js +11 -11
  51. package/v2Containers/Cap/tests/saga.test.js +1 -1
  52. package/v2Containers/Cap/tests/selectors.test.js +3 -3
  53. package/v2Containers/CapFacebookPreview/tests/saga.test.js +1 -1
  54. package/v2Containers/CreativesContainer/SlideBoxContent.js +23 -3
  55. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  56. package/v2Containers/CreativesContainer/constants.js +2 -1
  57. package/v2Containers/CreativesContainer/index.js +37 -10
  58. package/v2Containers/CreativesContainer/messages.js +4 -0
  59. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -3
  60. package/v2Containers/Ebill/index.js +3 -3
  61. package/v2Containers/Ebill/test/saga.test.js +1 -1
  62. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -4
  63. package/v2Containers/Email/tests/actions.test.js +1 -1
  64. package/v2Containers/Email/tests/reducer.test.js +2 -2
  65. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +1 -1
  66. package/v2Containers/FTP/test/saga.test.js +1 -1
  67. package/v2Containers/Facebook/test/saga.test.js +7 -7
  68. package/v2Containers/InApp/index.js +127 -49
  69. package/v2Containers/InApp/tests/action.test.js +7 -7
  70. package/v2Containers/InApp/tests/index.test.js +2 -4
  71. package/v2Containers/InApp/tests/reducer.test.js +175 -89
  72. package/v2Containers/InApp/tests/sagas.test.js +1 -1
  73. package/v2Containers/InApp/utils.js +37 -0
  74. package/v2Containers/LanguageProvider/tests/actions.test.js +1 -1
  75. package/v2Containers/LanguageProvider/tests/reducer.test.js +3 -3
  76. package/v2Containers/LanguageProvider/tests/saga.test.js +2 -2
  77. package/v2Containers/LanguageProvider/tests/selectors.test.js +1 -1
  78. package/v2Containers/Line/Container/ImageCarousel/tests/utils.test.js +3 -3
  79. package/v2Containers/Line/Container/Sticker/tests/utils.test.js +6 -6
  80. package/v2Containers/MobilePush/Create/index.js +24 -20
  81. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  82. package/v2Containers/MobilePush/Edit/index.js +4 -1
  83. package/v2Containers/MobilePush/Edit/test/saga.test.js +14 -14
  84. package/v2Containers/MobilepushWrapper/index.js +2 -0
  85. package/v2Containers/Rcs/tests/saga.test.js +1 -1
  86. package/v2Containers/Sms/Create/index.js +14 -2
  87. package/v2Containers/Sms/Create/test/saga.test.js +1 -1
  88. package/v2Containers/Sms/Edit/index.js +2 -0
  89. package/v2Containers/Sms/Edit/test/saga.test.js +5 -5
  90. package/v2Containers/SmsTrai/Create/tests/saga.test.js +1 -1
  91. package/v2Containers/SmsTrai/Create/tests/selectors.test.js +1 -1
  92. package/v2Containers/SmsWrapper/index.js +2 -0
  93. package/v2Containers/TagList/tests/TagList.test.js +1 -3
  94. package/v2Containers/TagList/tests/utils.test.js +3 -3
  95. package/v2Containers/Templates/tests/actions.test.js +1 -1
  96. package/v2Containers/Templates/tests/reducer.test.js +8 -8
  97. package/v2Containers/Templates/tests/sagas.test.js +2 -4
  98. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  99. package/v2Containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  100. package/v2Containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  101. package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +9 -9
  102. package/v2Containers/Whatsapp/tests/actions.test.js +3 -3
  103. package/v2Containers/Whatsapp/tests/reducer.test.js +32 -36
  104. package/v2Containers/Whatsapp/tests/utils.test.js +19 -19
  105. package/v2Containers/Zalo/tests/actions.test.js +3 -3
  106. package/v2Containers/Zalo/tests/reducer.test.js +72 -42
@@ -0,0 +1,130 @@
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
+
6
+ /**
7
+ * Renders error sections for either generic (non-platform) or platform-specific (Android/iOS) errors.
8
+ * - If `genericErrors` is provided, renders standard and liquid errors (if present) without platform labels.
9
+ * - If `androidErrors`/`iosErrors` are provided, renders merged errors for each platform with platform labels and dividers.
10
+ */
11
+ const ErrorTypeRenderer = ({
12
+ genericErrors,
13
+ androidErrors,
14
+ iosErrors,
15
+ ErrorSectionComponent,
16
+ }) => {
17
+ // Render generic (non-platform) errors
18
+ if (genericErrors) {
19
+ const hasStandard = genericErrors.standard?.errorsToShow?.length > 0;
20
+ const hasLiquid = genericErrors.liquid?.errorsToShow?.length > 0;
21
+ if (!hasStandard && !hasLiquid) return null;
22
+ return (
23
+ <div className="error-container">
24
+ {hasStandard && (
25
+ <ErrorSectionComponent
26
+ title={genericErrors.standard.title}
27
+ errors={genericErrors.standard.errorsToShow}
28
+ liquidError={false}
29
+ platformLabel={null}
30
+ />
31
+ )}
32
+ {hasStandard && hasLiquid && <CapDivider className="liquid-divider" />}
33
+ {hasLiquid && (
34
+ <ErrorSectionComponent
35
+ title={genericErrors.liquid.title}
36
+ errors={genericErrors.liquid.errorsToShow}
37
+ liquidError
38
+ platformLabel={null}
39
+ />
40
+ )}
41
+ </div>
42
+ );
43
+ }
44
+
45
+ // Render platform-specific errors (Android/iOS)
46
+ const hasAndroid = (androidErrors?.standard?.errorsToShow?.length || 0) > 0
47
+ || (androidErrors?.liquid?.errorsToShow?.length || 0) > 0;
48
+ const hasIos = (iosErrors?.standard?.errorsToShow?.length || 0) > 0
49
+ || (iosErrors?.liquid?.errorsToShow?.length || 0) > 0;
50
+
51
+ if (!hasAndroid && !hasIos) return null;
52
+
53
+ const showPlatformLabels = !!androidErrors?.standard?.platformLabel || !!iosErrors?.standard?.platformLabel;
54
+ const showTitle = hasAndroid && hasIos && showPlatformLabels;
55
+ return (
56
+ <div className="error-container">
57
+ {hasAndroid && (
58
+ <ErrorSectionComponent
59
+ title={showTitle && androidErrors.liquid?.title}
60
+ errors={[
61
+ ...(androidErrors.standard?.errorsToShow || []),
62
+ ...(androidErrors.liquid?.errorsToShow || []),
63
+ ]}
64
+ liquidError={!!androidErrors.liquid?.errorsToShow?.length}
65
+ platformLabel={showPlatformLabels ? 'ANDROID' : null}
66
+ />
67
+ )}
68
+ {hasAndroid && hasIos && showPlatformLabels && (
69
+ <CapDivider className="platform-divider" />
70
+ )}
71
+ {hasIos && (
72
+ <ErrorSectionComponent
73
+ title={!showTitle && iosErrors.liquid?.title}
74
+ errors={[
75
+ ...(iosErrors.standard?.errorsToShow || []),
76
+ ...(iosErrors.liquid?.errorsToShow || []),
77
+ ]}
78
+ liquidError={!!iosErrors.liquid?.errorsToShow?.length}
79
+ platformLabel={showPlatformLabels ? 'IOS' : null}
80
+ />
81
+ )}
82
+ </div>
83
+ );
84
+ };
85
+
86
+ ErrorTypeRenderer.propTypes = {
87
+ genericErrors: PropTypes.shape({
88
+ standard: PropTypes.shape({
89
+ errorsToShow: PropTypes.array.isRequired,
90
+ title: PropTypes.node.isRequired,
91
+ }),
92
+ liquid: PropTypes.shape({
93
+ errorsToShow: PropTypes.array.isRequired,
94
+ title: PropTypes.node.isRequired,
95
+ }),
96
+ }),
97
+ androidErrors: PropTypes.shape({
98
+ standard: PropTypes.shape({
99
+ errorsToShow: PropTypes.array.isRequired,
100
+ title: PropTypes.node.isRequired,
101
+ platformLabel: PropTypes.string,
102
+ }),
103
+ liquid: PropTypes.shape({
104
+ errorsToShow: PropTypes.array.isRequired,
105
+ title: PropTypes.node.isRequired,
106
+ platformLabel: PropTypes.string,
107
+ }),
108
+ }),
109
+ iosErrors: PropTypes.shape({
110
+ standard: PropTypes.shape({
111
+ errorsToShow: PropTypes.array.isRequired,
112
+ title: PropTypes.node.isRequired,
113
+ platformLabel: PropTypes.string,
114
+ }),
115
+ liquid: PropTypes.shape({
116
+ errorsToShow: PropTypes.array.isRequired,
117
+ title: PropTypes.node.isRequired,
118
+ platformLabel: PropTypes.string,
119
+ }),
120
+ }),
121
+ ErrorSectionComponent: PropTypes.elementType.isRequired,
122
+ };
123
+
124
+ ErrorTypeRenderer.defaultProps = {
125
+ genericErrors: null,
126
+ androidErrors: null,
127
+ iosErrors: null,
128
+ };
129
+
130
+ export default ErrorTypeRenderer;
@@ -0,0 +1,146 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import ErrorTypeRenderer from './ErrorTypeRenderer';
4
+
5
+ describe('ErrorTypeRenderer', () => { const ErrorSectionComponent = jest.fn(({
6
+ title,
7
+ errors,
8
+ liquidError,
9
+ platformLabel,
10
+ }) => (
11
+ <div data-testid="error-section">
12
+ <span>{title}</span>
13
+ <span>{errors && errors.join(',')}</span>
14
+ <span>{liquidError ? 'liquid' : 'standard'}</span>
15
+ <span>{platformLabel}</span>
16
+ </div>
17
+ ));
18
+
19
+ afterEach(() => {
20
+ ErrorSectionComponent.mockClear();
21
+ });
22
+
23
+ it('renders nothing if all errors are empty', () => {
24
+ const { container } = render(
25
+ <ErrorTypeRenderer
26
+ genericErrors={{ standard: { errorsToShow: [], title: 'Standard' }, liquid: { errorsToShow: [], title: 'Liquid' } }}
27
+ ErrorSectionComponent={ErrorSectionComponent}
28
+ />
29
+ );
30
+ expect(container.firstChild).toBeNull();
31
+ expect(ErrorSectionComponent).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it('renders generic standard errors only', () => {
35
+ render(
36
+ <ErrorTypeRenderer
37
+ genericErrors={{ standard: { errorsToShow: ['err1', 'err2'], title: 'Standard' }, liquid: { errorsToShow: [], title: 'Liquid' } }}
38
+ ErrorSectionComponent={ErrorSectionComponent}
39
+ />
40
+ );
41
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
42
+ expect.objectContaining({
43
+ title: 'Standard',
44
+ errors: ['err1', 'err2'],
45
+ liquidError: false,
46
+ platformLabel: null,
47
+ }),
48
+ expect.anything()
49
+ );
50
+ });
51
+
52
+ it('renders generic liquid errors only', () => {
53
+ render(
54
+ <ErrorTypeRenderer
55
+ genericErrors={{ standard: { errorsToShow: [], title: 'Standard' }, liquid: { errorsToShow: ['l1'], title: 'Liquid' } }}
56
+ ErrorSectionComponent={ErrorSectionComponent}
57
+ />
58
+ );
59
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
60
+ expect.objectContaining({
61
+ title: 'Liquid',
62
+ errors: ['l1'],
63
+ liquidError: true,
64
+ platformLabel: null,
65
+ }),
66
+ expect.anything()
67
+ );
68
+ });
69
+
70
+ it('renders both generic standard and liquid errors', () => {
71
+ render(
72
+ <ErrorTypeRenderer
73
+ genericErrors={{ standard: { errorsToShow: ['err1'], title: 'Standard' }, liquid: { errorsToShow: ['l1'], title: 'Liquid' } }}
74
+ ErrorSectionComponent={ErrorSectionComponent}
75
+ />
76
+ );
77
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
78
+ expect.objectContaining({
79
+ title: 'Standard',
80
+ errors: ['err1'],
81
+ liquidError: false,
82
+ platformLabel: null,
83
+ }),
84
+ expect.anything()
85
+ );
86
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
87
+ expect.objectContaining({
88
+ title: 'Liquid',
89
+ errors: ['l1'],
90
+ liquidError: true,
91
+ platformLabel: null,
92
+ }),
93
+ expect.anything()
94
+ );
95
+ });
96
+
97
+ it('renders merged android errors with platform label', () => {
98
+ render(
99
+ <ErrorTypeRenderer
100
+ androidErrors={{
101
+ standard: { errorsToShow: ['a1'], title: 'Android Standard', platformLabel: 'ANDROID' },
102
+ liquid: { errorsToShow: ['al1'], title: 'Android Liquid', platformLabel: 'ANDROID' },
103
+ }}
104
+ iosErrors={{
105
+ standard: { errorsToShow: [], title: 'iOS Standard', platformLabel: 'IOS' },
106
+ liquid: { errorsToShow: [], title: 'iOS Liquid', platformLabel: 'IOS' },
107
+ }}
108
+ ErrorSectionComponent={ErrorSectionComponent}
109
+ />
110
+ );
111
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
112
+ expect.objectContaining({
113
+ title: false,
114
+ errors: ['a1', 'al1'],
115
+ liquidError: true,
116
+ platformLabel: 'ANDROID',
117
+ }),
118
+ expect.anything()
119
+ );
120
+ });
121
+
122
+ it('renders merged ios errors with platform label', () => {
123
+ render(
124
+ <ErrorTypeRenderer
125
+ androidErrors={{
126
+ standard: { errorsToShow: [], title: 'Android Standard', platformLabel: 'ANDROID' },
127
+ liquid: { errorsToShow: [], title: 'Android Liquid', platformLabel: 'ANDROID' },
128
+ }}
129
+ iosErrors={{
130
+ standard: { errorsToShow: ['i1'], title: 'iOS Standard', platformLabel: 'IOS' },
131
+ liquid: { errorsToShow: ['il1'], title: 'iOS Liquid', platformLabel: 'IOS' },
132
+ }}
133
+ ErrorSectionComponent={ErrorSectionComponent}
134
+ />
135
+ );
136
+ expect(ErrorSectionComponent).toHaveBeenCalledWith(
137
+ expect.objectContaining({
138
+ title: 'iOS Liquid',
139
+ errors: ['i1', 'il1'],
140
+ liquidError: true,
141
+ platformLabel: 'IOS',
142
+ }),
143
+ expect.anything()
144
+ );
145
+ });
146
+ });
@@ -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,111 @@ 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
  };
128
+
129
+ ErrorInfoNote.defaultProps = {
130
+ errorMessages: {
131
+ LIQUID_ERROR_MSG: [],
132
+ STANDARD_ERROR_MSG: [],
133
+ },
134
+ };
135
+
83
136
  ErrorInfoNote.propTypes = {
84
- errorMessage: PropTypes.string,
85
- errorType: PropTypes.string,
86
- intl: intlShape.isRequired,
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
+ }),
87
155
  };
88
156
 
89
157
  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
  }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { ANDROID, GENERIC, IOS } from '../../v2Containers/CreativesContainer/constants';
4
+
5
+ export const processErrors = (rawErrorData, errorType, platform, messages) => {
6
+ let errorsToShow = [];
7
+ let titleMsg = messages.standardErrorHeader;
8
+ let platformLabel = null;
9
+
10
+ const isObject = typeof rawErrorData === 'object' && rawErrorData !== null;
11
+ const isNotArray = !Array.isArray(rawErrorData);
12
+ const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawErrorData);
13
+ if (hasPlatformKeys) {
14
+ if (errorType === "liquid") {
15
+ titleMsg = messages.dynamicErrorHeader;
16
+ }
17
+ errorsToShow = rawErrorData[platform] || [];
18
+ platformLabel = platform; // Only set platform label for new structure
19
+ } else if (Array.isArray(rawErrorData)) {
20
+ errorsToShow = rawErrorData;
21
+ if (errorType === "liquid") {
22
+ titleMsg = messages.dynamicErrorHeader;
23
+ }
24
+ }
25
+
26
+ const title = <FormattedMessage {...titleMsg} />;
27
+ return { errorsToShow, title, platformLabel };
28
+ };
@@ -0,0 +1,93 @@
1
+ import { processErrors } from './utils';
2
+
3
+ const makeMessages = (overrides = {}) => ({
4
+ standardErrorHeader: { id: 'standard', defaultMessage: 'Standard' },
5
+ dynamicErrorHeader: { id: 'liquid', defaultMessage: 'Liquid' },
6
+ ...overrides,
7
+ });
8
+
9
+ describe('processErrors', () => {
10
+ it('returns empty errors if platform is not specified and input has GENERIC key', () => {
11
+ const messages = makeMessages();
12
+ const raw = { ANDROID: ['a1'], IOS: ['i1'], GENERIC: ['g1', 'g2'] };
13
+ const result = processErrors(raw, 'standard', undefined, messages);
14
+ expect(result.errorsToShow).toEqual([]);
15
+ expect(result.platformLabel).toBeUndefined();
16
+ });
17
+
18
+ it('returns empty errors for object input with missing keys', () => {
19
+ const messages = makeMessages();
20
+ const raw = { ANDROID: [], IOS: [], GENERIC: [] };
21
+ const result = processErrors(raw, 'standard', 'ANDROID', messages);
22
+ expect(result.errorsToShow).toEqual([]);
23
+ expect(result.platformLabel).toBe('ANDROID');
24
+ });
25
+
26
+ it('returns empty errors for null/undefined/empty input', () => {
27
+ const messages = makeMessages();
28
+ expect(processErrors(null, 'standard', undefined, messages).errorsToShow).toEqual([]);
29
+ expect(processErrors(undefined, 'standard', undefined, messages).errorsToShow).toEqual([]);
30
+ expect(processErrors([], 'standard', undefined, messages).errorsToShow).toEqual([]);
31
+ });
32
+
33
+ it('returns empty errors for object input with only GENERIC key', () => {
34
+ const messages = makeMessages();
35
+ const raw = { GENERIC: ['g1', 'g2'] };
36
+ const result = processErrors(raw, 'standard', undefined, messages);
37
+ expect(result.errorsToShow).toEqual([]);
38
+ expect(result.platformLabel).toBeUndefined();
39
+ });
40
+
41
+ it('handles object input with only ANDROID/IOS keys', () => {
42
+ const messages = makeMessages();
43
+ const raw = { ANDROID: ['a1'], IOS: ['i1'] };
44
+ const result = processErrors(raw, 'standard', 'ANDROID', messages);
45
+ expect(result.errorsToShow).toEqual(['a1']);
46
+ expect(result.platformLabel).toBe('ANDROID');
47
+ });
48
+
49
+ it('handles object input with no errors for currentTab', () => {
50
+ const messages = makeMessages();
51
+ const raw = { ANDROID: [], IOS: ['i1'] };
52
+ const result = processErrors(raw, 'standard', 'ANDROID', messages);
53
+ expect(result.errorsToShow).toEqual([]);
54
+ expect(result.platformLabel).toBe('ANDROID');
55
+ });
56
+
57
+ it('returns undefined platformLabel for object input with no GENERIC key and no platform', () => {
58
+ const messages = makeMessages();
59
+ const raw = { ANDROID: ['a1'], IOS: ['i1'] };
60
+ const result = processErrors(raw, 'standard', undefined, messages);
61
+ expect(result.errorsToShow).toEqual([]);
62
+ expect(result.platformLabel).toBeUndefined();
63
+ });
64
+
65
+ it('returns correct title and errors for array input with errorType liquid', () => {
66
+ const messages = makeMessages();
67
+ const result = processErrors(['liquidErr1', 'liquidErr2'], 'liquid', undefined, messages);
68
+ expect(result.errorsToShow).toEqual(['liquidErr1', 'liquidErr2']);
69
+ expect(result.title.props.id).toBe('liquid');
70
+ expect(result.title.props.defaultMessage).toBe('Liquid');
71
+ expect(result.platformLabel).toBeNull();
72
+ });
73
+
74
+ it('returns correct title and errors for object input with errorType liquid and platform', () => {
75
+ const messages = makeMessages();
76
+ const raw = { ANDROID: ['l1', 'l2'], IOS: ['l3'], GENERIC: ['lg1'] };
77
+ const result = processErrors(raw, 'liquid', 'ANDROID', messages);
78
+ expect(result.errorsToShow).toEqual(['l1', 'l2']);
79
+ expect(result.title.props.id).toBe('liquid');
80
+ expect(result.title.props.defaultMessage).toBe('Liquid');
81
+ expect(result.platformLabel).toBe('ANDROID');
82
+ });
83
+
84
+ it('returns empty errors for object input with errorType liquid and no platform', () => {
85
+ const messages = makeMessages();
86
+ const raw = { ANDROID: ['l1'], IOS: ['l2'], GENERIC: ['lg1', 'lg2'] };
87
+ const result = processErrors(raw, 'liquid', undefined, messages);
88
+ expect(result.errorsToShow).toEqual([]);
89
+ expect(result.title.props.id).toBe('liquid');
90
+ expect(result.title.props.defaultMessage).toBe('Liquid');
91
+ expect(result.platformLabel).toBeUndefined();
92
+ });
93
+ });