@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.
- package/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +85 -0
- package/utils/commonUtils.js +10 -0
- package/utils/tagValidations.js +2 -3
- package/utils/tests/commonUtil.test.js +169 -0
- package/utils/tests/tagValidations.test.js +1 -1
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +93 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +79 -51
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +134 -34
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +15 -1
- package/v2Components/CommonTestAndPreview/index.js +315 -15
- package/v2Components/CommonTestAndPreview/messages.js +106 -0
- package/v2Components/CommonTestAndPreview/reducer.js +10 -0
- 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 +24 -0
- 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 +52 -0
- 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 +1886 -1754
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +351 -318
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5586 -5212
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,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
|
+
});
|
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
|
+
};
|
package/utils/tagValidations.js
CHANGED
|
@@ -207,8 +207,7 @@ export const validateIfTagClosed = (value) => {
|
|
|
207
207
|
export const preprocessHtml = (content) => {
|
|
208
208
|
const replacements = {
|
|
209
209
|
"'": "'",
|
|
210
|
-
""": "'
|
|
211
|
-
'"': "'",
|
|
210
|
+
""": '"',
|
|
212
211
|
"&": "&",
|
|
213
212
|
"<": "<",
|
|
214
213
|
">": ">",
|
|
@@ -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(/'|"|&|<|>
|
|
228
|
+
return contentWithStyleFixes?.replace(/'|"|&|<|>|\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 " with double quotes', () => {
|
|
644
644
|
const input = 'She said, "Hello, World!"';
|
|
645
|
-
const expectedOutput =
|
|
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);
|