Skip to content

Commit 3fdcdbf

Browse files
authoredOct 23, 2024··
feat(clerk-js): Legal consent (#4337)
1 parent 3d5512f commit 3fdcdbf

32 files changed

+598
-118
lines changed
 

‎.changeset/curvy-ghosts-relax.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@clerk/localizations": minor
3+
"@clerk/clerk-js": minor
4+
"@clerk/types": minor
5+
---
6+
7+
Adding experimental support for legal consent for `<SignUp/>` component

‎packages/clerk-js/bundlewatch.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"files": [
33
{ "path": "./dist/clerk.browser.js", "maxSize": "68kB" },
44
{ "path": "./dist/clerk.headless.js", "maxSize": "44kB" },
5-
{ "path": "./dist/ui-common*.js", "maxSize": "86KB" },
5+
{ "path": "./dist/ui-common*.js", "maxSize": "87KB" },
66
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
77
{ "path": "./dist/coinbase*.js", "maxSize": "58KB" },
88
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },

‎packages/clerk-js/src/core/clerk.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ export class Clerk implements ClerkInterface {
14001400
return this.client?.signUp.create({
14011401
strategy: 'google_one_tap',
14021402
token: params.token,
1403+
__experimental_legalAccepted: params.__experimental_legalAccepted,
14031404
});
14041405
}
14051406
throw err;
@@ -1420,6 +1421,7 @@ export class Clerk implements ClerkInterface {
14201421
customNavigate,
14211422
unsafeMetadata,
14221423
strategy,
1424+
__experimental_legalAccepted,
14231425
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
14241426
if (!this.client || !this.environment) {
14251427
return;
@@ -1442,6 +1444,7 @@ export class Clerk implements ClerkInterface {
14421444
generateSignature,
14431445
unsafeMetadata,
14441446
strategy,
1447+
__experimental_legalAccepted,
14451448
});
14461449

14471450
if (

‎packages/clerk-js/src/core/resources/DisplayConfig.ts

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource
4444
afterCreateOrganizationUrl!: string;
4545
googleOneTapClientId?: string;
4646
showDevModeWarning!: boolean;
47+
termsUrl!: string;
48+
privacyPolicyUrl!: string;
4749

4850
public constructor(data: DisplayConfigJSON) {
4951
super();
@@ -87,6 +89,8 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource
8789
this.afterCreateOrganizationUrl = data.after_create_organization_url;
8890
this.googleOneTapClientId = data.google_one_tap_client_id;
8991
this.showDevModeWarning = data.show_devmode_warning;
92+
this.termsUrl = data.terms_url;
93+
this.privacyPolicyUrl = data.privacy_policy_url;
9094
return this;
9195
}
9296
}

‎packages/clerk-js/src/core/resources/SignUp.ts

+39-5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export class SignUp extends BaseResource implements SignUpResource {
7070
createdSessionId: string | null = null;
7171
createdUserId: string | null = null;
7272
abandonAt: number | null = null;
73+
legalAcceptedAt: number | null = null;
7374

7475
constructor(data: SignUpJSON | null = null) {
7576
super();
@@ -111,6 +112,12 @@ export class SignUp extends BaseResource implements SignUpResource {
111112
paramsWithCaptcha.strategy = SignUp.clerk.client?.signIn.firstFactorVerification.strategy;
112113
}
113114

115+
// TODO(@vaggelis): Remove this once the legalAccepted is stable
116+
if (typeof params.__experimental_legalAccepted !== 'undefined') {
117+
paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted;
118+
paramsWithCaptcha.__experimental_legalAccepted = undefined;
119+
}
120+
114121
return this._basePost({
115122
path: this.pathRoot,
116123
body: normalizeUnsafeMetadata(paramsWithCaptcha),
@@ -190,17 +197,26 @@ export class SignUp extends BaseResource implements SignUpResource {
190197
};
191198

192199
public authenticateWithWeb3 = async (
193-
params: AuthenticateWithWeb3Params & { unsafeMetadata?: SignUpUnsafeMetadata },
200+
params: AuthenticateWithWeb3Params & {
201+
unsafeMetadata?: SignUpUnsafeMetadata;
202+
__experimental_legalAccepted?: boolean;
203+
},
194204
): Promise<SignUpResource> => {
195-
const { generateSignature, identifier, unsafeMetadata, strategy = 'web3_metamask_signature' } = params || {};
205+
const {
206+
generateSignature,
207+
identifier,
208+
unsafeMetadata,
209+
strategy = 'web3_metamask_signature',
210+
__experimental_legalAccepted,
211+
} = params || {};
196212
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
197213

198214
if (!(typeof generateSignature === 'function')) {
199215
clerkMissingOptionError('generateSignature');
200216
}
201217

202218
const web3Wallet = identifier || this.web3wallet!;
203-
await this.create({ web3Wallet, unsafeMetadata });
219+
await this.create({ web3Wallet, unsafeMetadata, __experimental_legalAccepted: __experimental_legalAccepted });
204220
await this.prepareWeb3WalletVerification({ strategy });
205221

206222
const { message } = this.verifications.web3Wallet;
@@ -229,25 +245,33 @@ export class SignUp extends BaseResource implements SignUpResource {
229245
return this.attemptWeb3WalletVerification({ signature, strategy });
230246
};
231247

232-
public authenticateWithMetamask = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
248+
public authenticateWithMetamask = async (
249+
params?: SignUpAuthenticateWithWeb3Params & {
250+
__experimental_legalAccepted?: boolean;
251+
},
252+
): Promise<SignUpResource> => {
233253
const identifier = await getMetamaskIdentifier();
234254
return this.authenticateWithWeb3({
235255
identifier,
236256
generateSignature: generateSignatureWithMetamask,
237257
unsafeMetadata: params?.unsafeMetadata,
238258
strategy: 'web3_metamask_signature',
259+
__experimental_legalAccepted: params?.__experimental_legalAccepted,
239260
});
240261
};
241262

242263
public authenticateWithCoinbaseWallet = async (
243-
params?: SignUpAuthenticateWithWeb3Params,
264+
params?: SignUpAuthenticateWithWeb3Params & {
265+
__experimental_legalAccepted?: boolean;
266+
},
244267
): Promise<SignUpResource> => {
245268
const identifier = await getCoinbaseWalletIdentifier();
246269
return this.authenticateWithWeb3({
247270
identifier,
248271
generateSignature: generateSignatureWithCoinbaseWallet,
249272
unsafeMetadata: params?.unsafeMetadata,
250273
strategy: 'web3_coinbase_wallet_signature',
274+
__experimental_legalAccepted: params?.__experimental_legalAccepted,
251275
});
252276
};
253277

@@ -258,6 +282,7 @@ export class SignUp extends BaseResource implements SignUpResource {
258282
continueSignUp = false,
259283
unsafeMetadata,
260284
emailAddress,
285+
__experimental_legalAccepted,
261286
}: AuthenticateWithRedirectParams & {
262287
unsafeMetadata?: SignUpUnsafeMetadata;
263288
}): Promise<void> => {
@@ -268,6 +293,7 @@ export class SignUp extends BaseResource implements SignUpResource {
268293
actionCompleteRedirectUrl: redirectUrlComplete,
269294
unsafeMetadata,
270295
emailAddress,
296+
__experimental_legalAccepted,
271297
};
272298
return continueSignUp && this.id ? this.update(params) : this.create(params);
273299
};
@@ -294,6 +320,13 @@ export class SignUp extends BaseResource implements SignUpResource {
294320
};
295321

296322
update = (params: SignUpUpdateParams): Promise<SignUpResource> => {
323+
// TODO(@vaggelis): Remove this once the legalAccepted is stable
324+
if (typeof params.__experimental_legalAccepted !== 'undefined') {
325+
// @ts-expect-error - We need to remove the __experimental_legalAccepted key from the params
326+
params.legalAccepted = params.__experimental_legalAccepted;
327+
params.__experimental_legalAccepted = undefined;
328+
}
329+
297330
return this._basePatch({
298331
body: normalizeUnsafeMetadata(params),
299332
});
@@ -328,6 +361,7 @@ export class SignUp extends BaseResource implements SignUpResource {
328361
this.createdUserId = data.created_user_id;
329362
this.abandonAt = data.abandon_at;
330363
this.web3wallet = data.web3_wallet;
364+
this.legalAcceptedAt = data.legal_accepted_at;
331365
}
332366
return this;
333367
}

‎packages/clerk-js/src/core/resources/User.ts

+5
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export class User extends BaseResource implements UserResource {
8787
createOrganizationsLimit: number | null = null;
8888
deleteSelfEnabled = false;
8989
lastSignInAt: Date | null = null;
90+
legalAcceptedAt: Date | null = null;
9091
updatedAt: Date | null = null;
9192
createdAt: Date | null = null;
9293

@@ -360,6 +361,10 @@ export class User extends BaseResource implements UserResource {
360361
this.lastSignInAt = unixEpochToDate(data.last_sign_in_at);
361362
}
362363

364+
if (data.legal_accepted_at) {
365+
this.legalAcceptedAt = unixEpochToDate(data.legal_accepted_at);
366+
}
367+
363368
this.updatedAt = unixEpochToDate(data.updated_at);
364369
this.createdAt = unixEpochToDate(data.created_at);
365370
return this;

‎packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx

+34-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useClerk } from '@clerk/shared/react';
2-
import React from 'react';
2+
import React, { useMemo } from 'react';
33

44
import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
55
import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
@@ -38,12 +38,6 @@ function _SignUpContinue() {
3838
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
3939
);
4040

41-
// Redirect to sign-up if there is no persisted sign-up
42-
if (!signUp.id) {
43-
void navigate(displayConfig.signUpUrl);
44-
return <LoadingCard />;
45-
}
46-
4741
// TODO: This form should be shared between SignUpStart and SignUpContinue
4842
const formState = {
4943
firstName: useFormControl('firstName', initialValues.firstName || '', {
@@ -77,8 +71,25 @@ function _SignUpContinue() {
7771
placeholder: localizationKeys('formFieldInputPlaceholder__password'),
7872
validatePassword: true,
7973
}),
74+
__experimental_legalAccepted: useFormControl('__experimental_legalAccepted', '', {
75+
type: 'checkbox',
76+
label: '',
77+
defaultChecked: false,
78+
isRequired: userSettings.signUp.legal_consent_enabled || false,
79+
}),
8080
} as const;
8181

82+
const onlyLegalConsentMissing = useMemo(
83+
() => signUp.missingFields && signUp.missingFields.length === 1 && signUp.missingFields[0] === 'legal_accepted',
84+
[signUp.missingFields],
85+
);
86+
87+
// Redirect to sign-up if there is no persisted sign-up
88+
if (!signUp.id) {
89+
void navigate(displayConfig.signUpUrl);
90+
return <LoadingCard />;
91+
}
92+
8293
const hasEmail = !!formState.emailAddress.value;
8394
const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status == 'verified';
8495
const hasVerifiedWeb3 = signUp.verifications?.web3Wallet?.status == 'verified';
@@ -89,6 +100,7 @@ function _SignUpContinue() {
89100
activeCommIdentifierType,
90101
signUp,
91102
isProgressiveSignUp,
103+
legalConsentRequired: userSettings.signUp.legal_consent_enabled,
92104
});
93105
minimizeFieldsForExistingSignup(fields, signUp);
94106

@@ -131,12 +143,13 @@ function _SignUpContinue() {
131143
!phoneNumberProvided &&
132144
emailOrPhone(attributes, isProgressiveSignUp)
133145
) {
134-
fieldsToSubmit.push(formState['emailAddress']);
135-
fieldsToSubmit.push(formState['phoneNumber']);
146+
fieldsToSubmit.push(formState.emailAddress);
147+
fieldsToSubmit.push(formState.phoneNumber);
136148
}
137149

138150
card.setLoading();
139151
card.setError(undefined);
152+
140153
return signUp
141154
.update(buildRequest(fieldsToSubmit))
142155
.then(res =>
@@ -156,13 +169,21 @@ function _SignUpContinue() {
156169
const showOauthProviders = !hasVerifiedExternalAccount && oauthOptions.length > 0;
157170
const showWeb3Providers = !hasVerifiedWeb3 && web3Options.length > 0;
158171

172+
const headerTitle = !onlyLegalConsentMissing
173+
? localizationKeys('signUp.continue.title')
174+
: localizationKeys('signUp.__experimental_legalConsent.continue.title');
175+
176+
const headerSubtitle = !onlyLegalConsentMissing
177+
? localizationKeys('signUp.continue.subtitle')
178+
: localizationKeys('signUp.__experimental_legalConsent.continue.subtitle');
179+
159180
return (
160181
<Flow.Part part='complete'>
161182
<Card.Root>
162183
<Card.Content>
163184
<Header.Root showLogo>
164-
<Header.Title localizationKey={localizationKeys('signUp.continue.title')} />
165-
<Header.Subtitle localizationKey={localizationKeys('signUp.continue.subtitle')} />
185+
<Header.Title localizationKey={headerTitle} />
186+
<Header.Subtitle localizationKey={headerSubtitle} />
166187
</Header.Root>
167188
<Card.Alert>{card.error}</Card.Alert>
168189
<Flex
@@ -171,7 +192,7 @@ function _SignUpContinue() {
171192
gap={8}
172193
>
173194
<SocialButtonsReversibleContainerWithDivider>
174-
{(showOauthProviders || showWeb3Providers) && (
195+
{(showOauthProviders || showWeb3Providers) && !onlyLegalConsentMissing && (
175196
<SignUpSocialButtons
176197
enableOAuthProviders={showOauthProviders}
177198
enableWeb3Providers={showWeb3Providers}
@@ -182,6 +203,7 @@ function _SignUpContinue() {
182203
handleSubmit={handleSubmit}
183204
fields={fields}
184205
formState={formState}
206+
onlyLegalAcceptedMissing={onlyLegalConsentMissing}
185207
canToggleEmailPhone={canToggleEmailPhone}
186208
handleEmailPhoneToggle={handleChangeActive}
187209
/>

0 commit comments

Comments
 (0)
Please sign in to comment.