@salesforcedevs/dw-components 1.3.155 → 1.3.157-canary.1

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.
@@ -0,0 +1,344 @@
1
+ import { toJson } from "dxUtils/normalizers";
2
+ import { stateMap } from "dwUtils/forms";
3
+ import { api } from "lwc";
4
+ import debounce from "debounce";
5
+ import { LightningElementWithState } from "dxBaseElements/lightningElementWithState";
6
+ import { DrmFormData } from "dw/j2sCardSequence";
7
+
8
+ // Hard-coding for now because we can't get information about an endpoint for this
9
+ const tbidValidRoles = [
10
+ "Developer",
11
+ "Administrator",
12
+ "Architect",
13
+ "Sales Representative / Manager",
14
+ "Marketer",
15
+ "Service Agent / Supervisor",
16
+ "Data Analyst",
17
+ "Consultant",
18
+ "Business Operations Manager",
19
+ "Designer",
20
+ "Educator",
21
+ "Student",
22
+ "Executive",
23
+ "Product Manager"
24
+ ];
25
+
26
+ export type J2sFormState = DrmFormData & {
27
+ legalText: string;
28
+ marketingText: string;
29
+ learnMore: string;
30
+ showLegalCheckbox: boolean;
31
+ showMarketingText: boolean;
32
+ isFormValid: boolean;
33
+ };
34
+
35
+ const debounceCallback = (callback: Function) =>
36
+ debounce(callback, 1000 / 15 /* 15fps roughly */);
37
+
38
+ export default class J2sForm extends LightningElementWithState<J2sFormState> {
39
+ private _countriesJson = "";
40
+ private _countriesLookup: any = {}; // TODO typings
41
+ private _didSetFormId = false;
42
+ private _isInitialRender = true;
43
+ private _recaptchaToken = "";
44
+
45
+ @api formMetadata: any = {};
46
+ @api id = "";
47
+ @api incomingUserInfo = {} as DrmFormData;
48
+ @api referralText = "";
49
+
50
+ @api
51
+ get countries() {
52
+ return this._countriesJson || "[]";
53
+ }
54
+ set countries(value: string) {
55
+ this._countriesJson = value;
56
+
57
+ // Store a lookup table of country data by value so that we don't have to loop all the time
58
+ const countries = toJson(value);
59
+ const countriesLookup: any = {};
60
+ countries.forEach((country: any) => {
61
+ countriesLookup[country.value] = country;
62
+ });
63
+ this._countriesLookup = countriesLookup;
64
+ }
65
+
66
+ @api
67
+ get formDataForDrm(): Partial<DrmFormData> {
68
+ // We should be able to just get the formData from the form automatically using `new FormData(theForm)`,
69
+ // but our custom form elements don't seem to implement what it is required for that currently. See:
70
+ // https://web.dev/more-capable-form-controls/#defining-a-form-associated-custom-element
71
+ const formData: any = {};
72
+ this.template
73
+ .querySelectorAll("form [name]")
74
+ .forEach((namedElement) => {
75
+ const typedElement = namedElement as HTMLInputElement;
76
+ formData[typedElement.name] =
77
+ typeof typedElement.checked === "boolean"
78
+ ? typedElement.checked
79
+ : typedElement.value;
80
+ });
81
+ return formData;
82
+ }
83
+
84
+ @api
85
+ get recaptchaToken() {
86
+ return this._recaptchaToken;
87
+ }
88
+ set recaptchaToken(value: string) {
89
+ this._recaptchaToken = value;
90
+ if (!this._isInitialRender) {
91
+ // Treat recaptcha checkbox as a required form field even though not technically marked as such:
92
+ this.setState({
93
+ isFormValid: this.checkValidity()
94
+ });
95
+ }
96
+ }
97
+
98
+ private participatingCountryCodes = ["US", "CA", "IN"];
99
+ private countriesWithReferralInput = ["IN"];
100
+ private requiredFormFields: HTMLElement[] = [];
101
+ private showSubmitFailureAlert = false;
102
+
103
+ get isNotParticipatingCountry() {
104
+ return !this.isParticipatingCountry(this.state.MailingCountry);
105
+ }
106
+
107
+ get isCountryWithReferralInput() {
108
+ return this.countriesWithReferralInput.includes(
109
+ this.state.MailingCountry
110
+ );
111
+ }
112
+
113
+ get isSalesforceEmployee() {
114
+ return this.state.RelationshipToSalesforce === "Salesforce Employee";
115
+ }
116
+
117
+ get notAvailableAlertInfo() {
118
+ return this.formMetadata.notAvailableAlertInfo;
119
+ }
120
+
121
+ get formSubmitFailureAlertInfo() {
122
+ return this.formMetadata.formSubmitFailureAlertInfo;
123
+ }
124
+
125
+ get jobRoles() {
126
+ return tbidValidRoles.map((role) => ({
127
+ label: role,
128
+ value: role
129
+ }));
130
+ }
131
+
132
+ get showStateProvince(): boolean {
133
+ return this.state.MailingCountry in stateMap;
134
+ }
135
+
136
+ get states(): { id: string; label: string; value: string }[] {
137
+ return (
138
+ stateMap[this.state.MailingCountry as keyof typeof stateMap] || []
139
+ );
140
+ }
141
+
142
+ connectedCallback() {
143
+ const initialState: Partial<J2sFormState> = {
144
+ ...this.incomingUserInfo,
145
+ isFormValid: false,
146
+ showMarketingText: true
147
+ };
148
+ const initialCountry = initialState.MailingCountry;
149
+ const initialCountryData =
150
+ initialCountry && this._countriesLookup[initialCountry];
151
+ if (initialCountryData) {
152
+ initialState.showLegalCheckbox =
153
+ initialCountryData.showLegalCheckbox;
154
+ initialState.showMarketingText =
155
+ initialCountryData.showMarketingText;
156
+ initialState.legalText = initialCountryData.legalText;
157
+ initialState.marketingText = initialCountryData.marketingText;
158
+ initialState.learnMore = initialCountryData.learnMore;
159
+ }
160
+
161
+ this.state = initialState as J2sFormState;
162
+ }
163
+
164
+ renderedCallback(): void {
165
+ if (this._isInitialRender) {
166
+ this.updateInnerHtml("span.j2s-terms", this.formMetadata.j2sTerms);
167
+ this._isInitialRender = false;
168
+ }
169
+
170
+ if (
171
+ this.state.MailingCountry &&
172
+ this.state.MailingCountry !== this.prevState.MailingCountry
173
+ ) {
174
+ // Re-render new country's terms
175
+ this.updateInnerHtml(
176
+ "span.marketing-terms",
177
+ this.state.marketingText
178
+ );
179
+ this.updateInnerHtml("span.legal-terms", this.state.legalText);
180
+ if (this.state.learnMore) {
181
+ this.updateInnerHtml(
182
+ "span.legal-terms-learn-more",
183
+ this.state.learnMore
184
+ );
185
+ } else {
186
+ this.updateInnerHtml("span.legal-terms-learn-more", "");
187
+ this.template
188
+ .querySelector(".legal-terms-learn-more")
189
+ ?.classList.add("hidden");
190
+ }
191
+ }
192
+
193
+ if (this.state.isFormValid !== this.prevState.isFormValid) {
194
+ this.dispatchEvent(
195
+ new CustomEvent("formvaliditychange", {
196
+ detail: {
197
+ isValid: this.state.isFormValid
198
+ },
199
+ bubbles: true,
200
+ composed: true
201
+ })
202
+ );
203
+ }
204
+
205
+ this.requiredFormFields = Array.from(
206
+ this.template.querySelectorAll("[required]")
207
+ );
208
+
209
+ if (this.id && !this._didSetFormId) {
210
+ this._didSetFormId = true;
211
+ this.template.querySelector("form")?.setAttribute("id", this.id);
212
+ }
213
+ }
214
+
215
+ handleLearnMore() {
216
+ this.template
217
+ .querySelector(".legal-terms-learn-more")
218
+ ?.classList.toggle("hidden");
219
+ }
220
+
221
+ debouncedFormFieldChangeHandler = debounceCallback(
222
+ (targetName: string, value: any, isRequiredField = false) => {
223
+ if (targetName === "MailingCountry") {
224
+ this.handleCountryChange(value);
225
+ } else {
226
+ const nextState: Partial<J2sFormState> = {};
227
+ if (targetName) {
228
+ nextState[targetName as keyof J2sFormState] = value;
229
+ }
230
+ if (isRequiredField) {
231
+ nextState.isFormValid = this.checkValidity();
232
+ }
233
+ this.setState(nextState);
234
+ }
235
+
236
+ this.unsetSubmitFailure();
237
+ }
238
+ );
239
+
240
+ handleFormFieldChange = (evt: CustomEvent) => {
241
+ // Event target information must be read prior to debouncing; the target is gone after the debounce.
242
+ const { target = {}, detail } = evt;
243
+ const { name: targetName, required: isRequiredField } =
244
+ target as HTMLInputElement;
245
+ const value =
246
+ typeof detail === "string"
247
+ ? detail
248
+ : typeof detail.checked === "boolean"
249
+ ? detail.checked
250
+ : detail.value;
251
+
252
+ // Perform the real work after a debounce, in order to prevent unnecessary work on rapid form value changes:
253
+ this.debouncedFormFieldChangeHandler(
254
+ targetName,
255
+ value,
256
+ isRequiredField
257
+ );
258
+ };
259
+
260
+ @api
261
+ notifySubmitFailure(formSubmissionFailureState: Partial<DrmFormData>) {
262
+ this.showSubmitFailureAlert = true;
263
+ // After form submission failure, restore at most the following values to the form:
264
+ const {
265
+ FirstName,
266
+ LastName,
267
+ Email,
268
+ MailingCountry,
269
+ MailingState,
270
+ Company__c,
271
+ DRM_Role__c,
272
+ Referral_Email__c
273
+ } = formSubmissionFailureState;
274
+ this.setState({
275
+ FirstName,
276
+ LastName,
277
+ Email,
278
+ MailingCountry,
279
+ MailingState,
280
+ Company__c,
281
+ DRM_Role__c,
282
+ Referral_Email__c
283
+ });
284
+ }
285
+
286
+ private unsetSubmitFailure() {
287
+ if (this.showSubmitFailureAlert) {
288
+ this.showSubmitFailureAlert = false;
289
+ }
290
+ }
291
+
292
+ private handleCountryChange(countryCode: string) {
293
+ const countryData = this._countriesLookup[countryCode];
294
+
295
+ this.setState({
296
+ MailingCountry: countryCode,
297
+ learnMore: countryData.learnMore,
298
+ legalText: countryData.legalText,
299
+ marketingText: countryData.marketingText,
300
+ showLegalCheckbox: countryData.showLegalCheckbox,
301
+ showMarketingText: countryData.showMarketingText
302
+ });
303
+
304
+ this.dispatchEvent(
305
+ new CustomEvent("countrychange", {
306
+ bubbles: true,
307
+ composed: true,
308
+ detail: countryCode
309
+ })
310
+ );
311
+
312
+ // Because updating the country can update the required fields, we need to re-set form
313
+ // validity _after_ render
314
+ requestAnimationFrame(() =>
315
+ this.setState({
316
+ isFormValid: this.checkValidity()
317
+ })
318
+ );
319
+ }
320
+
321
+ private updateInnerHtml(selector: string, html: string): void {
322
+ const element = this.template.querySelector(selector);
323
+ if (element) {
324
+ element.innerHTML = html;
325
+ }
326
+ }
327
+
328
+ private isParticipatingCountry(country: string) {
329
+ return this.participatingCountryCodes.includes(country);
330
+ }
331
+
332
+ private checkValidity() {
333
+ return (
334
+ Boolean(this.recaptchaToken) &&
335
+ !this.isSalesforceEmployee &&
336
+ this.isParticipatingCountry(this.state.MailingCountry) &&
337
+ this.requiredFormFields.every(
338
+ (requiredFormField) =>
339
+ // @ts-ignore
340
+ requiredFormField.checkValidity?.() === true
341
+ )
342
+ );
343
+ }
344
+ }