@capillarytech/creatives-library 8.0.326 → 8.0.327-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 (30) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +17 -0
  3. package/services/tests/api.test.js +85 -0
  4. package/utils/commonUtils.js +10 -0
  5. package/utils/tagValidations.js +2 -3
  6. package/utils/tests/commonUtil.test.js +169 -0
  7. package/utils/tests/tagValidations.test.js +1 -1
  8. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
  9. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
  10. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +93 -0
  11. package/v2Components/CommonTestAndPreview/SendTestMessage.js +79 -51
  12. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +134 -34
  13. package/v2Components/CommonTestAndPreview/actions.js +10 -0
  14. package/v2Components/CommonTestAndPreview/constants.js +15 -1
  15. package/v2Components/CommonTestAndPreview/index.js +315 -15
  16. package/v2Components/CommonTestAndPreview/messages.js +106 -0
  17. package/v2Components/CommonTestAndPreview/reducer.js +10 -0
  18. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
  19. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +648 -0
  20. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +24 -0
  21. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +174 -0
  22. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
  23. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +52 -0
  24. package/v2Components/CommonTestAndPreview/tests/constants.test.js +31 -1
  25. package/v2Components/CommonTestAndPreview/tests/index.test.js +36 -0
  26. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
  27. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
  28. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1886 -1754
  29. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +351 -318
  30. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5586 -5212
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.326",
4
+ "version": "8.0.327-alpha.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -741,4 +741,21 @@ export const getBeePopupBuilderToken = () => {
741
741
  return request(url, getAPICallObject('GET'));
742
742
  };
743
743
 
744
+ /**
745
+ * Look up member by identifier (email or mobile) for add test customer flow.
746
+ * Uses standard API auth headers (no x-cap-ct) to avoid CORS preflight issues with node layer.
747
+ * @param {string} identifierType - 'email' or 'mobile'
748
+ * @param {string} identifierValue - email address or mobile number
749
+ * @returns {Promise} Promise resolving to { success, response: { exists, customerDetails } }
750
+ */
751
+ export const getMembersLookup = (identifierType, identifierValue) => {
752
+ const url = `${API_ENDPOINT}/members?identifierType=${encodeURIComponent(identifierType)}&identifierValue=${encodeURIComponent(identifierValue)}`;
753
+ return request(url, getAPICallObject('GET'));
754
+ };
755
+
756
+ export const createTestCustomer = (payload) => {
757
+ const url = `${API_ENDPOINT}/testCustomers`;
758
+ return request(url, getAPICallObject('POST', payload));
759
+ };
760
+
744
761
  export {request, getAPICallObject};
@@ -29,6 +29,8 @@ import {
29
29
  getAssetStatus,
30
30
  getBeePopupBuilderToken,
31
31
  getCmsAccounts,
32
+ getMembersLookup,
33
+ createTestCustomer,
32
34
  } from '../api';
33
35
  import { mockData } from './mockData';
34
36
  import getSchema from '../getSchema';
@@ -1014,3 +1016,86 @@ describe('getCmsAccounts', () => {
1014
1016
  expect(result).toBeInstanceOf(Promise);
1015
1017
  });
1016
1018
  });
1019
+
1020
+ describe('getMembersLookup', () => {
1021
+ beforeEach(() => {
1022
+ global.fetch = jest.fn();
1023
+ global.fetch.mockReturnValue(Promise.resolve({
1024
+ status: 200,
1025
+ json: () => Promise.resolve({ success: true, response: { exists: false, customerDetails: [] } }),
1026
+ }));
1027
+ });
1028
+
1029
+ it('should return a Promise', () => {
1030
+ const result = getMembersLookup('email', 'user@example.com');
1031
+ expect(result).toBeInstanceOf(Promise);
1032
+ });
1033
+
1034
+ it('should be callable with identifierType and identifierValue', () => {
1035
+ expect(typeof getMembersLookup).toBe('function');
1036
+ const result = getMembersLookup('mobile', '9123456789');
1037
+ expect(result).toBeInstanceOf(Promise);
1038
+ });
1039
+
1040
+ it('should call fetch with correct URL encoding and GET method', () => {
1041
+ global.fetch.mockClear();
1042
+ getMembersLookup('email', 'user+test@example.com');
1043
+ expect(global.fetch).toHaveBeenCalled();
1044
+ const calls = global.fetch.mock.calls;
1045
+ const withEncoding = calls.find(
1046
+ (c) =>
1047
+ c[0] &&
1048
+ String(c[0]).includes('members') &&
1049
+ String(c[0]).includes('identifierType=email') &&
1050
+ String(c[0]).includes('user%2Btest%40example.com')
1051
+ );
1052
+ if (withEncoding) {
1053
+ const [url, options] = withEncoding;
1054
+ expect(url).toContain('identifierType=email');
1055
+ expect(url).toContain('identifierValue=user%2Btest%40example.com');
1056
+ expect(options?.method || 'GET').toBe('GET');
1057
+ }
1058
+ const anyMembersCall = calls.find((c) => c[0] && String(c[0]).includes('members'));
1059
+ expect(anyMembersCall).toBeDefined();
1060
+ expect(anyMembersCall[0]).toContain('identifierType=');
1061
+ expect(anyMembersCall[0]).toContain('identifierValue=');
1062
+ expect(anyMembersCall[1]?.method || 'GET').toBe('GET');
1063
+ });
1064
+ });
1065
+
1066
+ describe('createTestCustomer', () => {
1067
+ beforeEach(() => {
1068
+ global.fetch = jest.fn();
1069
+ global.fetch.mockReturnValue(Promise.resolve({
1070
+ status: 200,
1071
+ json: () => Promise.resolve({ success: true }),
1072
+ }));
1073
+ });
1074
+
1075
+ it('should return a Promise', () => {
1076
+ const payload = { customer: { email: 'test@example.com', firstName: 'Test' } };
1077
+ const result = createTestCustomer(payload);
1078
+ expect(result).toBeInstanceOf(Promise);
1079
+ });
1080
+
1081
+ it('should accept customerId payload for existing customer', () => {
1082
+ const payload = { customerId: 'cust-123' };
1083
+ const result = createTestCustomer(payload);
1084
+ expect(result).toBeInstanceOf(Promise);
1085
+ expect(global.fetch).toHaveBeenCalled();
1086
+ const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1087
+ expect(lastCall[0]).toContain('testCustomers');
1088
+ expect(lastCall[1].method).toBe('POST');
1089
+ expect(lastCall[1].body).toBe(JSON.stringify(payload));
1090
+ });
1091
+
1092
+ it('should call fetch with /testCustomers URL and POST method', () => {
1093
+ const payload = { customer: { email: 'n@b.co', firstName: 'N' } };
1094
+ createTestCustomer(payload);
1095
+ expect(global.fetch).toHaveBeenCalled();
1096
+ const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1097
+ expect(lastCall[0]).toContain('testCustomers');
1098
+ expect(lastCall[1].method).toBe('POST');
1099
+ expect(lastCall[1].body).toBe(JSON.stringify(payload));
1100
+ });
1101
+ });
@@ -17,6 +17,8 @@ import {
17
17
  } from "../v2Containers/CreativesContainer/constants";
18
18
  import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
19
19
  import { SMS_TRAI_VAR } from '../v2Containers/SmsTrai/Edit/constants';
20
+ import { EMAIL_REGEX, PHONE_REGEX } from '../v2Components/CommonTestAndPreview/constants';
21
+
20
22
  export const apiMessageFormatHandler = (id, fallback) => (
21
23
  <FormattedMessage id={id} defaultMessage={fallback} />
22
24
  );
@@ -530,3 +532,11 @@ export const checkForPersonalizationTokens = (formData) => {
530
532
  }
531
533
  return false;
532
534
  };
535
+
536
+ export const isValidEmail = (email) => EMAIL_REGEX.test(email);
537
+ export const isValidMobile = (mobile) => PHONE_REGEX.test(mobile);
538
+
539
+ export const formatPhoneNumber = (phone) => {
540
+ if (!phone) return '';
541
+ return String(phone).replace(/[^\d]/g, '');
542
+ };
@@ -207,8 +207,7 @@ export const validateIfTagClosed = (value) => {
207
207
  export const preprocessHtml = (content) => {
208
208
  const replacements = {
209
209
  "&#39;": "'",
210
- "&quot;": "'",
211
- '"': "'",
210
+ "&quot;": '"',
212
211
  "&amp;": "&",
213
212
  "&lt;": "<",
214
213
  "&gt;": ">",
@@ -226,7 +225,7 @@ export const preprocessHtml = (content) => {
226
225
  });
227
226
 
228
227
  // Step 2: Perform the standard replacements on the entire content
229
- return contentWithStyleFixes?.replace(/&#39;|&quot;|&amp;|&lt;|&gt;|"|\n/g, (match) => replacements[match]);
228
+ return contentWithStyleFixes?.replace(/&#39;|&quot;|&amp;|&lt;|&gt;|\n/g, (match) => replacements[match]);
230
229
  };
231
230
 
232
231
  //this is used to get the subtags from custom or extended tags
@@ -8,6 +8,11 @@ import {
8
8
  validateCarouselCards,
9
9
  hasPersonalizationTags,
10
10
  checkForPersonalizationTokens,
11
+ isValidEmail,
12
+ isValidMobile,
13
+ formatPhoneNumber,
14
+ getMessageForDevice,
15
+ getTitleForDevice,
11
16
  } from "../commonUtils";
12
17
  import { skipTags } from "../tagValidations";
13
18
  import { SMS_TRAI_VAR } from '../../v2Containers/SmsTrai/Edit/constants';
@@ -629,6 +634,170 @@ describe("validateLiquidTemplateContent", () => {
629
634
  });
630
635
  });
631
636
 
637
+ describe("isValidEmail", () => {
638
+ it("returns true for valid email addresses", () => {
639
+ expect(isValidEmail("user@example.com")).toBe(true);
640
+ expect(isValidEmail("test.user@domain.co")).toBe(true);
641
+ expect(isValidEmail("a@b.co")).toBe(true);
642
+ });
643
+
644
+ it("returns false for invalid email addresses", () => {
645
+ expect(isValidEmail("")).toBe(false);
646
+ expect(isValidEmail("invalid")).toBe(false);
647
+ expect(isValidEmail("@nodomain.com")).toBe(false);
648
+ expect(isValidEmail("noatsign.com")).toBe(false);
649
+ expect(isValidEmail("missingtld@domain")).toBe(false);
650
+ expect(isValidEmail(" user@example.com ")).toBe(false);
651
+ });
652
+
653
+ it("returns true for edge case single-char local part", () => {
654
+ expect(isValidEmail("x@y.co")).toBe(true);
655
+ });
656
+ });
657
+
658
+ describe("isValidMobile", () => {
659
+ it("returns true for valid mobile numbers (8-15 digits, no leading zero)", () => {
660
+ expect(isValidMobile("12345678")).toBe(true);
661
+ expect(isValidMobile("9123456789")).toBe(true);
662
+ expect(isValidMobile("123456789012345")).toBe(true);
663
+ });
664
+
665
+ it("returns false for invalid mobile numbers", () => {
666
+ expect(isValidMobile("")).toBe(false);
667
+ expect(isValidMobile("01234567")).toBe(false);
668
+ expect(isValidMobile("1234567")).toBe(false);
669
+ expect(isValidMobile("1234567890123456")).toBe(false);
670
+ expect(isValidMobile("abc12345678")).toBe(false);
671
+ expect(isValidMobile("12345 67890")).toBe(false);
672
+ });
673
+
674
+ it("returns true for exactly 8 and exactly 15 digits", () => {
675
+ expect(isValidMobile("12345678")).toBe(true);
676
+ expect(isValidMobile("123456789012345")).toBe(true);
677
+ });
678
+ });
679
+
680
+ describe("formatPhoneNumber", () => {
681
+ it("returns empty string for falsy input", () => {
682
+ expect(formatPhoneNumber("")).toBe("");
683
+ expect(formatPhoneNumber(null)).toBe("");
684
+ expect(formatPhoneNumber(undefined)).toBe("");
685
+ });
686
+
687
+ it("strips non-digit characters", () => {
688
+ expect(formatPhoneNumber("91 234 567 890")).toBe("91234567890");
689
+ expect(formatPhoneNumber("+91-234567890")).toBe("91234567890");
690
+ expect(formatPhoneNumber("(123) 456-7890")).toBe("1234567890");
691
+ });
692
+
693
+ it("returns digits-only string unchanged", () => {
694
+ expect(formatPhoneNumber("9123456789")).toBe("9123456789");
695
+ });
696
+
697
+ it("returns empty string for whitespace-only input", () => {
698
+ expect(formatPhoneNumber(" ")).toBe("");
699
+ });
700
+ });
701
+
702
+ describe("hasPersonalizationTags", () => {
703
+ it("returns true when text has liquid tags {{ }}", () => {
704
+ expect(hasPersonalizationTags("Hello {{name}}")).toBe(true);
705
+ expect(hasPersonalizationTags("{{foo}}")).toBe(true);
706
+ });
707
+
708
+ it("returns true when text has bracket tags [ ]", () => {
709
+ expect(hasPersonalizationTags("Hello [event.name]")).toBe(true);
710
+ expect(hasPersonalizationTags("[tag]")).toBe(true);
711
+ });
712
+
713
+ it("returns false for empty or no tags", () => {
714
+ expect(hasPersonalizationTags("")).toBeFalsy();
715
+ expect(hasPersonalizationTags()).toBeFalsy();
716
+ expect(hasPersonalizationTags("plain text")).toBe(false);
717
+ expect(hasPersonalizationTags("only {{ open")).toBe(false);
718
+ });
719
+
720
+ it("returns false when only [ or only ] present without matching pair", () => {
721
+ expect(hasPersonalizationTags("only [ open")).toBe(false);
722
+ expect(hasPersonalizationTags("only ] close")).toBe(false);
723
+ });
724
+
725
+ it("returns true when liquid tags have content with spaces", () => {
726
+ expect(hasPersonalizationTags("Hello {{ customer.name }}")).toBe(true);
727
+ });
728
+ });
729
+
730
+ describe("getMessageForDevice", () => {
731
+ it("returns message for device from templateData", () => {
732
+ const templateData = {
733
+ versions: {
734
+ android: { base: { expandableDetails: { message: "Android msg" } } },
735
+ ios: { base: { expandableDetails: { message: "iOS msg" } } },
736
+ },
737
+ };
738
+ expect(getMessageForDevice(templateData, "android")).toBe("Android msg");
739
+ expect(getMessageForDevice(templateData, "ios")).toBe("iOS msg");
740
+ });
741
+
742
+ it("returns undefined for missing path", () => {
743
+ expect(getMessageForDevice(null, "android")).toBeUndefined();
744
+ expect(getMessageForDevice({}, "android")).toBeUndefined();
745
+ expect(getMessageForDevice({ versions: {} }, "android")).toBeUndefined();
746
+ });
747
+
748
+ it("returns undefined when base exists but expandableDetails is missing", () => {
749
+ expect(getMessageForDevice({ versions: { android: { base: {} } } }, "android")).toBeUndefined();
750
+ });
751
+ });
752
+
753
+ describe("getTitleForDevice", () => {
754
+ it("returns title for device from templateData", () => {
755
+ const templateData = {
756
+ versions: {
757
+ android: { base: { title: "Android title" } },
758
+ ios: { base: { title: "iOS title" } },
759
+ },
760
+ };
761
+ expect(getTitleForDevice(templateData, "android")).toBe("Android title");
762
+ expect(getTitleForDevice(templateData, "ios")).toBe("iOS title");
763
+ });
764
+
765
+ it("returns empty string for missing title", () => {
766
+ expect(getTitleForDevice(null, "android")).toBe("");
767
+ expect(getTitleForDevice({}, "android")).toBe("");
768
+ expect(getTitleForDevice({ versions: { android: { base: {} } } }, "android")).toBe("");
769
+ });
770
+
771
+ it("returns empty string when base.title is undefined", () => {
772
+ expect(getTitleForDevice({ versions: { android: { base: { title: undefined } } } }, "android")).toBe("");
773
+ });
774
+ });
775
+
776
+ describe("checkForPersonalizationTokens", () => {
777
+ it("returns true when formData contains liquid or bracket tokens", () => {
778
+ expect(checkForPersonalizationTokens({ 0: { content: "Hi {{name}}" } })).toBe(true);
779
+ expect(checkForPersonalizationTokens({ tab1: { message: "Hello [event.id]" } })).toBe(true);
780
+ });
781
+
782
+ it("returns false for empty or no tokens", () => {
783
+ expect(checkForPersonalizationTokens(null)).toBe(false);
784
+ expect(checkForPersonalizationTokens({})).toBe(false);
785
+ expect(checkForPersonalizationTokens({ 0: { content: "plain" } })).toBe(false);
786
+ });
787
+
788
+ it("returns false for non-object formData", () => {
789
+ expect(checkForPersonalizationTokens("string")).toBe(false);
790
+ });
791
+
792
+ it("returns true for two-level nested object containing token", () => {
793
+ expect(checkForPersonalizationTokens({ tab: { body: "Hi {{name}}" } })).toBe(true);
794
+ });
795
+
796
+ it("returns false for formData with array value (no string tokens)", () => {
797
+ expect(checkForPersonalizationTokens({ 0: { items: ["a", "b"] } })).toBe(false);
798
+ });
799
+ });
800
+
632
801
  describe("validateMobilePushContent", () => {
633
802
  const formatMessage = jest.fn(msg => msg.id);
634
803
  const messages = {
@@ -642,7 +642,7 @@ describe('preprocessHtml', () => {
642
642
 
643
643
  it('should replace &quot; with double quotes', () => {
644
644
  const input = 'She said, &quot;Hello, World!&quot;';
645
- const expectedOutput = "She said, 'Hello, World!'";
645
+ const expectedOutput = 'She said, "Hello, World!"';
646
646
  expect(preprocessHtml(input)).toEqual(expectedOutput);
647
647
  });
648
648
 
@@ -0,0 +1,42 @@
1
+ import PropTypes from "prop-types";
2
+ import { FormattedMessage } from "react-intl";
3
+ import CapButton from "@capillarytech/cap-ui-library/CapButton";
4
+ import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
5
+ import messages from "./messages";
6
+ import React from "react";
7
+
8
+ const AddTestCustomerButton = ({
9
+ searchValue,
10
+ handleAddTestCustomer
11
+ }) => (
12
+ <CapButton
13
+ onClick={handleAddTestCustomer}
14
+ type="flat"
15
+ size="small"
16
+ className="test-customer-add-btn"
17
+ prefix={
18
+ <CapIcon
19
+ type="add-profile"
20
+ className="add-test-customer-icon"
21
+ />
22
+ }
23
+ >
24
+ <FormattedMessage
25
+ {...messages.addTestCustomerWithValue}
26
+ values={{
27
+ searchValue: (
28
+ <span className="test-customer-add-btn-value" title={searchValue || ""}>
29
+ "{searchValue}"
30
+ </span>
31
+ ),
32
+ }}
33
+ />
34
+ </CapButton>
35
+ );
36
+
37
+ AddTestCustomerButton.propTypes = {
38
+ searchValue: PropTypes.string.isRequired,
39
+ handleAddTestCustomer: PropTypes.func.isRequired
40
+ };
41
+
42
+ export default AddTestCustomerButton;
@@ -0,0 +1,155 @@
1
+ import CapModal from "@capillarytech/cap-ui-library/CapModal";
2
+ import CapButton from "@capillarytech/cap-ui-library/CapButton";
3
+ import CapInput from "@capillarytech/cap-ui-library/CapInput";
4
+ import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
5
+ import CapRow from "@capillarytech/cap-ui-library/CapRow";
6
+ import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
7
+ import { FormattedMessage, injectIntl, intlShape } from "react-intl";
8
+ import messages from "./messages";
9
+ import React, { useState } from "react";
10
+ import PropTypes from "prop-types";
11
+ import { CHANNELS } from "./constants";
12
+
13
+ /** Identifier validation runs in the parent before this modal opens; email/mobile are read-only here. */
14
+ const EMPTY_VALIDATION = { email: "", mobile: "" };
15
+
16
+ const CustomerCreationModal = ({
17
+ customerModal,
18
+ onCloseCustomerModal,
19
+ channel,
20
+ customerData,
21
+ setCustomerData,
22
+ onSave,
23
+ intl,
24
+ }) => {
25
+ const [isLoading, setIsLoading] = useState(false);
26
+
27
+ // Check if required fields are filled based on channel
28
+ const isRequiredFieldMissing = () => {
29
+ if (channel === CHANNELS.EMAIL && !customerData.email) {
30
+ return true;
31
+ }
32
+ if (channel === CHANNELS.SMS && !customerData.mobile) {
33
+ return true;
34
+ }
35
+ return false;
36
+ };
37
+
38
+ const isSaveDisabled = () => isRequiredFieldMissing() || isLoading;
39
+
40
+ return (
41
+ <CapModal
42
+ visible={customerModal[0]}
43
+ onCancel={onCloseCustomerModal}
44
+ centered={true}
45
+ width={500}
46
+ maskStyle={{ backgroundColor: 'rgba(244, 245, 247, 0.9)' }}
47
+ footer={
48
+ <CapRow justify="start" gutter={8}>
49
+ <CapButton
50
+ type="primary"
51
+ onClick={() => onSave(EMPTY_VALIDATION, setIsLoading)}
52
+ disabled={isSaveDisabled()}
53
+ loading={isLoading}
54
+ >
55
+ <FormattedMessage {...messages.saveButton} />
56
+ </CapButton>
57
+ <CapButton
58
+ type="secondary"
59
+ onClick={onCloseCustomerModal}
60
+ disabled={isLoading}
61
+ >
62
+ <FormattedMessage {...messages.cancelButton} />
63
+ </CapButton>
64
+ </CapRow>
65
+ }
66
+ title={<FormattedMessage {...messages.customerCreationModalTitle} />}
67
+ wrapClassName="common-test-preview-modal-wrap"
68
+ className="common-test-preview-modal"
69
+ >
70
+ <CapRow className="customer-creation-modal-row">
71
+ <CapColumn span={24}>
72
+ <span className="customer-creation-modal-description">
73
+ <FormattedMessage {...messages.customerCreationModalDescription} />
74
+ </span>
75
+ </CapColumn>
76
+ </CapRow>
77
+
78
+ <CapRow className="customer-creation-modal-row">
79
+ <CapColumn span={24}>
80
+ <CapLabel type="label1" className="customer-creation-modal-label">
81
+ <FormattedMessage {...messages.customerName} />{' '}
82
+ <span className="customer-creation-modal-optional">
83
+ <FormattedMessage {...messages.customerNameOptional} />
84
+ </span>
85
+ </CapLabel>
86
+ <CapInput
87
+ value={customerData.name || ""}
88
+ onChange={e =>
89
+ setCustomerData({ ...customerData, name: e.target.value })
90
+ }
91
+ placeholder={intl.formatMessage(messages.customerNamePlaceholder)}
92
+ size="default"
93
+ className="customer-creation-modal-input"
94
+ />
95
+ </CapColumn>
96
+ </CapRow>
97
+
98
+ {channel===CHANNELS.EMAIL && <CapRow className="customer-creation-modal-row">
99
+ <CapColumn span={24}>
100
+ <CapLabel type="label1" className="customer-creation-modal-label">
101
+ <FormattedMessage {...messages.customerEmail} />
102
+ </CapLabel>
103
+ <CapInput
104
+ value={customerData.email || ""}
105
+ onChange={() => {}}
106
+ placeholder={intl.formatMessage(messages.customerEmailPlaceholder)}
107
+ size="default"
108
+ className="customer-creation-modal-input"
109
+ disabled={true}
110
+ />
111
+ </CapColumn>
112
+ </CapRow>}
113
+
114
+ {channel===CHANNELS.SMS &&
115
+ <CapRow className="customer-creation-modal-row customer-creation-modal-row--last">
116
+ <CapColumn span={24}>
117
+ <CapLabel type="label1" className="customer-creation-modal-label">
118
+ <FormattedMessage {...messages.customerMobile} />
119
+ </CapLabel>
120
+ <CapInput
121
+ value={customerData.mobile || ""}
122
+ onChange={() => {}}
123
+ placeholder={intl.formatMessage(messages.customerMobilePlaceholder)}
124
+ size="default"
125
+ className="customer-creation-modal-input"
126
+ disabled={true}
127
+ />
128
+ </CapColumn>
129
+ </CapRow>}
130
+ </CapModal>
131
+ );
132
+ };
133
+
134
+ CustomerCreationModal.propTypes = {
135
+ customerModal: PropTypes.arrayOf(
136
+ PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
137
+ ).isRequired,
138
+ onCloseCustomerModal: PropTypes.func.isRequired,
139
+ channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
140
+ customerData: PropTypes.shape({
141
+ name: PropTypes.string,
142
+ email: PropTypes.string,
143
+ mobile: PropTypes.string,
144
+ customerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
145
+ }),
146
+ setCustomerData: PropTypes.func.isRequired,
147
+ onSave: PropTypes.func.isRequired,
148
+ intl: intlShape.isRequired,
149
+ };
150
+
151
+ CustomerCreationModal.defaultProps = {
152
+ customerData: {},
153
+ };
154
+
155
+ export default injectIntl(CustomerCreationModal);
@@ -0,0 +1,93 @@
1
+ import CapModal from "@capillarytech/cap-ui-library/CapModal";
2
+ import { FormattedMessage, injectIntl, intlShape } from "react-intl";
3
+ import messages from "./messages";
4
+ import React, { useState } from "react";
5
+ import PropTypes from "prop-types";
6
+ import { CapCard, CapRow, CapColumn } from "@capillarytech/cap-ui-library";
7
+ import { CHANNELS } from "./constants";
8
+ import CapButton from "@capillarytech/cap-ui-library/CapButton";
9
+ import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
10
+
11
+
12
+ const ExistingCustomerModal = ({ customerModal, onCloseCustomerModal, customerData, channel, onSave, intl }) => {
13
+ const [isLoading, setIsLoading] = useState(false);
14
+ return (
15
+ <CapModal
16
+ visible={customerModal[0]}
17
+ onCancel={onCloseCustomerModal}
18
+ centered={true}
19
+ width={500}
20
+ maskStyle={{ backgroundColor: 'rgba(244, 245, 247, 0.9)' }}
21
+ footer={
22
+ <CapRow justify="start">
23
+ <CapButton
24
+ type="primary"
25
+ onClick={() => onSave({}, setIsLoading)}
26
+ loading={isLoading}
27
+ >
28
+ <FormattedMessage {...messages.saveButton} />
29
+ </CapButton>
30
+ <CapButton
31
+ type="secondary"
32
+ onClick={onCloseCustomerModal}
33
+ disabled={isLoading}
34
+ >
35
+ <FormattedMessage {...messages.cancelButton} />
36
+ </CapButton>
37
+ </CapRow>
38
+ }
39
+ title={intl.formatMessage(messages.customerCreationModalTitle)}
40
+ wrapClassName="common-test-preview-modal-wrap existing-customer-modal-wrap"
41
+ className="common-test-preview-modal"
42
+ >
43
+ <div className="existing-customer-modal">
44
+ <CapRow className="existing-customer-modal-intro-row">
45
+ <FormattedMessage {...messages.existingCustomerModalDescription} />
46
+ </CapRow>
47
+ <CapCard className="existing-customer-modal-card">
48
+ <CapRow className="existing-customer-modal-card-row">
49
+ <CapColumn className="existing-customer-modal-avatar">
50
+ <CapIcon type="user-profile" className="existing-customer-modal-avatar-icon" />
51
+ </CapColumn>
52
+ <CapColumn className="existing-customer-modal-details">
53
+ <CapRow className="existing-customer-modal-name">
54
+ {customerData.name || "-"}
55
+ </CapRow>
56
+ <CapColumn className="existing-customer-modal-meta">
57
+ {channel === CHANNELS.EMAIL && customerData.email && (
58
+ <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerEmail} />: </span> {customerData.email}</CapRow>
59
+ )}
60
+ {channel === CHANNELS.SMS && customerData.mobile && (
61
+ <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerMobile} />: </span>{customerData.mobile}</CapRow>
62
+ )}
63
+ <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerID} />: </span>{customerData.customerId}</CapRow>
64
+ </CapColumn>
65
+ </CapColumn>
66
+ </CapRow>
67
+ </CapCard>
68
+ </div>
69
+ </CapModal>
70
+ );
71
+ };
72
+
73
+ ExistingCustomerModal.propTypes = {
74
+ customerModal: PropTypes.arrayOf(
75
+ PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
76
+ ).isRequired,
77
+ onCloseCustomerModal: PropTypes.func.isRequired,
78
+ customerData: PropTypes.shape({
79
+ name: PropTypes.string,
80
+ email: PropTypes.string,
81
+ mobile: PropTypes.string,
82
+ customerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
83
+ }),
84
+ channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
85
+ onSave: PropTypes.func.isRequired,
86
+ intl: intlShape.isRequired,
87
+ };
88
+
89
+ ExistingCustomerModal.defaultProps = {
90
+ customerData: {},
91
+ };
92
+
93
+ export default injectIntl(ExistingCustomerModal);