@salesforcedevs/dw-components 1.3.148-canary.0 → 1.3.148-canary.10

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.0",
3
+ "version": "1.3.148-canary.10",
4
4
  "description": "Lightning web components for https://developer.salesforce.com",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -38,7 +38,12 @@
38
38
  form-metadata={stepDatum.formMetadata}
39
39
  incoming-user-info={formIncomingUserInfo}
40
40
  onformvaliditychange={handleFormValidityChange}
41
- ></dw-j2s-form>
41
+ recaptcha-token={recaptchaToken}
42
+ >
43
+ <div slot="form-captcha-slot">
44
+ <slot name="captcha-slot"></slot>
45
+ </div>
46
+ </dw-j2s-form>
42
47
  </template>
43
48
  </template>
44
49
  </div>
@@ -52,6 +57,7 @@
52
57
  <dx-button
53
58
  class="primary-action"
54
59
  disabled={isJ2sFormInvalid}
60
+ href={stepDatum.primaryAction.link}
55
61
  icon-sprite={stepDatum.primaryAction.iconSprite}
56
62
  icon-symbol={stepDatum.primaryAction.iconSymbol}
57
63
  loading={isLoading}
@@ -1,8 +1,8 @@
1
1
  import { api, LightningElement } from "lwc";
2
2
  import StepSequence from "dx/stepSequence";
3
3
  import { getListWithUuids } from "dxUtils/list";
4
+ import { track } from "dxUtils/analytics";
4
5
  import { toJson } from "dxUtils/normalizers";
5
- import Button from "dx/button";
6
6
 
7
7
  export type StepDatum = {
8
8
  bodyHtml?: string;
@@ -25,23 +25,34 @@ export type StepDatum = {
25
25
  textColor?: string;
26
26
  };
27
27
  primaryAction: {
28
- text: string;
29
- variant?: string;
30
28
  iconSprite?: string;
31
29
  iconSymbol?: string;
30
+ link?: string;
31
+ text: string;
32
+ variant?: string;
32
33
  };
33
34
  secondaryActionHtml?: string;
34
35
  showStepNumber?: boolean;
35
36
  stepTitle: string;
36
37
  };
37
38
 
39
+ function isNoAnimationCookieSet() {
40
+ return document.cookie
41
+ .split(";")
42
+ .some((ck) => ck.trim() === "no_j2s_animation=true");
43
+ }
44
+
38
45
  export default class J2sCardSequence extends LightningElement {
46
+ _didRenderStaticInnerHtml = false;
39
47
  _isDrmLoading = false;
48
+ _isFormSubmitting = false;
40
49
  _isJ2sFormInvalid = false;
41
50
  _isTbidLoading = false;
42
51
  _stepsData: StepDatum[] = [];
43
52
  @api animateTransitions = false;
44
53
  @api drmDomain: string = "https://drm.my.salesforce-sites.com";
54
+ @api formHandler: string =
55
+ "https://formhandlermule-1s9xrb.5sc6y6-4.usa-e2.cloudhub.io/form_handler";
45
56
  @api tbidApiBaseUrl: string | undefined;
46
57
  @api useHistory: boolean = false;
47
58
 
@@ -50,9 +61,9 @@ export default class J2sCardSequence extends LightningElement {
50
61
  return this._stepsData;
51
62
  }
52
63
  set stepsData(value) {
64
+ // normalize incoming value with uuid keys and non-required props (for cleaner HTML template), etc.
53
65
  this._stepsData = getListWithUuids<StepDatum>(toJson(value)).map(
54
66
  (stepDatum) => {
55
- // small bit of normalization for cleaner HTML templates with non-required props, etc.
56
67
  const normalizedStepDatum = {
57
68
  ...stepDatum,
58
69
  headerData: stepDatum.headerData || {},
@@ -79,11 +90,14 @@ export default class J2sCardSequence extends LightningElement {
79
90
 
80
91
  private countries = "[]";
81
92
  private currentSequenceStep = 0;
93
+ private recaptchaToken = "";
82
94
  private sessionStorageId = "j2s-card-sequence";
83
95
  private tbidUserInfo: any; // TODO typings?
84
96
 
85
97
  private get isLoading() {
86
- return this._isDrmLoading || this._isTbidLoading;
98
+ return (
99
+ this._isDrmLoading || this._isTbidLoading || this._isFormSubmitting
100
+ );
87
101
  }
88
102
 
89
103
  private get isLoggedIn() {
@@ -144,12 +158,26 @@ export default class J2sCardSequence extends LightningElement {
144
158
  );
145
159
  }
146
160
 
161
+ @api
162
+ recaptchaCallback(responseToken: string) {
163
+ this.recaptchaToken = responseToken;
164
+ }
165
+
166
+ @api
167
+ recaptchaExpiredCallback() {
168
+ this.recaptchaToken = "";
169
+ }
170
+
147
171
  connectedCallback() {
148
172
  this.getCountryList();
149
173
  this.getTbidUserInfo();
150
174
  // This has to be defined here since it depends on a variable that depends on sessionStorage.
151
175
  this.currentSequenceStep = this.initialStepIndex;
152
176
  this.resetCustomButtonColors();
177
+ if (isNoAnimationCookieSet()) {
178
+ /* eslint-disable-next-line @lwc/lwc/no-api-reassignments */
179
+ this.animateTransitions = false;
180
+ }
153
181
  }
154
182
 
155
183
  renderedCallback() {
@@ -157,24 +185,21 @@ export default class J2sCardSequence extends LightningElement {
157
185
  return;
158
186
  }
159
187
 
160
- const { bodyHtml, secondaryActionHtml } =
161
- this.stepsData[this.currentSequenceStep];
162
- const bodyMarkupContainer =
163
- this.template.querySelectorAll("span.body-markup")[
164
- this.currentSequenceStep
165
- ];
166
- const secondaryActionMarkupContainer = this.template.querySelectorAll(
167
- "span.secondary-action-markup"
168
- )[this.currentSequenceStep];
169
- if (bodyMarkupContainer) {
170
- bodyMarkupContainer.innerHTML = bodyHtml || "";
171
- }
172
- if (secondaryActionMarkupContainer) {
173
- secondaryActionMarkupContainer.innerHTML =
174
- secondaryActionHtml || "";
188
+ if (!this._didRenderStaticInnerHtml) {
189
+ this.updateStaticInnerHtml();
190
+ this._didRenderStaticInnerHtml = true;
175
191
  }
176
192
  }
177
193
 
194
+ handleFormValidityChange({ detail }: CustomEvent) {
195
+ this.isJ2sFormInvalid = !detail.isValid;
196
+ }
197
+
198
+ handleStepSequenceChange({ detail }: CustomEvent) {
199
+ this.currentSequenceStep = detail.stepIndex;
200
+ this.resetCustomButtonColors();
201
+ }
202
+
178
203
  handleGetStarted(evt: Event) {
179
204
  // If the user is not yet logged in, we need to prevent moving immediately to the next
180
205
  // step, but set storage up so that they are on the second step when they return to the
@@ -182,32 +207,82 @@ export default class J2sCardSequence extends LightningElement {
182
207
  if (!this.isLoggedIn) {
183
208
  evt.preventDefault();
184
209
  this.initialStepIndex = 1;
210
+ this._isTbidLoading = true;
185
211
  // @ts-ignore: call out to the tbid-controller on DSC
186
212
  window.__startTbidLogin();
187
213
  }
188
214
  }
189
215
 
190
- handleFormValidityChange({ detail }: CustomEvent) {
191
- this.isJ2sFormInvalid = !detail.isValid;
192
- }
216
+ async handleFormSubmit(evt: Event) {
217
+ if (!this.formHandler || this.isJ2sFormInvalid || !this.isLoggedIn) {
218
+ return;
219
+ }
193
220
 
194
- handleStepSequenceChange({ detail }: CustomEvent) {
195
- this.currentSequenceStep = detail.stepIndex;
196
- this.resetCustomButtonColors();
221
+ evt.preventDefault(); // don't proceed automatically to next step
222
+ this._isFormSubmitting = true;
223
+
224
+ try {
225
+ const formData = (this.template.querySelector("dw-j2s-form") as any)
226
+ ?.formDataForDrm;
227
+ // For some countries, explicit approvals will be present; for others, it is implicitly assumed
228
+ if (typeof formData.Opt_In__c !== "boolean") {
229
+ formData.Opt_In__c = true;
230
+ }
231
+ if (typeof formData.Privacy_Statement__c !== "boolean") {
232
+ formData.Privacy_Statement__c = true;
233
+ }
234
+
235
+ const res = await fetch(this.formHandler, {
236
+ method: "POST",
237
+ headers: {
238
+ "Captcha-Response": this.recaptchaToken
239
+ },
240
+ body: JSON.stringify({
241
+ ...formData,
242
+ handlerName: "J2S Registration Page",
243
+ TBID__c: this.tbidUserInfo.user_id
244
+ })
245
+ });
246
+
247
+ if (res.ok) {
248
+ const resData = await res.json();
249
+ if (resData.status === "success") {
250
+ this.jumpToStepIndex(2);
251
+ } else {
252
+ throw new Error(
253
+ `Form submission failed. (status: ${resData.status}, message: ${resData.errorMessage})`
254
+ );
255
+ }
256
+ }
257
+ } catch (ex) {
258
+ console.error(ex); // TODO
259
+ }
260
+
261
+ this._isFormSubmitting = false;
197
262
  }
198
263
 
199
264
  primaryActionHandler(evt: Event) {
200
- if ((evt.target as Button)?.disabled) {
265
+ const target = evt.target as HTMLButtonElement;
266
+
267
+ if (!target || target.disabled) {
201
268
  evt.preventDefault();
202
269
  return;
203
270
  }
204
271
 
272
+ track(target, "custEv_ctaButtonClick", {
273
+ click_text: target.textContent,
274
+ element_title: this.stepsData[this.currentSequenceStep].stepTitle,
275
+ click_url:
276
+ this.stepsData[this.currentSequenceStep].primaryAction.link ||
277
+ "",
278
+ element_type: "button",
279
+ content_category: "cta"
280
+ });
281
+
205
282
  if (!this.currentSequenceStep) {
206
283
  this.handleGetStarted(evt);
207
- } else if (this.currentSequenceStep === 2) {
208
- // TODO: send data to DRM
209
- } else if (this.currentSequenceStep === 3) {
210
- // TODO: Join Slack
284
+ } else if (this.currentSequenceStep === 1) {
285
+ this.handleFormSubmit(evt);
211
286
  }
212
287
  }
213
288
 
@@ -237,10 +312,42 @@ export default class J2sCardSequence extends LightningElement {
237
312
  );
238
313
  }
239
314
 
315
+ private jumpToStepIndex(stepIndex: number) {
316
+ this.initialStepIndex = stepIndex; // update sessionStorage value
317
+ ((this.template.querySelector(
318
+ "dx-step-sequence"
319
+ ) as unknown) as StepSequence)?.jumpToStep(stepIndex);
320
+ }
321
+
322
+ // HTML is received via config from DSC and should be static after initial render.
323
+ private updateStaticInnerHtml() {
324
+ const bodyMarkupContainers = this.template.querySelectorAll(
325
+ "span.body-markup"
326
+ );
327
+ const secondaryActionMarkupContainers = this.template.querySelectorAll(
328
+ "span.secondary-action-markup"
329
+ );
330
+
331
+ this.stepsData.forEach((step, stepIndex) => {
332
+ const { bodyHtml, secondaryActionHtml } = step;
333
+ const bodyMarkupContainer = bodyMarkupContainers[stepIndex];
334
+ const secondaryActionMarkupContainer =
335
+ secondaryActionMarkupContainers[stepIndex];
336
+
337
+ if (bodyMarkupContainer) {
338
+ bodyMarkupContainer.innerHTML = bodyHtml || "";
339
+ }
340
+ if (secondaryActionMarkupContainer) {
341
+ secondaryActionMarkupContainer.innerHTML =
342
+ secondaryActionHtml || "";
343
+ }
344
+ });
345
+ }
346
+
240
347
  private async getCountryList() {
241
348
  this._isDrmLoading = true;
242
349
 
243
- const errorMsg = "Error while getting county list";
350
+ const errorMsg = "Error while getting country list";
244
351
  try {
245
352
  const res = await fetch(
246
353
  `${this.drmDomain}/utilities/services/apexrest/countries`
@@ -252,6 +359,11 @@ export default class J2sCardSequence extends LightningElement {
252
359
  this.countries = JSON.stringify(data);
253
360
  } catch (error) {
254
361
  console.error(error);
362
+ if (this.initialStepIndex > 0) {
363
+ // Page loaded on a step higher than the first, but we can't get data for the form;
364
+ // send user back to the start for now.
365
+ this.jumpToStepIndex(0);
366
+ }
255
367
  }
256
368
 
257
369
  this._isDrmLoading = false;
@@ -272,12 +384,7 @@ export default class J2sCardSequence extends LightningElement {
272
384
  if (!this.tbidUserInfo && this.initialStepIndex > 0) {
273
385
  // login failed and the user was previously beyond the start step; need to send them
274
386
  // back to the start:
275
- this.initialStepIndex = 0;
276
- (
277
- this.template.querySelector(
278
- "dx-step-sequence"
279
- ) as unknown as StepSequence
280
- )?.jumpToStep(0);
387
+ this.jumpToStepIndex(0);
281
388
  }
282
389
 
283
390
  this._isTbidLoading = false;
@@ -1,46 +1,61 @@
1
- export const stepsData = [
2
- {
3
- showStepNumber: "true",
4
- stepTitle: "Get started now.",
5
- bodyHtml:
6
- '<p class="dx-text-display-7">Register for Journey to Salesforce and you\'ll be on your way to becoming a Salesforce Developer.</p><ul class="dx-text-display-8"><li>Step 1: Log in or sign up for a free Trailblazer.me account</li><li>Step 2: Confirm your program registration</li><li>Step 3: Begin your journey!</li></ul>',
7
- primaryActionText: "Get Started"
8
- },
9
- {
10
- showStepNumber: "true",
11
- stepTitle: "Complete your registration.",
12
- formData: {
13
- countriesWithReferralInput: ["IN"],
14
- countriewWithRequiredJobRole: ["CA"],
15
- notAvailableAlertInfo: {
16
- title: "Not Available",
17
- body: 'The Journey to Salesforce program is not currently available in your country. See the <a href="https://www.google.com/">program FAQs</a> for a list of participating countries.'
1
+ export const stepSequenceData = {
2
+ animateTransitions: "true",
3
+ stepsData: [
4
+ {
5
+ bodyHtml:
6
+ '<p class="dx-text-display-7">Register for Journey to Salesforce and you\'ll be on your way to becoming a Salesforce Developer.</p><ul class="dx-text-display-8"><li>Step 1: Log in or sign up for a free Trailblazer.me account</li><li>Step 2: Confirm your program registration</li><li>Step 3: Begin your journey!</li></ul>',
7
+ primaryAction: {
8
+ text: "Get Started"
18
9
  },
19
- participatingCountryCodes: ["US", "CA", "IN"]
10
+ showStepNumber: "true",
11
+ stepTitle: "Get started now."
20
12
  },
21
- bodyHtml:
22
- '<p class="dx-text-display-7">You\'re almost done! Please review your information and agree to the Journey to Salesforce program terms and conditions.</p>',
23
- secondaryActionHtml:
24
- '<span class="required-notice">* Required to participate in Journey to Salesforce</span>',
25
- primaryActionText: "Complete Registration"
26
- },
27
- {
28
- showStepNumber: "true",
29
- stepTitle: "Your Registration is Confirmed",
30
- headerData: {
31
- iconSymbol: "check-circle",
32
- iconSprite: "general",
33
- iconColor: "green-vibrant-65",
34
- backgroundColor: "green-vibrant-95",
35
- textColor: "indigo-vibrant-20"
13
+ {
14
+ bodyHtml:
15
+ '<p class="dx-text-display-7">You\'re almost done! Please review your information and agree to the Journey to Salesforce program terms and conditions.</p>',
16
+ formData: {
17
+ countriesWithReferralInput: ["IN"],
18
+ countriesWithRequiredJobRole: ["CA"],
19
+ formId: "j2s-form",
20
+ j2sTerms:
21
+ 'I agree to the Journey to Salesforce Program Terms, subject to the <a href="http://www.google.com">Salesforce Program Agreement</a>.',
22
+ notAvailableAlertInfo: {
23
+ title: "Not Available",
24
+ body:
25
+ 'The Journey to Salesforce program is not currently available in your country. See the <a href="https://www.google.com/">program FAQs</a> for a list of participating countries.'
26
+ },
27
+ participatingCountryCodes: ["US", "CA", "IN"]
28
+ },
29
+ primaryAction: {
30
+ text: "Complete Registration"
31
+ },
32
+ secondaryActionHtml:
33
+ '<span class="required-notice">* Required to participate in Journey to Salesforce</span>',
34
+ showStepNumber: "true",
35
+ stepTitle: "Complete your registration."
36
36
  },
37
- bodyHtml:
38
- '<div class="dx-text-display-8"><p>Welcome to Journey to Salesforce! Congratulations on taking this step in your journey towards becoming a Salesforce Developer. Through this program you\'ll gain the knowledge, experience, and skills to help you excel in the Salesforce ecosystem.</p><p>To get started, click the button below to join the Journey to Salesforce community, and make sure to check your email for links to bookmark!</p>',
39
- secondaryActionHtml:
40
- '<span class="dx-text-display-7">Ready to jump in?</span>',
41
- primaryActionText: "Join Slack",
42
- primaryActionVariant: "custom",
43
- primaryActionIconSprite: "utility",
44
- primaryActionIconSymbol: "new_window"
45
- }
46
- ];
37
+ {
38
+ bodyHtml:
39
+ '<div class="dx-text-display-8"><p>Welcome to Journey to Salesforce! Congratulations on taking this step in your journey towards becoming a Salesforce Developer. Through this program you\'ll gain the knowledge, experience, and skills to help you excel in the Salesforce ecosystem.</p><p>To get started, click the button below to join the Journey to Salesforce community, and make sure to check your email for links to bookmark!</p></div>',
40
+ headerData: {
41
+ backgroundColor: "green-vibrant-95",
42
+ iconSymbol: "check-circle",
43
+ iconSprite: "general",
44
+ iconColor: "green-vibrant-65",
45
+ textColor: "indigo-vibrant-20"
46
+ },
47
+ primaryAction: {
48
+ iconSprite: "utility",
49
+ iconSymbol: "new_window",
50
+ link:
51
+ "https://salesforce-external.slack.com/archives/C05HJK4PGJV",
52
+ text: "Join Slack",
53
+ variant: "custom"
54
+ },
55
+ secondaryActionHtml:
56
+ '<span class="dx-text-display-7">Ready to jump in?</span>',
57
+ showStepNumber: "true",
58
+ stepTitle: "Your Registration is Confirmed"
59
+ }
60
+ ]
61
+ };
@@ -21,8 +21,8 @@
21
21
  }
22
22
 
23
23
  .form-row .terms {
24
- --dx-c-checkbox-text-line-height: 20px;
25
- --dx-c-checkbox-font-size: 14px;
24
+ --dx-c-checkbox-text-line-height: var(--dx-g-text-lg);
25
+ --dx-c-checkbox-font-size: var(--dx-g-text-sm);
26
26
 
27
27
  font-family: var(--dx-g-font-sans);
28
28
  font-size: var(--dx-c-checkbox-font-size);
@@ -44,3 +44,9 @@
44
44
  .form-terms-container {
45
45
  padding-top: var(--dx-g-spacing-sm);
46
46
  }
47
+
48
+ .form-captcha-slot {
49
+ align-items: center;
50
+ display: flex;
51
+ justify-content: center;
52
+ }
@@ -1,11 +1,12 @@
1
1
  <template>
2
+ <!-- For the names of the form fields, see: https://salesforce.quip.com/daInAFETbPGm -->
2
3
  <form class="j2s-form">
3
4
  <div class="form-row">
4
5
  <dx-input
5
6
  class="first-name-field"
6
7
  label="First Name"
7
8
  missing-error-message="Enter your first name"
8
- name="first_name"
9
+ name="FirstName"
9
10
  placeholder="Your first name"
10
11
  required
11
12
  validity-on-blur
@@ -16,7 +17,7 @@
16
17
  class="last-name-field"
17
18
  label="Last Name"
18
19
  missing-error-message="Enter your last name"
19
- name="last_name"
20
+ name="LastName"
20
21
  placeholder="Your last name"
21
22
  required
22
23
  validity-on-blur
@@ -29,7 +30,7 @@
29
30
  class="company-name-field"
30
31
  label="Company Name"
31
32
  missing-error-message="Enter your company name"
32
- name="company_name"
33
+ name="Company__c"
33
34
  placeholder="Your company name"
34
35
  required
35
36
  validity-on-blur
@@ -41,7 +42,7 @@
41
42
  class="job-role-field"
42
43
  label="Job Role"
43
44
  message-when-value-missing="Select your job role"
44
- name="job_role"
45
+ name="DRM_Role__c"
45
46
  options={jobRoles}
46
47
  placeholder="Select Job Role"
47
48
  value={state.jobRole}
@@ -51,7 +52,7 @@
51
52
  class="job-role-field"
52
53
  label="Job Role"
53
54
  message-when-value-missing="Select your job role"
54
- name="job_role"
55
+ name="DRM_Role__c"
55
56
  options={jobRoles}
56
57
  placeholder="Select Job Role"
57
58
  required
@@ -61,7 +62,7 @@
61
62
  </div>
62
63
  <div class="form-row">
63
64
  <dx-input
64
- name="email"
65
+ name="Email"
65
66
  type="email"
66
67
  placeholder="Your email address"
67
68
  label="Email"
@@ -74,7 +75,7 @@
74
75
  <dx-select
75
76
  label="Country/Region"
76
77
  message-when-value-missing="Select your country/region"
77
- name="country"
78
+ name="MailingCountry"
78
79
  options={countries}
79
80
  placeholder="Select Country/Region"
80
81
  required
@@ -84,7 +85,7 @@
84
85
  </div>
85
86
  <div class="form-row" if:true={isCountryWithReferralInput}>
86
87
  <dx-input
87
- name="referral_email"
88
+ name="Referral_Email__c"
88
89
  class="referral-email-field full-width"
89
90
  label="If anyone has referred you, please share their Email ID below, with their consent."
90
91
  type="email"
@@ -102,7 +103,7 @@
102
103
  <div class="form-row" if:true={state.showMarketingText}>
103
104
  <dx-checkbox
104
105
  value="marketing-terms"
105
- name="marketing_terms"
106
+ name="Opt_In__c"
106
107
  required
107
108
  class="terms"
108
109
  onchange={handleRequiredFieldUpdate}
@@ -127,7 +128,7 @@
127
128
  <dx-checkbox
128
129
  if:true={state.showLegalCheckbox}
129
130
  value="legal-terms"
130
- name="legal_terms"
131
+ name="Privacy_Statement__c"
131
132
  required
132
133
  class="terms"
133
134
  onchange={handleRequiredFieldUpdate}
@@ -153,6 +154,7 @@
153
154
  class="legal-terms no-checkbox"
154
155
  ></span>
155
156
  </div>
157
+ <slot class="form-captcha-slot" name="form-captcha-slot"></slot>
156
158
  </div>
157
159
  </form>
158
160
  </template>
@@ -31,12 +31,14 @@ export type J2sFormState = {
31
31
  };
32
32
 
33
33
  export default class J2sForm extends LightningElementWithState<J2sFormState> {
34
- private _didSetFormId = false;
35
- private _isInitialRender = true;
36
- private _incomingUserInfo: any = {}; // TODO typings
37
- private _formMetadata: any = {}; // TODO typings
38
34
  private _countriesJson = "";
39
35
  private _countriesLookup: any = {}; // TODO typings
36
+ private _didSetFormId = false;
37
+ private _formMetadata: any = {}; // TODO typings
38
+ private _incomingUserInfo: any = {}; // TODO typings
39
+ private _isInitialRender = true;
40
+ private _recaptchaToken = "";
41
+
40
42
  @api id = "";
41
43
  @api referralText = "";
42
44
 
@@ -72,6 +74,33 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
72
74
  this._formMetadata = toJson(value);
73
75
  }
74
76
 
77
+ @api
78
+ get formDataForDrm() {
79
+ // We should be able to just get the formData from the form automatically using `new FormData(theForm)`,
80
+ // but our custom form elements don't seem to implement what it is required for that currently. See:
81
+ // https://web.dev/more-capable-form-controls/#defining-a-form-associated-custom-element
82
+ const formData: any = {};
83
+ this.template
84
+ .querySelectorAll("form [name]")
85
+ .forEach((namedElement) => {
86
+ const typedElement = namedElement as HTMLInputElement;
87
+ formData[typedElement.name] =
88
+ typeof typedElement.checked === "boolean"
89
+ ? typedElement.checked
90
+ : typedElement.value;
91
+ });
92
+ return formData;
93
+ }
94
+
95
+ @api
96
+ get recaptchaToken() {
97
+ return this._recaptchaToken;
98
+ }
99
+ set recaptchaToken(value: string) {
100
+ this._recaptchaToken = value;
101
+ this.handleRequiredFieldUpdate();
102
+ }
103
+
75
104
  private participatingCountryCodes = ["US", "CA", "IN"];
76
105
  private countriesWithReferralInput = ["IN"];
77
106
  private countriesWithRequiredJobRole = ["CA"];
@@ -141,7 +170,7 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
141
170
  if (this.state.learnMore) {
142
171
  this.updateInnerHtml(
143
172
  "span.legal-terms-learn-more",
144
- this.state.learnMore
173
+ `&nbsp;${this.state.learnMore}`
145
174
  );
146
175
  } else {
147
176
  this.updateInnerHtml("span.legal-terms-learn-more", "");
@@ -173,19 +202,8 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
173
202
  }
174
203
  }
175
204
 
176
- private updateInnerHtml(selector: string, html: string): void {
177
- const element = this.template.querySelector(selector);
178
- if (element) {
179
- element.innerHTML = html;
180
- }
181
- }
182
-
183
- private isParticipatingCountry(country: string) {
184
- return this.participatingCountryCodes.includes(country);
185
- }
186
-
187
205
  handleRequiredFieldUpdate() {
188
- const isFormNowValid = this.checkValidity(this.state.country);
206
+ const isFormNowValid = this.checkValidity();
189
207
  if (this.state.isFormValid !== isFormNowValid) {
190
208
  this.setState({
191
209
  isFormValid: isFormNowValid
@@ -196,14 +214,22 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
196
214
  handleCountryChange({ detail }: CustomEvent) {
197
215
  const { value } = detail;
198
216
  const countryData = this._countriesLookup[value];
217
+
199
218
  this.setState({
200
219
  country: value,
201
- isFormValid: this.checkValidity(value),
202
220
  learnMore: countryData.learnMore,
203
221
  legalText: countryData.legalText,
204
222
  marketingText: countryData.marketingText,
205
223
  showLegalCheckbox: countryData.showLegalCheckbox
206
224
  });
225
+
226
+ // Because updating the country can update the required fields, we need to re-set form
227
+ // validity after render
228
+ requestAnimationFrame(() =>
229
+ this.setState({
230
+ isFormValid: this.checkValidity()
231
+ })
232
+ );
207
233
  }
208
234
 
209
235
  handleLearnMore() {
@@ -212,9 +238,21 @@ export default class J2sForm extends LightningElementWithState<J2sFormState> {
212
238
  ?.classList.toggle("hidden");
213
239
  }
214
240
 
215
- private checkValidity(country: string) {
241
+ private updateInnerHtml(selector: string, html: string): void {
242
+ const element = this.template.querySelector(selector);
243
+ if (element) {
244
+ element.innerHTML = html;
245
+ }
246
+ }
247
+
248
+ private isParticipatingCountry(country: string) {
249
+ return this.participatingCountryCodes.includes(country);
250
+ }
251
+
252
+ private checkValidity() {
216
253
  return (
217
- this.isParticipatingCountry(country) &&
254
+ Boolean(this.recaptchaToken) &&
255
+ this.isParticipatingCountry(this.state.country) &&
218
256
  this.requiredFormFields.every(
219
257
  (requiredFormField) =>
220
258
  // @ts-ignore