@salesforcedevs/dw-components 1.3.397-canary.2 → 1.3.397-canary.4

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.2",
3
+ "version": "1.3.397-canary.4",
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();
@@ -147,7 +132,7 @@ export default class DeSignupForm extends LightningElement {
147
132
  ) {
148
133
  trackGtm(target!, "custEv_formCompletion", {
149
134
  converting_page: `SFDC:us:signup:${convertingPage}`,
150
- converting_page_url: `https://developer.salesforce.com/signup/${convertingPage}`,
135
+ converting_page_url: `https://developer.salesforce.com/developer-legacy/signup/${convertingPage}`,
151
136
  form_country: formData.user.country,
152
137
  form_name: "Dev Edition Signup",
153
138
  form_primary_product_interest: "Developer Edition",
@@ -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",
@@ -196,7 +181,7 @@ export default class DeSignupForm extends LightningElement {
196
181
  new_registration: true
197
182
  });
198
183
  this.trackFormCompletion(event.target!, formData, "success");
199
- window.location.href = "/signup/success";
184
+ window.location.href = "/developer-legacy/signup/success";
200
185
  })
201
186
  .catch((error) => {
202
187
  if (error.message === ErrorCode.GENERIC && didSubmitTimeout) {
@@ -208,10 +193,11 @@ export default class DeSignupForm extends LightningElement {
208
193
  // If the timeout time has passed at the point of error, and the error code is simply "GENERIC",
209
194
  // we do not know whether the org will ultimately be created (and we have no current way of confirming),
210
195
  // so we navigate the user to an interim page that tells them to watch their email.
211
- window.location.href = "/signup/account-verification";
196
+ window.location.href =
197
+ "/developer-legacy/signup/account-verification";
212
198
  } else {
213
199
  this.setSubmitButtonDisabled(false);
214
- this.resetRecaptcha();
200
+ this.recaptchaComponent?.reset();
215
201
  if (error.message === ErrorCode.DUPLICATE) {
216
202
  this.knownDuplicateUsers.push(formData.user.username);
217
203
  } else {
@@ -256,7 +242,15 @@ export default class DeSignupForm extends LightningElement {
256
242
  validCheckboxes = validCheckboxes && checkbox.reportValidity();
257
243
  });
258
244
 
259
- 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
+ ) {
260
254
  return null;
261
255
  }
262
256
 
@@ -276,7 +270,8 @@ export default class DeSignupForm extends LightningElement {
276
270
  user: userData as UserData,
277
271
  default_driver: DEFAULT_DRIVER,
278
272
  info_email: InfoEmail,
279
- form_join_id_unique: this.createUUID()
273
+ form_join_id_unique: this.createUUID(),
274
+ "g-recaptcha-response": this.recaptchaComponent?.value
280
275
  };
281
276
 
282
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
+ <slot onslotchange={onSlotChange}></slot>
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,82 @@
1
+ import { api, LightningElement } from "lwc";
2
+
3
+ declare module globalThis {
4
+ let grecaptcha: {
5
+ render(element: HTMLElement | null, recaptchaConfig: any): void;
6
+ reset(): void;
7
+ };
8
+ }
9
+
10
+ export default class Recaptcha extends LightningElement {
11
+ private _recaptchaToken = "";
12
+ private errorMessage = "";
13
+ private slottedRecaptchaDiv: HTMLDivElement | null = null;
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
+ reset = () => this.resetRecaptcha();
25
+
26
+ @api
27
+ reportValidity = () => {
28
+ if (!this.checkValidity()) {
29
+ this.errorMessage = this.missingErrorMessage;
30
+ return false;
31
+ }
32
+
33
+ this.errorMessage = "";
34
+ return true;
35
+ };
36
+
37
+ @api
38
+ checkValidity = () => {
39
+ return Boolean(
40
+ (
41
+ this.slottedRecaptchaDiv?.querySelector(
42
+ "#g-recaptcha-response"
43
+ ) as HTMLTextAreaElement
44
+ )?.value
45
+ );
46
+ };
47
+
48
+ private onSlotChange(e: Event) {
49
+ this.slottedRecaptchaDiv = e.target as HTMLDivElement;
50
+ const smallMatchMedia = window.matchMedia(
51
+ "screen and (max-width: 768px)"
52
+ );
53
+
54
+ // render recaptcha, referencing this component's callbacks for handling events
55
+ globalThis.grecaptcha?.render(this.slottedRecaptchaDiv, {
56
+ sitekey: this.recaptchaSiteKey,
57
+ callback: this.recaptchaCallback,
58
+ "expired-callback": this.recaptchaExpiredCallback,
59
+ "error-callback": this.recaptchaErrorCallback,
60
+ size: smallMatchMedia.matches ? "compact" : "normal"
61
+ });
62
+ }
63
+
64
+ private recaptchaCallback = (token: string) => {
65
+ this._recaptchaToken = token;
66
+ this.errorMessage = "";
67
+ };
68
+
69
+ private recaptchaExpiredCallback = () => {
70
+ this._recaptchaToken = ""; // note: recaptcha element itself handles showing an "expired" message
71
+ };
72
+
73
+ private recaptchaErrorCallback = () => {
74
+ this.resetRecaptcha();
75
+ this.errorMessage = ""; // this error message is only set to a value when the form is submitted or `reportValidity` is called explicitly
76
+ };
77
+
78
+ private resetRecaptcha = () => {
79
+ this._recaptchaToken = "";
80
+ globalThis.grecaptcha?.reset();
81
+ };
82
+ }