@capillarytech/creatives-library 8.0.330 → 8.0.331-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/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +72 -0
- package/utils/commonUtils.js +10 -0
- package/utils/tests/commonUtil.test.js +169 -0
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +94 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +78 -49
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +134 -34
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +17 -1
- package/v2Components/CommonTestAndPreview/index.js +356 -22
- package/v2Components/CommonTestAndPreview/messages.js +106 -0
- package/v2Components/CommonTestAndPreview/reducer.js +12 -0
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +648 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +23 -5
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +174 -0
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +39 -19
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +31 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +36 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1408 -1276
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +321 -288
- package/v2Containers/TagList/index.js +11 -15
- package/v2Containers/WebPush/Create/index.js +1 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5246 -4872
package/package.json
CHANGED
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,73 @@ 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 anyMembersCall = calls.find((c) => c[0] && String(c[0]).includes('members'));
|
|
1046
|
+
expect(anyMembersCall).toBeDefined();
|
|
1047
|
+
expect(anyMembersCall[0]).toContain('identifierType=');
|
|
1048
|
+
expect(anyMembersCall[0]).toContain('identifierValue=');
|
|
1049
|
+
expect(anyMembersCall[1]?.method || 'GET').toBe('GET');
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
describe('createTestCustomer', () => {
|
|
1054
|
+
beforeEach(() => {
|
|
1055
|
+
global.fetch = jest.fn();
|
|
1056
|
+
global.fetch.mockReturnValue(Promise.resolve({
|
|
1057
|
+
status: 200,
|
|
1058
|
+
json: () => Promise.resolve({ success: true }),
|
|
1059
|
+
}));
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it('should return a Promise', () => {
|
|
1063
|
+
const payload = { customer: { email: 'test@example.com', firstName: 'Test' } };
|
|
1064
|
+
const result = createTestCustomer(payload);
|
|
1065
|
+
expect(result).toBeInstanceOf(Promise);
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it('should accept customerId payload for existing customer', () => {
|
|
1069
|
+
const payload = { customerId: 'cust-123' };
|
|
1070
|
+
const result = createTestCustomer(payload);
|
|
1071
|
+
expect(result).toBeInstanceOf(Promise);
|
|
1072
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
1073
|
+
const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
|
|
1074
|
+
expect(lastCall[0]).toContain('testCustomers');
|
|
1075
|
+
expect(lastCall[1].method).toBe('POST');
|
|
1076
|
+
expect(lastCall[1].body).toBe(JSON.stringify(payload));
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
it('should call fetch with /testCustomers URL and POST method', () => {
|
|
1080
|
+
const payload = { customer: { email: 'n@b.co', firstName: 'N' } };
|
|
1081
|
+
createTestCustomer(payload);
|
|
1082
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
1083
|
+
const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
|
|
1084
|
+
expect(lastCall[0]).toContain('testCustomers');
|
|
1085
|
+
expect(lastCall[1].method).toBe('POST');
|
|
1086
|
+
expect(lastCall[1].body).toBe(JSON.stringify(payload));
|
|
1087
|
+
});
|
|
1088
|
+
});
|
package/utils/commonUtils.js
CHANGED
|
@@ -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
|
+
};
|
|
@@ -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 = {
|
|
@@ -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,94 @@
|
|
|
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
|
+
disabled={isLoading}
|
|
28
|
+
>
|
|
29
|
+
<FormattedMessage {...messages.saveButton} />
|
|
30
|
+
</CapButton>
|
|
31
|
+
<CapButton
|
|
32
|
+
type="secondary"
|
|
33
|
+
onClick={onCloseCustomerModal}
|
|
34
|
+
disabled={isLoading}
|
|
35
|
+
>
|
|
36
|
+
<FormattedMessage {...messages.cancelButton} />
|
|
37
|
+
</CapButton>
|
|
38
|
+
</CapRow>
|
|
39
|
+
}
|
|
40
|
+
title={intl.formatMessage(messages.customerCreationModalTitle)}
|
|
41
|
+
wrapClassName="common-test-preview-modal-wrap existing-customer-modal-wrap"
|
|
42
|
+
className="common-test-preview-modal"
|
|
43
|
+
>
|
|
44
|
+
<div className="existing-customer-modal">
|
|
45
|
+
<CapRow className="existing-customer-modal-intro-row">
|
|
46
|
+
<FormattedMessage {...messages.existingCustomerModalDescription} />
|
|
47
|
+
</CapRow>
|
|
48
|
+
<CapCard className="existing-customer-modal-card">
|
|
49
|
+
<CapRow className="existing-customer-modal-card-row">
|
|
50
|
+
<CapColumn className="existing-customer-modal-avatar">
|
|
51
|
+
<CapIcon type="user-profile" className="existing-customer-modal-avatar-icon" />
|
|
52
|
+
</CapColumn>
|
|
53
|
+
<CapColumn className="existing-customer-modal-details">
|
|
54
|
+
<CapRow className="existing-customer-modal-name">
|
|
55
|
+
{customerData.name || "-"}
|
|
56
|
+
</CapRow>
|
|
57
|
+
<CapColumn className="existing-customer-modal-meta">
|
|
58
|
+
{channel === CHANNELS.EMAIL && customerData.email && (
|
|
59
|
+
<CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerEmail} />: </span> {customerData.email}</CapRow>
|
|
60
|
+
)}
|
|
61
|
+
{channel === CHANNELS.SMS && customerData.mobile && (
|
|
62
|
+
<CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerMobile} />: </span>{customerData.mobile}</CapRow>
|
|
63
|
+
)}
|
|
64
|
+
<CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerID} />: </span>{customerData.customerId}</CapRow>
|
|
65
|
+
</CapColumn>
|
|
66
|
+
</CapColumn>
|
|
67
|
+
</CapRow>
|
|
68
|
+
</CapCard>
|
|
69
|
+
</div>
|
|
70
|
+
</CapModal>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
ExistingCustomerModal.propTypes = {
|
|
75
|
+
customerModal: PropTypes.arrayOf(
|
|
76
|
+
PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
|
|
77
|
+
).isRequired,
|
|
78
|
+
onCloseCustomerModal: PropTypes.func.isRequired,
|
|
79
|
+
customerData: PropTypes.shape({
|
|
80
|
+
name: PropTypes.string,
|
|
81
|
+
email: PropTypes.string,
|
|
82
|
+
mobile: PropTypes.string,
|
|
83
|
+
customerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
84
|
+
}),
|
|
85
|
+
channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
|
|
86
|
+
onSave: PropTypes.func.isRequired,
|
|
87
|
+
intl: intlShape.isRequired,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
ExistingCustomerModal.defaultProps = {
|
|
91
|
+
customerData: {},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default injectIntl(ExistingCustomerModal);
|