@salesforcedevs/dw-components 1.3.148-canary.13 → 1.3.148-canary.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dw-components",
3
- "version": "1.3.148-canary.13",
3
+ "version": "1.3.148-canary.15",
4
4
  "description": "Lightning web components for https://developer.salesforce.com",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -35,7 +35,7 @@
35
35
  <dw-j2s-form
36
36
  id={stepDatum.formData.formId}
37
37
  countries={countries}
38
- form-metadata={stepDatum.formMetadata}
38
+ form-metadata={stepDatum.formData}
39
39
  incoming-user-info={formIncomingUserInfo}
40
40
  onformvaliditychange={handleFormValidityChange}
41
41
  recaptcha-token={recaptchaToken}
@@ -3,6 +3,7 @@ import StepSequence from "dx/stepSequence";
3
3
  import { getListWithUuids } from "dxUtils/list";
4
4
  import { track } from "dxUtils/analytics";
5
5
  import { toJson } from "dxUtils/normalizers";
6
+ import J2sForm from "dw/j2sForm";
6
7
 
7
8
  export type StepDatum = {
8
9
  bodyHtml?: string;
@@ -36,18 +37,13 @@ export type StepDatum = {
36
37
  stepTitle: string;
37
38
  };
38
39
 
39
- function isNoAnimationCookieSet() {
40
- return document.cookie
41
- .split(";")
42
- .some((ck) => ck.trim() === "no_j2s_animation=true");
43
- }
44
-
45
40
  export default class J2sCardSequence extends LightningElement {
46
41
  _didRenderStaticInnerHtml = false;
47
42
  _isDrmLoading = false;
48
43
  _isFormSubmitting = false;
49
44
  _isJ2sFormInvalid = false;
50
45
  _isTbidLoading = false;
46
+ _recaptchaResetTimeoutId: number | null = null;
51
47
  _stepsData: StepDatum[] = [];
52
48
  @api animateTransitions = false;
53
49
  @api drmDomain: string = "https://drm.my.salesforce-sites.com";
@@ -62,30 +58,22 @@ export default class J2sCardSequence extends LightningElement {
62
58
  }
63
59
  set stepsData(value) {
64
60
  // normalize incoming value with uuid keys and non-required props (for cleaner HTML template), etc.
65
- this._stepsData = getListWithUuids<StepDatum>(toJson(value)).map(
66
- (stepDatum) => {
67
- const normalizedStepDatum = {
68
- ...stepDatum,
69
- headerData: stepDatum.headerData || {},
70
- primaryAction: {
71
- ...stepDatum.primaryAction,
72
- variant: stepDatum.primaryAction.variant || "primary",
73
- iconSprite:
74
- stepDatum.primaryAction.iconSprite || "general",
75
- iconSymbol:
76
- stepDatum.primaryAction.iconSymbol || "arrow-right"
77
- }
78
- };
79
-
80
- if (normalizedStepDatum.formData) {
81
- (normalizedStepDatum as any).formMetadata = JSON.stringify(
82
- normalizedStepDatum.formData
83
- );
61
+ const valueWithUuids = getListWithUuids<StepDatum>(toJson(value));
62
+ this._stepsData = valueWithUuids.map((stepDatum) => {
63
+ const normalizedStepDatum = {
64
+ ...stepDatum,
65
+ headerData: stepDatum.headerData || {},
66
+ primaryAction: {
67
+ ...stepDatum.primaryAction,
68
+ variant: stepDatum.primaryAction.variant || "primary",
69
+ iconSprite: stepDatum.primaryAction.iconSprite || "general",
70
+ iconSymbol:
71
+ stepDatum.primaryAction.iconSymbol || "arrow-right"
84
72
  }
73
+ };
85
74
 
86
- return normalizedStepDatum;
87
- }
88
- );
75
+ return normalizedStepDatum;
76
+ });
89
77
  }
90
78
 
91
79
  private countries = "[]";
@@ -161,11 +149,13 @@ export default class J2sCardSequence extends LightningElement {
161
149
  @api
162
150
  recaptchaCallback(responseToken: string) {
163
151
  this.recaptchaToken = responseToken;
152
+ clearTimeout(this._recaptchaResetTimeoutId as number);
164
153
  }
165
154
 
166
155
  @api
167
156
  recaptchaExpiredCallback() {
168
157
  this.recaptchaToken = "";
158
+ clearTimeout(this._recaptchaResetTimeoutId as number);
169
159
  }
170
160
 
171
161
  connectedCallback() {
@@ -174,10 +164,6 @@ export default class J2sCardSequence extends LightningElement {
174
164
  // This has to be defined here since it depends on a variable that depends on sessionStorage.
175
165
  this.currentSequenceStep = this.initialStepIndex;
176
166
  this.resetCustomButtonColors();
177
- if (isNoAnimationCookieSet()) {
178
- /* eslint-disable-next-line @lwc/lwc/no-api-reassignments */
179
- this.animateTransitions = false;
180
- }
181
167
  }
182
168
 
183
169
  renderedCallback() {
@@ -199,6 +185,21 @@ export default class J2sCardSequence extends LightningElement {
199
185
  handleStepSequenceChange({ detail }: CustomEvent) {
200
186
  this.currentSequenceStep = detail.stepIndex;
201
187
  this.resetCustomButtonColors();
188
+
189
+ if (
190
+ this.currentSequenceStep !== 1 &&
191
+ this.recaptchaToken &&
192
+ this._recaptchaResetTimeoutId === null
193
+ ) {
194
+ // If the recaptcha element expires on its own while you are not on the 2nd (form) step, it messes with the
195
+ // page's layout (basically breaks it). So, we need to reset it _before_ expiration if you leave that step.
196
+ // We do it with a timeout so that the token is not immediately wiped if the user goes back and forth a bit.
197
+ this._recaptchaResetTimeoutId = window.setTimeout(() => {
198
+ this._recaptchaResetTimeoutId = null;
199
+ this.recaptchaToken = "";
200
+ (window as any).grecaptcha.reset();
201
+ }, 1000 * 30 /* 30 seconds */);
202
+ }
202
203
  }
203
204
 
204
205
  handleGetStarted(evt: Event) {
@@ -209,8 +210,7 @@ export default class J2sCardSequence extends LightningElement {
209
210
  evt.preventDefault();
210
211
  this.initialStepIndex = 1;
211
212
  this._isTbidLoading = true;
212
- // @ts-ignore: call out to the tbid-controller on DSC
213
- window.__startTbidLogin();
213
+ (window as any).__startTbidLogin?.(); // call out to DSC tbid controller
214
214
  }
215
215
  }
216
216
 
@@ -222,9 +222,12 @@ export default class J2sCardSequence extends LightningElement {
222
222
  evt.preventDefault(); // don't proceed automatically to next step
223
223
  this._isFormSubmitting = true;
224
224
 
225
+ const j2sFormEl = (this.template.querySelector(
226
+ "dw-j2s-form"
227
+ ) as unknown) as J2sForm | null;
228
+
225
229
  try {
226
- const formData = (this.template.querySelector("dw-j2s-form") as any)
227
- ?.formDataForDrm;
230
+ const formData = j2sFormEl?.formDataForDrm;
228
231
  // For some countries, explicit approvals will be present; for others, it is implicitly assumed
229
232
  if (typeof formData.Opt_In__c !== "boolean") {
230
233
  formData.Opt_In__c = true;
@@ -249,7 +252,8 @@ export default class J2sCardSequence extends LightningElement {
249
252
  const resData = await res.json();
250
253
  const resOutput = resData.handlePostRequestOutput || resData;
251
254
  if (resOutput?.status === "success") {
252
- this.jumpToStepIndex(2);
255
+ this.jumpToStepIndex(2, false);
256
+ this.initialStepIndex = 0; // don't save final step in storage; rather, reset the starting step
253
257
  } else {
254
258
  throw new Error(
255
259
  `Form submission failed. (status: ${resOutput.status}, message: ${resOutput.errorMessage})`
@@ -257,6 +261,7 @@ export default class J2sCardSequence extends LightningElement {
257
261
  }
258
262
  }
259
263
  } catch (ex) {
264
+ j2sFormEl?.notifySubmitFailure();
260
265
  console.error(ex); // TODO
261
266
  }
262
267
 
@@ -314,8 +319,10 @@ export default class J2sCardSequence extends LightningElement {
314
319
  );
315
320
  }
316
321
 
317
- private jumpToStepIndex(stepIndex: number) {
318
- this.initialStepIndex = stepIndex; // update sessionStorage value
322
+ private jumpToStepIndex(stepIndex: number, andStoreIndex = true) {
323
+ if (andStoreIndex) {
324
+ this.initialStepIndex = stepIndex; // update sessionStorage value
325
+ }
319
326
  ((this.template.querySelector(
320
327
  "dx-step-sequence"
321
328
  ) as unknown) as StepSequence)?.jumpToStep(stepIndex);
@@ -10,7 +10,7 @@
10
10
  placeholder="Your first name"
11
11
  required
12
12
  validity-on-blur
13
- onblur={handleRequiredFieldUpdate}
13
+ onchange={handleFormFieldChange}
14
14
  value={state.firstName}
15
15
  ></dx-input>
16
16
  <dx-input
@@ -21,7 +21,7 @@
21
21
  placeholder="Your last name"
22
22
  required
23
23
  validity-on-blur
24
- onblur={handleRequiredFieldUpdate}
24
+ onchange={handleFormFieldChange}
25
25
  value={state.lastName}
26
26
  ></dx-input>
27
27
  </div>
@@ -34,30 +34,19 @@
34
34
  placeholder="Your company name"
35
35
  required
36
36
  validity-on-blur
37
- onblur={handleRequiredFieldUpdate}
38
37
  value={state.companyName}
38
+ onchange={handleFormFieldChange}
39
39
  ></dx-input>
40
40
  <dx-select
41
- if:false={isJobRoleRequired}
42
41
  class="job-role-field"
43
42
  label="Job Role"
44
43
  message-when-value-missing="Select your job role"
45
44
  name="DRM_Role__c"
46
45
  options={jobRoles}
47
46
  placeholder="Select Job Role"
47
+ required={isJobRoleRequired}
48
48
  value={state.jobRole}
49
- ></dx-select>
50
- <dx-select
51
- if:true={isJobRoleRequired}
52
- class="job-role-field"
53
- label="Job Role"
54
- message-when-value-missing="Select your job role"
55
- name="DRM_Role__c"
56
- options={jobRoles}
57
- placeholder="Select Job Role"
58
- required
59
- value={state.jobRole}
60
- onchange={handleRequiredFieldUpdate}
49
+ onchange={handleFormFieldChange}
61
50
  ></dx-select>
62
51
  </div>
63
52
  <div class="form-row">
@@ -69,8 +58,8 @@
69
58
  missing-error-message="Enter your email address"
70
59
  validity-on-blur="true"
71
60
  required
72
- onblur={handleRequiredFieldUpdate}
73
61
  value={state.email}
62
+ onchange={handleFormFieldChange}
74
63
  ></dx-input>
75
64
  <dx-select
76
65
  label="Country/Region"
@@ -80,7 +69,7 @@
80
69
  placeholder="Select Country/Region"
81
70
  required
82
71
  value={state.country}
83
- onchange={handleCountryChange}
72
+ onchange={handleFormFieldChange}
84
73
  ></dx-select>
85
74
  </div>
86
75
  <div class="form-row" if:true={isCountryWithReferralInput}>
@@ -89,6 +78,7 @@
89
78
  class="referral-email-field full-width"
90
79
  label="If anyone has referred you, please share their Email ID below, with their consent."
91
80
  type="email"
81
+ onchange={handleFormFieldChange}
92
82
  ></dx-input>
93
83
  </div>
94
84
  <div class="form-row" if:true={isNotParticipatingCountry}>
@@ -99,6 +89,14 @@
99
89
  variant="accented"
100
90
  ></dx-alert>
101
91
  </div>
92
+ <div class="form-row" if:true={showSubmitFailureAlert}>
93
+ <dx-alert
94
+ alert-info={formSubmitFailureAlertInfo}
95
+ class="full-width"
96
+ type="danger"
97
+ variant="accented"
98
+ ></dx-alert>
99
+ </div>
102
100
  <div class="form-terms-container">
103
101
  <div class="form-row" if:true={state.showMarketingText}>
104
102
  <dx-checkbox
@@ -106,7 +104,7 @@
106
104
  name="Opt_In__c"
107
105
  required
108
106
  class="terms"
109
- onchange={handleRequiredFieldUpdate}
107
+ onchange={handleFormFieldChange}
110
108
  disabled={isNotParticipatingCountry}
111
109
  >
112
110
  <span lwc:dom="manual" class="marketing-terms"></span>
@@ -118,7 +116,7 @@
118
116
  name="j2s_terms"
119
117
  required
120
118
  class="terms"
121
- onchange={handleRequiredFieldUpdate}
119
+ onchange={handleFormFieldChange}
122
120
  disabled={isNotParticipatingCountry}
123
121
  >
124
122
  <span lwc:dom="manual" class="j2s-terms"></span>
@@ -131,7 +129,7 @@
131
129
  name="Privacy_Statement__c"
132
130
  required
133
131
  class="terms"
134
- onchange={handleRequiredFieldUpdate}
132
+ onchange={handleFormFieldChange}
135
133
  disabled={isNotParticipatingCountry}
136
134
  >
137
135
  <span lwc:dom="manual" class="legal-terms"></span>
@@ -1,5 +1,6 @@
1
1
  import { toJson } from "dxUtils/normalizers";
2
2
  import { api } from "lwc";
3
+ import debounce from "debounce";
3
4
  import { LightningElementWithState } from "dxBaseElements/lightningElementWithState";
4
5
 
5
6
  // Hard-coding for now because we can't get information about an endpoint for this
@@ -34,12 +35,12 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
34
35
  private _countriesJson = "";
35
36
  private _countriesLookup: any = {}; // TODO typings
36
37
  private _didSetFormId = false;
37
- private _formMetadata: any = {}; // TODO typings
38
38
  private _incomingUserInfo: any = {}; // TODO typings
39
39
  private _isInitialRender = true;
40
40
  private _recaptchaToken = "";
41
41
 
42
42
  @api id = "";
43
+ @api formMetadata: any = {};
43
44
  @api referralText = "";
44
45
 
45
46
  @api
@@ -66,14 +67,6 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
66
67
  this._incomingUserInfo = toJson(value);
67
68
  }
68
69
 
69
- @api
70
- get formMetadata() {
71
- return this._formMetadata;
72
- }
73
- set formMetadata(value: string) {
74
- this._formMetadata = toJson(value);
75
- }
76
-
77
70
  @api
78
71
  get formDataForDrm() {
79
72
  // We should be able to just get the formData from the form automatically using `new FormData(theForm)`,
@@ -98,13 +91,15 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
98
91
  }
99
92
  set recaptchaToken(value: string) {
100
93
  this._recaptchaToken = value;
101
- this.handleRequiredFieldUpdate();
94
+ // Treat recaptcha checkbox as a required form field even though not technically marked as such:
95
+ this.handleFormFieldChange({ target: { required: true } } as any);
102
96
  }
103
97
 
104
98
  private participatingCountryCodes = ["US", "CA", "IN"];
105
99
  private countriesWithReferralInput = ["IN"];
106
100
  private countriesWithRequiredJobRole = ["CA"];
107
101
  private requiredFormFields: HTMLElement[] = [];
102
+ private showSubmitFailureAlert = false;
108
103
 
109
104
  get isNotParticipatingCountry() {
110
105
  return !this.isParticipatingCountry(this.state.country);
@@ -119,7 +114,11 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
119
114
  }
120
115
 
121
116
  get notAvailableAlertInfo() {
122
- return (this.formMetadata as any).notAvailableAlertInfo;
117
+ return this.formMetadata.notAvailableAlertInfo;
118
+ }
119
+
120
+ get formSubmitFailureAlertInfo() {
121
+ return this.formMetadata.formSubmitFailureAlertInfo;
123
122
  }
124
123
 
125
124
  get jobRoles() {
@@ -151,10 +150,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
151
150
 
152
151
  renderedCallback(): void {
153
152
  if (this._isInitialRender) {
154
- this.updateInnerHtml(
155
- "span.j2s-terms",
156
- (this.formMetadata as any).j2sTerms
157
- );
153
+ this.updateInnerHtml("span.j2s-terms", this.formMetadata.j2sTerms);
158
154
  }
159
155
 
160
156
  if (
@@ -202,7 +198,47 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
202
198
  }
203
199
  }
204
200
 
205
- handleRequiredFieldUpdate() {
201
+ handleLearnMore() {
202
+ this.template
203
+ .querySelector(".legal-terms-learn-more")
204
+ ?.classList.toggle("hidden");
205
+ }
206
+
207
+ handleFormFieldChange = (evt: CustomEvent) => {
208
+ const callbacks: Function[] = [];
209
+
210
+ // Event target information must be read prior to debouncing; otherwise, the target is gone after the debounce.
211
+ if ((evt.target as HTMLInputElement)?.name === "MailingCountry") {
212
+ const { value } = evt.detail;
213
+ callbacks.push(() => this.handleCountryChange(value));
214
+ } else if ((evt.target as HTMLInputElement)?.required) {
215
+ callbacks.push(() => this.handleRequiredFieldUpdate());
216
+ }
217
+
218
+ callbacks.push(() => {
219
+ // Remove submit failure message once any change is made to the form.
220
+ if (this.showSubmitFailureAlert) {
221
+ this.showSubmitFailureAlert = false;
222
+ }
223
+ });
224
+
225
+ // Don't do unnecessary work too often if form fields are rapidly being changed
226
+ this.debouncedFormFieldChangeHandler(callbacks);
227
+ };
228
+
229
+ @api
230
+ notifySubmitFailure() {
231
+ this.showSubmitFailureAlert = true;
232
+ }
233
+
234
+ private debouncedFormFieldChangeHandler = debounce(
235
+ (callbacks: Function[]) => {
236
+ callbacks.forEach((callback) => callback());
237
+ },
238
+ 1000 / 15 /* 15fps seems reasonable */
239
+ );
240
+
241
+ private handleRequiredFieldUpdate() {
206
242
  const isFormNowValid = this.checkValidity();
207
243
  if (this.state.isFormValid !== isFormNowValid) {
208
244
  this.setState({
@@ -211,12 +247,11 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
211
247
  }
212
248
  }
213
249
 
214
- handleCountryChange({ detail }: CustomEvent) {
215
- const { value } = detail;
216
- const countryData = this._countriesLookup[value];
250
+ private handleCountryChange(countryCode: string) {
251
+ const countryData = this._countriesLookup[countryCode];
217
252
 
218
253
  this.setState({
219
- country: value,
254
+ country: countryCode,
220
255
  learnMore: countryData.learnMore,
221
256
  legalText: countryData.legalText,
222
257
  marketingText: countryData.marketingText,
@@ -232,12 +267,6 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
232
267
  );
233
268
  }
234
269
 
235
- handleLearnMore() {
236
- this.template
237
- .querySelector(".legal-terms-learn-more")
238
- ?.classList.toggle("hidden");
239
- }
240
-
241
270
  private updateInnerHtml(selector: string, html: string): void {
242
271
  const element = this.template.querySelector(selector);
243
272
  if (element) {