@salesforcedevs/dw-components 1.3.148-canary.25 → 1.3.148-canary.27

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.25",
3
+ "version": "1.3.148-canary.27",
4
4
  "description": "Lightning web components for https://developer.salesforce.com",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -15,7 +15,8 @@ dx-step-sequence {
15
15
  scroll-margin-top: var(--dx-g-global-header-height);
16
16
  }
17
17
 
18
- dx-step-sequence-step:last-child dx-card-step::part(footer-container) {
18
+ dx-step-sequence-step:last-child[data-has-primary-action="true"]
19
+ dx-card-step::part(footer-container) {
19
20
  background-color: var(--dx-g-indigo-vibrant-95);
20
21
  border-radius: 8px;
21
22
  margin: 0 30px 30px 30px;
@@ -8,7 +8,11 @@
8
8
  use-history={useHistory}
9
9
  >
10
10
  <template for:each={stepsData} for:item="stepDatum" for:index="index">
11
- <dx-step-sequence-step key={stepDatum.id}>
11
+ <dx-step-sequence-step
12
+ key={stepDatum.id}
13
+ class={stepClassName}
14
+ data-has-primary-action={hasPrimaryAction}
15
+ >
12
16
  <dx-card-step
13
17
  header-icon-symbol={stepDatum.headerData.iconSymbol}
14
18
  header-icon-sprite={stepDatum.headerData.iconSprite}
@@ -55,9 +59,10 @@
55
59
  </div>
56
60
  <div slot="primary-action-section">
57
61
  <dx-button
62
+ if:true={hasPrimaryAction}
58
63
  class="primary-action"
59
64
  disabled={isJ2sFormInvalid}
60
- href={stepDatum.primaryAction.link}
65
+ href={primaryActionLink}
61
66
  icon-sprite={stepDatum.primaryAction.iconSprite}
62
67
  icon-symbol={stepDatum.primaryAction.iconSymbol}
63
68
  loading={isLoading}
@@ -66,7 +71,7 @@
66
71
  target={primaryActionLinkTarget}
67
72
  variant={primaryActionVariant}
68
73
  >
69
- {stepDatum.primaryAction.text}
74
+ {primaryActionText}
70
75
  </dx-button>
71
76
  </div>
72
77
  </dx-card-step>
@@ -6,7 +6,12 @@ import { toJson } from "dxUtils/normalizers";
6
6
  import J2sForm from "dw/j2sForm";
7
7
 
8
8
  export type StepDatum = {
9
- bodyHtml?: string;
9
+ bodyHtml?:
10
+ | string
11
+ | {
12
+ default: string;
13
+ [countryCode: string]: string;
14
+ };
10
15
  formData?: {
11
16
  countriesWithReferralInput?: string[];
12
17
  countriesWithRequiredJobRole?: string[];
@@ -28,9 +33,19 @@ export type StepDatum = {
28
33
  primaryAction: {
29
34
  iconSprite?: string;
30
35
  iconSymbol?: string;
31
- link?: string;
36
+ link?:
37
+ | string
38
+ | {
39
+ default: string;
40
+ [countryCode: string]: string;
41
+ };
32
42
  linkTarget?: string;
33
- text: string;
43
+ text:
44
+ | string
45
+ | {
46
+ default: string;
47
+ [countryCode: string]: string;
48
+ };
34
49
  variant?: string;
35
50
  };
36
51
  secondaryActionHtml?: string;
@@ -38,6 +53,21 @@ export type StepDatum = {
38
53
  stepTitle: string;
39
54
  };
40
55
 
56
+ // Field names expected by DRM; see: https://salesforce.quip.com/daInAFETbPGm
57
+ export type DrmFormData = {
58
+ Email: string;
59
+ FirstName: string;
60
+ LastName: string;
61
+ MailingCountry: string;
62
+ Company__c: string;
63
+ DRM_Role__c: string;
64
+ TBID__c: string;
65
+ Opt_In__c?: boolean;
66
+ Privacy_Statement__c?: boolean;
67
+ j2s_terms?: boolean;
68
+ Referral_Email__c?: string;
69
+ };
70
+
41
71
  export default class J2sCardSequence extends LightningElement {
42
72
  _didRenderStaticInnerHtml = false;
43
73
  _isDrmLoading = false;
@@ -77,9 +107,10 @@ export default class J2sCardSequence extends LightningElement {
77
107
  });
78
108
  }
79
109
 
110
+ private confirmedCountry = "";
80
111
  private countries = "[]";
81
112
  private currentSequenceStep = 0;
82
- private didFormSubmissionFail = false;
113
+ private formSubmissionFailureState: Partial<DrmFormData> | null = null;
83
114
  private recaptchaToken = "";
84
115
  private sessionStorageId = "j2s-card-sequence";
85
116
  private tbidUserInfo: any; // TODO typings?
@@ -101,40 +132,59 @@ export default class J2sCardSequence extends LightningElement {
101
132
  this._isJ2sFormInvalid = isFormInvalid;
102
133
  }
103
134
 
135
+ private get primaryAction() {
136
+ return this.stepsData[this.currentSequenceStep || 0].primaryAction;
137
+ }
138
+
139
+ private get primaryActionLink() {
140
+ const { link } = this.primaryAction;
141
+ return typeof link === "string"
142
+ ? link
143
+ : link?.[this.confirmedCountry] || link?.default;
144
+ }
145
+
146
+ private get primaryActionText() {
147
+ const { text } = this.primaryAction;
148
+ return typeof text === "string"
149
+ ? text
150
+ : text[this.confirmedCountry] || text.default;
151
+ }
152
+
104
153
  private get primaryActionVariant() {
105
154
  if (this.currentSequenceStep === 1 && this.isJ2sFormInvalid) {
106
155
  return "custom"; // override for disabled button styling
107
156
  }
108
- return this.stepsData[this.currentSequenceStep || 0].primaryAction
109
- .variant;
157
+ return this.primaryAction.variant;
110
158
  }
111
159
 
112
160
  private get primaryActionLinkTarget() {
113
- return (
114
- this.stepsData[this.currentSequenceStep || 0].primaryAction
115
- .linkTarget || "_blank"
116
- );
161
+ return this.primaryAction.linkTarget || "_blank";
162
+ }
163
+
164
+ private get hasPrimaryAction() {
165
+ // new requirement: don't show primary action on final step if country isn't India
166
+ return this.currentSequenceStep !== 2 || this.confirmedCountry === "IN";
117
167
  }
118
168
 
119
169
  private get formIncomingUserInfo() {
120
170
  if (!this.tbidUserInfo) {
121
- return {};
171
+ return {} as DrmFormData;
122
172
  }
123
173
 
124
174
  return {
125
- firstName: decodeURIComponent(this.tbidUserInfo.given_name),
126
- lastName: decodeURIComponent(this.tbidUserInfo.family_name),
127
- email: decodeURIComponent(this.tbidUserInfo.email),
128
- jobRole: decodeURIComponent(
175
+ FirstName: decodeURIComponent(this.tbidUserInfo.given_name),
176
+ LastName: decodeURIComponent(this.tbidUserInfo.family_name),
177
+ Email: decodeURIComponent(this.tbidUserInfo.email),
178
+ DRM_Role__c: decodeURIComponent(
129
179
  this.tbidUserInfo.custom_attributes?.Role
130
180
  ),
131
- companyName: decodeURIComponent(
181
+ Company__c: decodeURIComponent(
132
182
  this.tbidUserInfo.custom_attributes?.CompanyName
133
183
  ),
134
- country: decodeURIComponent(
184
+ MailingCountry: decodeURIComponent(
135
185
  this.tbidUserInfo.custom_attributes?.Country
136
186
  )
137
- };
187
+ } as DrmFormData;
138
188
  }
139
189
 
140
190
  private get initialStepIndex() {
@@ -192,12 +242,12 @@ export default class J2sCardSequence extends LightningElement {
192
242
  this._didRenderStaticInnerHtml = true;
193
243
  }
194
244
 
195
- if (this.didFormSubmissionFail) {
245
+ if (this.formSubmissionFailureState) {
196
246
  const j2sFormEl = (this.template.querySelector(
197
247
  "dw-j2s-form"
198
248
  ) as unknown) as J2sForm | null;
199
- j2sFormEl?.notifySubmitFailure();
200
- this.didFormSubmissionFail = false;
249
+ j2sFormEl?.notifySubmitFailure(this.formSubmissionFailureState);
250
+ this.formSubmissionFailureState = null;
201
251
  }
202
252
  }
203
253
 
@@ -245,19 +295,20 @@ export default class J2sCardSequence extends LightningElement {
245
295
  evt.preventDefault(); // don't proceed automatically to next step
246
296
  this._isFormSubmitting = true;
247
297
 
248
- try {
249
- const j2sFormEl = (this.template.querySelector(
250
- "dw-j2s-form"
251
- ) as unknown) as J2sForm | null;
252
- const formData = j2sFormEl?.formDataForDrm;
253
- // For some countries, explicit approvals will be present; for others, it is implicitly assumed
254
- if (typeof formData.Opt_In__c !== "boolean") {
255
- formData.Opt_In__c = true;
256
- }
257
- if (typeof formData.Privacy_Statement__c !== "boolean") {
258
- formData.Privacy_Statement__c = true;
259
- }
298
+ const j2sFormEl = (this.template.querySelector(
299
+ "dw-j2s-form"
300
+ ) as unknown) as J2sForm;
301
+ const formData = j2sFormEl.formDataForDrm;
260
302
 
303
+ // For some countries, explicit approvals will be present; for others, it is implicitly assumed
304
+ if (typeof formData.Opt_In__c !== "boolean") {
305
+ formData.Opt_In__c = true;
306
+ }
307
+ if (typeof formData.Privacy_Statement__c !== "boolean") {
308
+ formData.Privacy_Statement__c = true;
309
+ }
310
+
311
+ try {
261
312
  const res = await fetch(this.formHandler, {
262
313
  method: "POST",
263
314
  headers: {
@@ -275,6 +326,7 @@ export default class J2sCardSequence extends LightningElement {
275
326
  const resData = await res.json();
276
327
  const resOutput = resData.handlePostRequestOutput || resData;
277
328
  if (resOutput?.status === "success") {
329
+ this.confirmedCountry = formData.MailingCountry as string;
278
330
  this.jumpToStepIndex(2, false);
279
331
  this.initialStepIndex = 0; // don't save final step in storage; rather, reset the starting step
280
332
  } else {
@@ -288,7 +340,7 @@ export default class J2sCardSequence extends LightningElement {
288
340
  );
289
341
  }
290
342
  } catch (ex) {
291
- this.didFormSubmissionFail = true;
343
+ this.formSubmissionFailureState = formData;
292
344
  console.error(ex);
293
345
  }
294
346
 
@@ -370,20 +422,32 @@ export default class J2sCardSequence extends LightningElement {
370
422
  secondaryActionMarkupContainers[stepIndex];
371
423
 
372
424
  if (bodyMarkupContainer) {
373
- let markup = bodyHtml;
425
+ let markup =
426
+ typeof bodyHtml === "string"
427
+ ? bodyHtml
428
+ : bodyHtml?.[this.confirmedCountry] ||
429
+ bodyHtml?.default;
430
+
374
431
  if (stepIndex === 2) {
375
432
  // User's email is supposed to show on final step
376
- markup = `${bodyHtml}${
377
- !this.formIncomingUserInfo.email
433
+ markup = `${markup}${
434
+ !this.formIncomingUserInfo.Email
378
435
  ? ""
379
- : `<span class="final-email-address">${this.formIncomingUserInfo.email}</span>`
436
+ : `<span class="final-email-address">${this.formIncomingUserInfo.Email}</span>`
380
437
  }`;
381
438
  }
439
+
382
440
  bodyMarkupContainer.innerHTML = markup || "";
383
441
  }
442
+
384
443
  if (secondaryActionMarkupContainer) {
385
- secondaryActionMarkupContainer.innerHTML =
386
- secondaryActionHtml || "";
444
+ const secondaryActionInnerHtml =
445
+ !secondaryActionHtml ||
446
+ // ugh, new special exception on step 2 to only show secondary action for IN
447
+ (stepIndex === 2 && this.confirmedCountry !== "IN")
448
+ ? ""
449
+ : secondaryActionHtml;
450
+ secondaryActionMarkupContainer.innerHTML = secondaryActionInnerHtml;
387
451
  }
388
452
  });
389
453
  }
@@ -11,7 +11,7 @@
11
11
  required
12
12
  validity-on-blur
13
13
  onchange={handleFormFieldChange}
14
- value={state.firstName}
14
+ value={state.FirstName}
15
15
  ></dx-input>
16
16
  <dx-input
17
17
  class="last-name-field"
@@ -22,7 +22,7 @@
22
22
  required
23
23
  validity-on-blur
24
24
  onchange={handleFormFieldChange}
25
- value={state.lastName}
25
+ value={state.LastName}
26
26
  ></dx-input>
27
27
  </div>
28
28
  <div class="form-row">
@@ -34,7 +34,7 @@
34
34
  placeholder="Your company name"
35
35
  required
36
36
  validity-on-blur
37
- value={state.companyName}
37
+ value={state.Company__c}
38
38
  onchange={handleFormFieldChange}
39
39
  ></dx-input>
40
40
  <dx-select
@@ -45,7 +45,7 @@
45
45
  options={jobRoles}
46
46
  placeholder="Select Job Role"
47
47
  required
48
- value={state.jobRole}
48
+ value={state.DRM_Role__c}
49
49
  onchange={handleFormFieldChange}
50
50
  ></dx-select>
51
51
  </div>
@@ -60,7 +60,7 @@
60
60
  disabled
61
61
  readonly="true"
62
62
  required
63
- value={state.email}
63
+ value={state.Email}
64
64
  ></dx-input>
65
65
  <dx-select
66
66
  label="Country/Region"
@@ -69,7 +69,7 @@
69
69
  options={countries}
70
70
  placeholder="Select Country/Region"
71
71
  required
72
- value={state.country}
72
+ value={state.MailingCountry}
73
73
  onchange={handleFormFieldChange}
74
74
  ></dx-select>
75
75
  </div>
@@ -80,6 +80,7 @@
80
80
  label="If anyone has referred you, please share their Email ID below, with their consent."
81
81
  validity-on-blur="true"
82
82
  type="email"
83
+ value={state.Referral_Email__c}
83
84
  onchange={handleFormFieldChange}
84
85
  ></dx-input>
85
86
  </div>
@@ -2,6 +2,7 @@ import { toJson } from "dxUtils/normalizers";
2
2
  import { api } from "lwc";
3
3
  import debounce from "debounce";
4
4
  import { LightningElementWithState } from "dxBaseElements/lightningElementWithState";
5
+ import { DrmFormData } from "dw/j2sCardSequence";
5
6
 
6
7
  // Hard-coding for now because we can't get information about an endpoint for this
7
8
  const tbidValidRoles = [
@@ -21,8 +22,7 @@ const tbidValidRoles = [
21
22
  "Product Manager"
22
23
  ];
23
24
 
24
- export type J2sFormState = {
25
- country: string;
25
+ export type J2sFormState = DrmFormData & {
26
26
  legalText: string;
27
27
  marketingText: string;
28
28
  learnMore: string;
@@ -40,7 +40,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
40
40
 
41
41
  @api formMetadata: any = {};
42
42
  @api id = "";
43
- @api incomingUserInfo: any = {};
43
+ @api incomingUserInfo = {} as DrmFormData;
44
44
  @api referralText = "";
45
45
 
46
46
  @api
@@ -60,7 +60,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
60
60
  }
61
61
 
62
62
  @api
63
- get formDataForDrm() {
63
+ get formDataForDrm(): Partial<DrmFormData> {
64
64
  // We should be able to just get the formData from the form automatically using `new FormData(theForm)`,
65
65
  // but our custom form elements don't seem to implement what it is required for that currently. See:
66
66
  // https://web.dev/more-capable-form-controls/#defining-a-form-associated-custom-element
@@ -85,22 +85,25 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
85
85
  this._recaptchaToken = value;
86
86
  if (!this._isInitialRender) {
87
87
  // Treat recaptcha checkbox as a required form field even though not technically marked as such:
88
- this.handleFormFieldChange({ target: { required: true } } as any);
88
+ this.setState({
89
+ isFormValid: this.checkValidity()
90
+ });
89
91
  }
90
92
  }
91
93
 
92
94
  private participatingCountryCodes = ["US", "CA", "IN"];
93
95
  private countriesWithReferralInput = ["IN"];
94
- private countriesWithRequiredJobRole = ["CA"];
95
96
  private requiredFormFields: HTMLElement[] = [];
96
97
  private showSubmitFailureAlert = false;
97
98
 
98
99
  get isNotParticipatingCountry() {
99
- return !this.isParticipatingCountry(this.state.country);
100
+ return !this.isParticipatingCountry(this.state.MailingCountry);
100
101
  }
101
102
 
102
103
  get isCountryWithReferralInput() {
103
- return this.countriesWithReferralInput.includes(this.state.country);
104
+ return this.countriesWithReferralInput.includes(
105
+ this.state.MailingCountry
106
+ );
104
107
  }
105
108
 
106
109
  get notAvailableAlertInfo() {
@@ -124,7 +127,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
124
127
  isFormValid: false,
125
128
  showMarketingText: true
126
129
  };
127
- const initialCountry = initialState.country;
130
+ const initialCountry = initialState.MailingCountry;
128
131
  const initialCountryData =
129
132
  initialCountry && this._countriesLookup[initialCountry];
130
133
  if (initialCountryData) {
@@ -147,8 +150,8 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
147
150
  }
148
151
 
149
152
  if (
150
- this.state.country &&
151
- this.state.country !== this.prevState.country
153
+ this.state.MailingCountry &&
154
+ this.state.MailingCountry !== this.prevState.MailingCountry
152
155
  ) {
153
156
  // Re-render new country's terms
154
157
  this.updateInnerHtml(
@@ -198,53 +201,77 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
198
201
  }
199
202
 
200
203
  handleFormFieldChange = (evt: CustomEvent) => {
201
- const callbacks: Function[] = [];
202
-
203
- // Event target information must be read prior to debouncing; otherwise, the target is gone after the debounce.
204
- if ((evt.target as HTMLInputElement)?.name === "MailingCountry") {
205
- const { value } = evt.detail;
206
- callbacks.push(() => this.handleCountryChange(value));
207
- } else if ((evt.target as HTMLInputElement)?.required) {
208
- callbacks.push(() => this.handleRequiredFieldUpdate());
209
- }
210
-
211
- callbacks.push(() => {
212
- // Remove submit failure message once any change is made to the form.
213
- if (this.showSubmitFailureAlert) {
214
- this.showSubmitFailureAlert = false;
204
+ // Event target information must be read prior to debouncing; the target is gone after the debounce.
205
+ const { target = {}, detail } = evt;
206
+ const {
207
+ name: targetName,
208
+ required: isRequiredField
209
+ } = target as HTMLInputElement;
210
+ const value =
211
+ typeof detail === "string"
212
+ ? detail
213
+ : typeof detail.checked === "boolean"
214
+ ? detail.checked
215
+ : detail.value;
216
+
217
+ // Perform the real work after a debounce, in order to prevent unnecessary work on rapid form value changes:
218
+ this.callbackWithDebounce(() => {
219
+ if (targetName === "MailingCountry") {
220
+ this.handleCountryChange(value);
221
+ } else {
222
+ const nextState: Partial<J2sFormState> = {};
223
+ if (targetName) {
224
+ nextState[targetName as keyof J2sFormState] = value;
225
+ }
226
+ if (isRequiredField) {
227
+ nextState.isFormValid = this.checkValidity();
228
+ }
229
+ this.setState(nextState);
215
230
  }
216
- });
217
231
 
218
- // Don't do unnecessary work too often if form fields are rapidly being changed
219
- this.debouncedFormFieldChangeHandler(callbacks);
232
+ this.unsetSubmitFailure();
233
+ });
220
234
  };
221
235
 
222
236
  @api
223
- notifySubmitFailure() {
237
+ notifySubmitFailure(formSubmissionFailureState: Partial<DrmFormData>) {
224
238
  this.showSubmitFailureAlert = true;
239
+ // After form submission failure, restore at most the following values to the form:
240
+ const {
241
+ FirstName,
242
+ LastName,
243
+ Email,
244
+ MailingCountry,
245
+ Company__c,
246
+ DRM_Role__c,
247
+ Referral_Email__c
248
+ } = formSubmissionFailureState;
249
+ this.setState({
250
+ FirstName,
251
+ LastName,
252
+ Email,
253
+ MailingCountry,
254
+ Company__c,
255
+ DRM_Role__c,
256
+ Referral_Email__c
257
+ });
225
258
  }
226
259
 
227
- private debouncedFormFieldChangeHandler = debounce(
228
- (callbacks: Function[]) => {
229
- callbacks.forEach((callback) => callback());
230
- },
231
- 1000 / 15 /* 15fps seems reasonable */
232
- );
233
-
234
- private handleRequiredFieldUpdate() {
235
- const isFormNowValid = this.checkValidity();
236
- if (this.state.isFormValid !== isFormNowValid) {
237
- this.setState({
238
- isFormValid: isFormNowValid
239
- });
260
+ private unsetSubmitFailure() {
261
+ if (this.showSubmitFailureAlert) {
262
+ this.showSubmitFailureAlert = false;
240
263
  }
241
264
  }
242
265
 
266
+ private callbackWithDebounce = debounce((callback: Function) => {
267
+ callback();
268
+ }, 1000 / 15 /* 15fps seems reasonable */);
269
+
243
270
  private handleCountryChange(countryCode: string) {
244
271
  const countryData = this._countriesLookup[countryCode];
245
272
 
246
273
  this.setState({
247
- country: countryCode,
274
+ MailingCountry: countryCode,
248
275
  learnMore: countryData.learnMore,
249
276
  legalText: countryData.legalText,
250
277
  marketingText: countryData.marketingText,
@@ -253,7 +280,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
253
280
  });
254
281
 
255
282
  // Because updating the country can update the required fields, we need to re-set form
256
- // validity after render
283
+ // validity _after_ render
257
284
  requestAnimationFrame(() =>
258
285
  this.setState({
259
286
  isFormValid: this.checkValidity()
@@ -275,7 +302,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
275
302
  private checkValidity() {
276
303
  return (
277
304
  // Boolean(this.recaptchaToken) &&
278
- this.isParticipatingCountry(this.state.country) &&
305
+ this.isParticipatingCountry(this.state.MailingCountry) &&
279
306
  this.requiredFormFields.every(
280
307
  (requiredFormField) =>
281
308
  // @ts-ignore