@salesforcedevs/dw-components 1.3.397-canary.3 → 1.3.397-canary.5

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/lwc.config.json CHANGED
@@ -25,6 +25,7 @@
25
25
  "dw/podcastsGrid",
26
26
  "dw/podcastsPromo",
27
27
  "dw/rcgTable",
28
+ "dw/recaptcha",
28
29
  "dw/slackCta",
29
30
  "dw/slackSuccess",
30
31
  "dw/tbidLoginMenu",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dw-components",
3
- "version": "1.3.397-canary.3",
3
+ "version": "1.3.397-canary.5",
4
4
  "description": "Lightning web components for https://developer.salesforce.com",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -117,7 +117,11 @@
117
117
  <span lwc:dom="manual" class="legal-checkbox privacy-terms"></span>
118
118
  </dx-checkbox>
119
119
 
120
- <slot class="form-captcha-slot" name="form-captcha-slot"></slot>
120
+ <slot
121
+ class="form-captcha-slot"
122
+ name="form-captcha-slot"
123
+ onslotchange={onSlotChange}
124
+ ></slot>
121
125
 
122
126
  <dx-button
123
127
  class="submit-button"
@@ -4,6 +4,7 @@ import { track as trackGtm } from "dxUtils/analytics";
4
4
  import { submitForm, ErrorCode, SignupForm, UserData } from "./submitForm";
5
5
  import { CountryOptions } from "typings/custom";
6
6
  import { stateMap } from "dwUtils/forms";
7
+ import Recaptcha from "packages/@salesforcedevs/dw-components/src/modules/dw/recaptcha/recaptcha";
7
8
 
8
9
  type SelectOption = { value: string; label: string };
9
10
  type Country = SelectOption & {
@@ -47,7 +48,7 @@ export default class DeSignupForm extends LightningElement {
47
48
  private marketingText: string = "";
48
49
  private showLegalCheckbox: boolean = false;
49
50
  private privacyText: string = "";
50
- private recaptchaToken = "";
51
+ private recaptchaComponent: Recaptcha | null = null;
51
52
 
52
53
  @track knownDuplicateUsers: string[] = [];
53
54
 
@@ -74,26 +75,6 @@ export default class DeSignupForm extends LightningElement {
74
75
  return stateMap[this.country] || [];
75
76
  }
76
77
 
77
- @api
78
- recaptchaCallback(responseToken: string) {
79
- this.recaptchaToken = responseToken;
80
- }
81
-
82
- @api
83
- recaptchaExpiredCallback() {
84
- this.recaptchaToken = "";
85
- }
86
-
87
- @api
88
- recaptchaErrorCallback() {
89
- this.resetRecaptcha();
90
- }
91
-
92
- private resetRecaptcha() {
93
- this.recaptchaToken = "";
94
- (window as any).grecaptcha?.reset();
95
- }
96
-
97
78
  connectedCallback(): void {
98
79
  this.getCountryList();
99
80
  }
@@ -104,6 +85,10 @@ export default class DeSignupForm extends LightningElement {
104
85
  this.updateInnerHtml("span.legal-checkbox", this.privacyText);
105
86
  }
106
87
 
88
+ onSlotChange(e: Event) {
89
+ this.recaptchaComponent = e.target as Recaptcha;
90
+ }
91
+
107
92
  createUUID() {
108
93
  if ("crypto" in window && "randomUUID" in crypto) {
109
94
  return crypto.randomUUID();
@@ -169,7 +154,7 @@ export default class DeSignupForm extends LightningElement {
169
154
  }
170
155
 
171
156
  const formData = this.prepareUserData();
172
- if (!formData || !this.recaptchaToken) {
157
+ if (!formData) {
173
158
  return;
174
159
  }
175
160
 
@@ -188,7 +173,7 @@ export default class DeSignupForm extends LightningElement {
188
173
  );
189
174
 
190
175
  this.setSubmitButtonDisabled(true);
191
- submitForm(formData, this.recaptchaToken)
176
+ submitForm(formData)
192
177
  .then(() => {
193
178
  trackGtm(event.target!, "custEv_signUp", {
194
179
  authentication_method: "email",
@@ -212,7 +197,7 @@ export default class DeSignupForm extends LightningElement {
212
197
  "/developer-legacy/signup/account-verification";
213
198
  } else {
214
199
  this.setSubmitButtonDisabled(false);
215
- this.resetRecaptcha();
200
+ this.recaptchaComponent?.reset();
216
201
  if (error.message === ErrorCode.DUPLICATE) {
217
202
  this.knownDuplicateUsers.push(formData.user.username);
218
203
  } else {
@@ -257,7 +242,15 @@ export default class DeSignupForm extends LightningElement {
257
242
  validCheckboxes = validCheckboxes && checkbox.reportValidity();
258
243
  });
259
244
 
260
- if (fields.some((field) => !field.valid) || !validCheckboxes) {
245
+ const hasInvalidCaptcha =
246
+ this.recaptchaComponent &&
247
+ !this.recaptchaComponent.reportValidity();
248
+
249
+ if (
250
+ fields.some((field) => !field.valid) ||
251
+ !validCheckboxes ||
252
+ hasInvalidCaptcha
253
+ ) {
261
254
  return null;
262
255
  }
263
256
 
@@ -277,7 +270,8 @@ export default class DeSignupForm extends LightningElement {
277
270
  user: userData as UserData,
278
271
  default_driver: DEFAULT_DRIVER,
279
272
  info_email: InfoEmail,
280
- form_join_id_unique: this.createUUID()
273
+ form_join_id_unique: this.createUUID(),
274
+ "g-recaptcha-response": this.recaptchaComponent?.value
281
275
  };
282
276
 
283
277
  return formData;
@@ -24,6 +24,7 @@ export type SignupForm = {
24
24
  default_driver: string;
25
25
  info_email: "True" | "False";
26
26
  form_join_id_unique: string;
27
+ "g-recaptcha-response"?: string;
27
28
  };
28
29
 
29
30
  type OrgStatus = "New" | "In Progress" | "Error";
@@ -79,19 +80,13 @@ const checkOrgStatus = (id: string, retries = 0): Promise<void> => {
79
80
  });
80
81
  };
81
82
 
82
- export const submitForm = async (
83
- formData: SignupForm,
84
- recaptchaToken: string
85
- ): Promise<void> => {
83
+ export const submitForm = async (formData: SignupForm): Promise<void> => {
86
84
  const response = await fetch(CREATE_PATH, {
87
85
  method: "POST",
88
86
  headers: {
89
87
  "Content-Type": "application/json"
90
88
  },
91
- body: JSON.stringify({
92
- ...formData,
93
- "g-recaptcha-response": recaptchaToken
94
- })
89
+ body: JSON.stringify(formData)
95
90
  });
96
91
 
97
92
  if (!response.ok) {
@@ -0,0 +1,14 @@
1
+ @import "dxHelpers/reset";
2
+ @import "dxHelpers/text";
3
+
4
+ .container {
5
+ width: 100%;
6
+ display: flex;
7
+ justify-content: center;
8
+ margin-bottom: var(--dx-g-spacing-lg);
9
+ }
10
+
11
+ .error-message-box {
12
+ margin-top: var(--dx-g-spacing-xs);
13
+ display: block;
14
+ }
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div class="container">
3
+ <div class="g-recaptcha"></div>
4
+ <div
5
+ if:true={errorMessage}
6
+ aria-live="assertive"
7
+ class="error-message-box dx-text-form-error-message"
8
+ >
9
+ {errorMessage}
10
+ </div>
11
+ </div>
12
+ </template>
@@ -0,0 +1,94 @@
1
+ import { api, LightningElement } from "lwc";
2
+
3
+ // NOTE: This component assumes it is the only Recaptcha widget on a page.
4
+ type RecaptchaApi = {
5
+ render(element: HTMLElement | null, recaptchaConfig: any): void;
6
+ reset(): void;
7
+ };
8
+
9
+ export default class Recaptcha extends LightningElement {
10
+ private errorMessage = "";
11
+ private recaptchaDiv!: HTMLDivElement;
12
+ private recaptchaApi?: RecaptchaApi;
13
+ private recaptchaToken = "";
14
+
15
+ @api missingErrorMessage = "Complete the captcha";
16
+ @api recaptchaSiteKey = "";
17
+
18
+ @api // read only
19
+ get value() {
20
+ return this.recaptchaToken;
21
+ }
22
+
23
+ @api
24
+ recaptchaScriptOnLoad = (recaptchaApi: RecaptchaApi) => {
25
+ this.recaptchaApi = recaptchaApi;
26
+ if (this.recaptchaDiv) {
27
+ this.renderRecaptcha();
28
+ }
29
+ };
30
+
31
+ @api
32
+ reset = () => this.resetRecaptcha();
33
+
34
+ @api
35
+ reportValidity = () => {
36
+ if (!this.checkValidity()) {
37
+ this.errorMessage = this.missingErrorMessage;
38
+ return false;
39
+ }
40
+
41
+ this.errorMessage = "";
42
+ return true;
43
+ };
44
+
45
+ @api
46
+ checkValidity = () => {
47
+ return Boolean(this.recaptchaToken);
48
+ };
49
+
50
+ renderedCallback() {
51
+ if (!this.recaptchaDiv) {
52
+ this.recaptchaDiv = this.template.querySelector(
53
+ ".g-recaptcha"
54
+ ) as HTMLDivElement;
55
+ if (this.recaptchaApi) {
56
+ this.renderRecaptcha();
57
+ }
58
+ }
59
+ }
60
+
61
+ private renderRecaptcha = () => {
62
+ const smallMatchMedia = window.matchMedia(
63
+ "screen and (max-width: 768px)"
64
+ );
65
+
66
+ // render recaptcha, referencing this component's callbacks for handling events
67
+ this.recaptchaApi?.render(this.recaptchaDiv, {
68
+ sitekey: this.recaptchaSiteKey,
69
+ callback: this.recaptchaCallback,
70
+ "expired-callback": this.recaptchaExpiredCallback,
71
+ "error-callback": this.recaptchaErrorCallback,
72
+ size: smallMatchMedia.matches ? "compact" : "normal"
73
+ });
74
+ };
75
+
76
+ private recaptchaCallback = (token: string) => {
77
+ this.recaptchaToken = token;
78
+ this.errorMessage = "";
79
+ };
80
+
81
+ private recaptchaExpiredCallback = () => {
82
+ this.recaptchaToken = ""; // note: recaptcha element itself handles showing an "expired" message
83
+ };
84
+
85
+ private recaptchaErrorCallback = () => {
86
+ this.resetRecaptcha();
87
+ this.errorMessage = ""; // this error message is only set to a value when the form is submitted or `reportValidity` is called explicitly
88
+ };
89
+
90
+ private resetRecaptcha = () => {
91
+ this.recaptchaToken = "";
92
+ this.recaptchaApi?.reset();
93
+ };
94
+ }