From 9c54f49f4491cd5d50f657ef0545f54cdb1e08e8 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Mon, 30 Sep 2024 23:10:59 +0300 Subject: [PATCH 01/27] feat(clerk-js): Add legal consent for Sign Up --- packages/clerk-js/src/core/resources/SignUp.ts | 4 ++++ packages/clerk-js/src/core/resources/User.ts | 5 +++++ packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx | 3 +++ packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx | 6 ++++++ packages/types/src/elementIds.ts | 3 ++- packages/types/src/json.ts | 2 ++ packages/types/src/redirects.ts | 5 +++++ packages/types/src/signUp.ts | 2 ++ 8 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index bf7a06932ea..4fb3a841d8d 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -70,6 +70,7 @@ export class SignUp extends BaseResource implements SignUpResource { createdSessionId: string | null = null; createdUserId: string | null = null; abandonAt: number | null = null; + legalAcceptedAt: number | null = null; constructor(data: SignUpJSON | null = null) { super(); @@ -258,6 +259,7 @@ export class SignUp extends BaseResource implements SignUpResource { continueSignUp = false, unsafeMetadata, emailAddress, + legalAccepted, }: AuthenticateWithRedirectParams & { unsafeMetadata?: SignUpUnsafeMetadata; }): Promise => { @@ -268,6 +270,7 @@ export class SignUp extends BaseResource implements SignUpResource { actionCompleteRedirectUrl: redirectUrlComplete, unsafeMetadata, emailAddress, + legalAccepted, }; return continueSignUp && this.id ? this.update(params) : this.create(params); }; @@ -328,6 +331,7 @@ export class SignUp extends BaseResource implements SignUpResource { this.createdUserId = data.created_user_id; this.abandonAt = data.abandon_at; this.web3wallet = data.web3_wallet; + this.legalAcceptedAt = data.legal_accepted_at; } return this; } diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 7fe40eda9c6..c46c4180c22 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -87,6 +87,7 @@ export class User extends BaseResource implements UserResource { createOrganizationsLimit: number | null = null; deleteSelfEnabled = false; lastSignInAt: Date | null = null; + legalAcceptedAt: Date | null = null; updatedAt: Date | null = null; createdAt: Date | null = null; @@ -360,6 +361,10 @@ export class User extends BaseResource implements UserResource { this.lastSignInAt = unixEpochToDate(data.last_sign_in_at); } + if (data.legal_accepted_at) { + this.legalAcceptedAt = unixEpochToDate(data.legal_accepted_at); + } + this.updatedAt = unixEpochToDate(data.updated_at); this.createdAt = unixEpochToDate(data.created_at); return this; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index 25de5e805c7..70672855c12 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -104,6 +104,9 @@ export const SignUpForm = (props: SignUpFormProps) => { + + + f.id === 'emailAddress')?.value || ''); diff --git a/packages/types/src/elementIds.ts b/packages/types/src/elementIds.ts index d07c2918926..bf673e5d264 100644 --- a/packages/types/src/elementIds.ts +++ b/packages/types/src/elementIds.ts @@ -20,7 +20,8 @@ export type FieldId = | 'deleteOrganizationConfirmation' | 'enrollmentMode' | 'affiliationEmailAddress' - | 'deleteExistingInvitationsSuggestions'; + | 'deleteExistingInvitationsSuggestions' + | 'legalConsent'; export type ProfileSectionId = | 'profile' | 'username' diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index 01f1a8b0c22..6e249fdaa83 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -97,6 +97,7 @@ export interface SignUpJSON extends ClerkResourceJSON { created_session_id: string | null; created_user_id: string | null; abandon_at: number | null; + legal_accepted_at: number | null; verifications: SignUpVerificationsJSON | null; } @@ -233,6 +234,7 @@ export interface UserJSON extends ClerkResourceJSON { create_organization_enabled: boolean; create_organizations_limit: number | null; delete_self_enabled: boolean; + legal_accepted_at: number | null; updated_at: number; created_at: number; } diff --git a/packages/types/src/redirects.ts b/packages/types/src/redirects.ts index cfd52ede8e2..ed58766cbca 100644 --- a/packages/types/src/redirects.ts +++ b/packages/types/src/redirects.ts @@ -79,6 +79,11 @@ export type AuthenticateWithRedirectParams = { * Email address to use for targeting a SAML connection at sign-up */ emailAddress?: string; + + /** + * Whether the user has accepted the legal terms. + */ + legalAccepted?: boolean; }; export type RedirectUrlProp = { diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index 9f060bae842..2330095da51 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -59,6 +59,7 @@ export interface SignUpResource extends ClerkResource { createdSessionId: string | null; createdUserId: string | null; abandonAt: number | null; + legalAcceptedAt: number | null; create: (params: SignUpCreateParams) => Promise; @@ -161,6 +162,7 @@ export type SignUpCreateParams = Partial< unsafeMetadata: SignUpUnsafeMetadata; ticket: string; token: string; + legalAccepted: boolean; } & SnakeToCamel> >; From 4e0f333073866c53bd6673f8f70c5b4ac3ee0a96 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Tue, 1 Oct 2024 22:37:52 +0300 Subject: [PATCH 02/27] feat(clerk-js): Add legal consent checkbox on sign up --- .../src/ui/components/SignUp/SignUpForm.tsx | 50 +++++++++++-------- .../src/ui/components/SignUp/SignUpStart.tsx | 25 +++++----- .../ui/components/SignUp/signUpFormHelpers.ts | 47 +++++++++++++---- .../clerk-js/src/ui/utils/useFormControl.ts | 4 +- packages/types/src/userSettings.ts | 1 + 5 files changed, 82 insertions(+), 45 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index 70672855c12..92bd70f3daf 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -47,15 +47,15 @@ export const SignUpForm = (props: SignUpFormProps) => { {shouldShow('firstName') && ( )} {shouldShow('lastName') && ( )} @@ -64,8 +64,8 @@ export const SignUpForm = (props: SignUpFormProps) => { )} @@ -73,9 +73,9 @@ export const SignUpForm = (props: SignUpFormProps) => { handleEmailPhoneToggle('phoneNumber') : undefined} /> @@ -85,8 +85,8 @@ export const SignUpForm = (props: SignUpFormProps) => { handleEmailPhoneToggle('emailAddress') : undefined} /> @@ -96,21 +96,31 @@ export const SignUpForm = (props: SignUpFormProps) => { )} - - - - + + + + + + ); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index f6c80a69efe..3875684ab8d 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -79,9 +79,11 @@ function _SignUpStart(): JSX.Element { label: localizationKeys('formFieldLabel__phoneNumber'), placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'), }), - legalAccepted: useFormControl('legalAccepted', 'false', { + legalAccepted: useFormControl('legalAccepted', '', { type: 'checkbox', - label: 'I agree to the terms and conditions', + label: 'I agree to the Terms of Service and Privacy Policy', + defaultChecked: false, + isRequired: userSettings.signUp.legal_consent_enabled || false, }), password: useFormControl('password', '', { type: 'password', @@ -106,6 +108,7 @@ function _SignUpStart(): JSX.Element { hasEmail, activeCommIdentifierType, isProgressiveSignUp, + legalConsentRequired: userSettings.signUp.legal_consent_enabled, }); const handleTokenFlow = () => { @@ -191,33 +194,29 @@ function _SignUpStart(): JSX.Element { e.preventDefault(); type FormStateKey = keyof typeof formState; - const fieldsToSubmit = Object.entries(fields).reduce( - (acc, [k, v]) => [...acc, ...(v && formState[k as FormStateKey] ? [formState[k as FormStateKey]] : [])], - [] as Array, - ); + const fieldsToSubmit = Object.entries(fields).reduce((acc, [k, v]) => { + acc.push(...(v && formState[k as FormStateKey] ? [formState[k as FormStateKey]] : [])); + return acc; + }, [] as Array); if (unsafeMetadata) { fieldsToSubmit.push({ id: 'unsafeMetadata', value: unsafeMetadata } as any); } if (fields.ticket) { - const noop = () => { - // - }; + const noop = () => {}; // fieldsToSubmit: Constructing a fake fields object for strategy. fieldsToSubmit.push({ id: 'strategy', value: 'ticket', setValue: noop, onChange: noop, setError: noop } as any); } - console.log('fieldsToSubmit', fieldsToSubmit); - // In case of emailOrPhone (both email & phone are optional) and neither of them is provided, // add both to the submitted fields to trigger and render an error for both respective inputs const emailAddressProvided = !!(fieldsToSubmit.find(f => f.id === 'emailAddress')?.value || ''); const phoneNumberProvided = !!(fieldsToSubmit.find(f => f.id === 'phoneNumber')?.value || ''); if (!emailAddressProvided && !phoneNumberProvided && emailOrPhone(attributes, isProgressiveSignUp)) { - fieldsToSubmit.push(formState['emailAddress']); - fieldsToSubmit.push(formState['phoneNumber']); + fieldsToSubmit.push(formState.emailAddress); + fieldsToSubmit.push(formState.phoneNumber); } card.setLoading(); diff --git a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts index 7258de0b994..2978b3253ce 100644 --- a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts +++ b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts @@ -8,7 +8,16 @@ import type { FieldState } from '../../common'; */ export type ActiveIdentifier = 'emailAddress' | 'phoneNumber' | null | undefined; -const FieldKeys = ['emailAddress', 'phoneNumber', 'username', 'firstName', 'lastName', 'password', 'ticket']; +const FieldKeys = [ + 'emailAddress', + 'phoneNumber', + 'username', + 'firstName', + 'lastName', + 'password', + 'ticket', + 'legalAccepted', +] as const; export type FieldKey = (typeof FieldKeys)[number]; export type FormState = { @@ -34,10 +43,11 @@ type FieldDeterminationProps = { hasEmail?: boolean; signUp?: SignUpResource | undefined; isProgressiveSignUp: boolean; + legalConsentRequired?: boolean; }; export function determineActiveFields(fieldProps: FieldDeterminationProps): Fields { - return FieldKeys.reduce((fields: Fields, fieldKey: string) => { + return FieldKeys.reduce((fields: Fields, fieldKey: FieldKey) => { const field = getField(fieldKey, fieldProps); if (field) { fields[fieldKey] = field; @@ -50,10 +60,11 @@ export function determineActiveFields(fieldProps: FieldDeterminationProps): Fiel export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpResource) { if (signUp) { const hasEmailFilled = !!signUp.emailAddress; - const hasVerifiedEmail = signUp.verifications?.emailAddress?.status == 'verified'; - const hasVerifiedPhone = signUp.verifications?.phoneNumber?.status == 'verified'; - const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status == 'verified'; - const hasVerifiedWeb3Wallet = signUp.verifications?.web3Wallet?.status == 'verified'; + const hasVerifiedEmail = signUp.verifications?.emailAddress?.status === 'verified'; + const hasVerifiedPhone = signUp.verifications?.phoneNumber?.status === 'verified'; + const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status === 'verified'; + const hasVerifiedWeb3Wallet = signUp.verifications?.web3Wallet?.status === 'verified'; + const hasLegalAccepted = signUp.legalAcceptedAt !== null; if (hasEmailFilled && hasVerifiedEmail) { delete fields.emailAddress; @@ -79,6 +90,10 @@ export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpRe delete fields.username; } + if (hasLegalAccepted) { + delete fields.legalAccepted; + } + // Hide any non-required fields Object.entries(fields).forEach(([k, v]) => { if (v && !v.required) { @@ -133,6 +148,8 @@ function getField(fieldKey: FieldKey, fieldProps: FieldDeterminationProps): Fiel return getPasswordField(fieldProps.attributes); case 'ticket': return getTicketField(fieldProps.hasTicket); + case 'legalAccepted': + return getLegalAcceptedField(fieldProps.legalConsentRequired); case 'username': case 'firstName': case 'lastName': @@ -174,7 +191,7 @@ function getEmailAddressField({ (!hasTicket || (hasTicket && hasEmail)) && attributes.email_address.enabled && attributes.email_address.used_for_first_factor && - activeCommIdentifierType == 'emailAddress'; + activeCommIdentifierType === 'emailAddress'; if (!show) { return; @@ -215,7 +232,7 @@ function getPhoneNumberField({ !hasTicket && attributes.phone_number.enabled && attributes.phone_number.used_for_first_factor && - activeCommIdentifierType == 'phoneNumber'; + activeCommIdentifierType === 'phoneNumber'; if (!show) { return; @@ -249,16 +266,26 @@ function getTicketField(hasTicket?: boolean): Field | undefined { }; } +function getLegalAcceptedField(legalConsentRequired?: boolean): Field | undefined { + if (!legalConsentRequired) { + return; + } + + return { + required: true, + }; +} + function getGenericField(fieldKey: FieldKey, attributes: Attributes): Field | undefined { const attrKey = camelToSnake(fieldKey); - // @ts-ignore + // @ts-expect-error - TS doesn't know that the key exists if (!attributes[attrKey].enabled) { return; } return { - // @ts-ignore + // @ts-expect-error - TS doesn't know that the key exists required: attributes[attrKey].required, }; } diff --git a/packages/clerk-js/src/ui/utils/useFormControl.ts b/packages/clerk-js/src/ui/utils/useFormControl.ts index dddb5060631..885596a56b1 100644 --- a/packages/clerk-js/src/ui/utils/useFormControl.ts +++ b/packages/clerk-js/src/ui/utils/useFormControl.ts @@ -181,12 +181,12 @@ export const useFormControl = ( return { props, ...props, buildErrorMessage, setError, setValue, setChecked }; }; -type FormControlStateLike = Pick; +type FormControlStateLike = Pick; export const buildRequest = (fieldStates: Array): Record => { const request: { [x: string]: any } = {}; fieldStates.forEach(x => { - request[x.id] = x.value; + request[x.id] = x.type !== 'checkbox' ? x.value : x.checked; }); return request; }; diff --git a/packages/types/src/userSettings.ts b/packages/types/src/userSettings.ts index 9edc2755e70..fafebce3fb4 100644 --- a/packages/types/src/userSettings.ts +++ b/packages/types/src/userSettings.ts @@ -54,6 +54,7 @@ export type SignUpData = { progressive: boolean; captcha_enabled: boolean; mode: SignUpModes; + legal_consent_enabled: boolean; }; export type PasswordSettingsData = { From f5bff6ea6df8902f68d39c11cf57b846886c5bc4 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 2 Oct 2024 16:20:16 +0300 Subject: [PATCH 03/27] feat(clerk-js): Handle legal consent in --- .../ui/components/SignUp/SignUpContinue.tsx | 45 +++-- .../src/ui/components/SignUp/SignUpForm.tsx | 156 ++++++++++-------- packages/types/src/attributes.ts | 1 + packages/types/src/signUp.ts | 4 +- 4 files changed, 121 insertions(+), 85 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 7ac0c7188e7..3658b46aae3 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -1,5 +1,5 @@ import { useClerk } from '@clerk/shared/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts'; import { descriptors, Flex, Flow, localizationKeys } from '../../customizables'; @@ -38,12 +38,6 @@ function _SignUpContinue() { getInitialActiveIdentifier(attributes, userSettings.signUp.progressive), ); - // Redirect to sign-up if there is no persisted sign-up - if (!signUp.id) { - void navigate(displayConfig.signUpUrl); - return ; - } - // TODO: This form should be shared between SignUpStart and SignUpContinue const formState = { firstName: useFormControl('firstName', initialValues.firstName || '', { @@ -77,8 +71,25 @@ function _SignUpContinue() { placeholder: localizationKeys('formFieldInputPlaceholder__password'), validatePassword: true, }), + legalAccepted: useFormControl('legalAccepted', '', { + type: 'checkbox', + label: 'I agree to the Terms of Service and Privacy Policy', + defaultChecked: false, + isRequired: userSettings.signUp.legal_consent_enabled || false, + }), } as const; + const onlyLegalConsentMissing = useMemo( + () => signUp.missingFields.length === 1 && signUp.missingFields[0] === 'legal_accepted', + [signUp.missingFields], + ); + + // Redirect to sign-up if there is no persisted sign-up + if (!signUp.id) { + void navigate(displayConfig.signUpUrl); + return ; + } + const hasEmail = !!formState.emailAddress.value; const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status == 'verified'; const hasVerifiedWeb3 = signUp.verifications?.web3Wallet?.status == 'verified'; @@ -89,6 +100,7 @@ function _SignUpContinue() { activeCommIdentifierType, signUp, isProgressiveSignUp, + legalConsentRequired: userSettings.signUp.legal_consent_enabled, }); minimizeFieldsForExistingSignup(fields, signUp); @@ -131,8 +143,8 @@ function _SignUpContinue() { !phoneNumberProvided && emailOrPhone(attributes, isProgressiveSignUp) ) { - fieldsToSubmit.push(formState['emailAddress']); - fieldsToSubmit.push(formState['phoneNumber']); + fieldsToSubmit.push(formState.emailAddress); + fieldsToSubmit.push(formState.phoneNumber); } card.setLoading(); @@ -161,8 +173,18 @@ function _SignUpContinue() { - - + + {card.error} diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index 92bd70f3daf..f1272bf13f5 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -13,10 +13,18 @@ type SignUpFormProps = { formState: Record, FormControlState>; canToggleEmailPhone: boolean; handleEmailPhoneToggle: (type: ActiveIdentifier) => void; + onlyLegalAcceptedMissing?: boolean; }; export const SignUpForm = (props: SignUpFormProps) => { - const { handleSubmit, fields, formState, canToggleEmailPhone, handleEmailPhoneToggle } = props; + const { + handleSubmit, + fields, + formState, + canToggleEmailPhone, + onlyLegalAcceptedMissing = false, + handleEmailPhoneToggle, + } = props; const { showOptionalFields } = useAppearance().parsedLayout; const shouldShow = (name: keyof typeof fields) => { @@ -34,88 +42,92 @@ export const SignUpForm = (props: SignUpFormProps) => { onSubmit={handleSubmit} gap={8} > - - {(shouldShow('firstName') || shouldShow('lastName')) && ( - - {shouldShow('firstName') && ( + {!onlyLegalAcceptedMissing && ( + + {(shouldShow('firstName') || shouldShow('lastName')) && ( + + {shouldShow('firstName') && ( + + )} + {shouldShow('lastName') && ( + + )} + + )} + {shouldShow('username') && ( + - )} - {shouldShow('lastName') && ( + + )} + {shouldShow('emailAddress') && ( + handleEmailPhoneToggle('phoneNumber') : undefined} /> - )} - - )} - {shouldShow('username') && ( - - - - )} - {shouldShow('emailAddress') && ( - - handleEmailPhoneToggle('phoneNumber') : undefined} - /> - - )} - {shouldShow('phoneNumber') && ( - - handleEmailPhoneToggle('emailAddress') : undefined} - /> - - )} - {shouldShow('password') && ( - - - - )} - + + )} + {shouldShow('phoneNumber') && ( + + handleEmailPhoneToggle('emailAddress') : undefined} + /> + + )} + {shouldShow('password') && ( + + + + )} + + )} - - - + {shouldShow('legalAccepted') && ( + + + + )} Date: Thu, 3 Oct 2024 10:27:50 +0300 Subject: [PATCH 04/27] fix(clerk-js): Fix types for SignUpCreateParams --- packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts | 2 +- packages/types/src/signUp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts index 2978b3253ce..7bd4f1161c8 100644 --- a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts +++ b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts @@ -97,7 +97,7 @@ export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpRe // Hide any non-required fields Object.entries(fields).forEach(([k, v]) => { if (v && !v.required) { - delete fields[k]; + delete fields[k as FieldKey]; } }); } diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index 1741d05d246..5fef0fc7ada 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -163,7 +163,7 @@ export type SignUpCreateParams = Partial< ticket: string; token: string; legalAccepted: boolean; - } & SnakeToCamel> + } & Omit>, 'legalAccepted'> >; export type SignUpUpdateParams = SignUpCreateParams; From cca62a9a5fb3a67b31322ff56435d366f5b812b9 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Fri, 4 Oct 2024 01:03:58 +0300 Subject: [PATCH 05/27] fix(clerk-js): Rename legalAccepted to __experimental_legalAccepted --- .../clerk-js/src/core/resources/SignUp.ts | 35 +++++++++++++++---- .../ui/components/SignUp/SignUpContinue.tsx | 2 +- .../src/ui/components/SignUp/SignUpForm.tsx | 2 +- packages/clerk-js/src/ui/elements/Form.tsx | 34 +++++++++++++++++- packages/types/src/redirects.ts | 4 +-- packages/types/src/signUp.ts | 2 +- 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 4fb3a841d8d..19e25402757 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -112,6 +112,10 @@ export class SignUp extends BaseResource implements SignUpResource { paramsWithCaptcha.strategy = SignUp.clerk.client?.signIn.firstFactorVerification.strategy; } + if (params.__experimental_legalAccepted) { + paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; + } + return this._basePost({ path: this.pathRoot, body: normalizeUnsafeMetadata(paramsWithCaptcha), @@ -191,9 +195,18 @@ export class SignUp extends BaseResource implements SignUpResource { }; public authenticateWithWeb3 = async ( - params: AuthenticateWithWeb3Params & { unsafeMetadata?: SignUpUnsafeMetadata }, + params: AuthenticateWithWeb3Params & { + unsafeMetadata?: SignUpUnsafeMetadata; + __experimental_legalAccepted?: boolean; + }, ): Promise => { - const { generateSignature, identifier, unsafeMetadata, strategy = 'web3_metamask_signature' } = params || {}; + const { + generateSignature, + identifier, + unsafeMetadata, + strategy = 'web3_metamask_signature', + __experimental_legalAccepted, + } = params || {}; const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider; if (!(typeof generateSignature === 'function')) { @@ -201,7 +214,7 @@ export class SignUp extends BaseResource implements SignUpResource { } const web3Wallet = identifier || this.web3wallet!; - await this.create({ web3Wallet, unsafeMetadata }); + await this.create({ web3Wallet, unsafeMetadata, __experimental_legalAccepted: __experimental_legalAccepted }); await this.prepareWeb3WalletVerification({ strategy }); const { message } = this.verifications.web3Wallet; @@ -230,18 +243,25 @@ export class SignUp extends BaseResource implements SignUpResource { return this.attemptWeb3WalletVerification({ signature, strategy }); }; - public authenticateWithMetamask = async (params?: SignUpAuthenticateWithWeb3Params): Promise => { + public authenticateWithMetamask = async ( + params?: SignUpAuthenticateWithWeb3Params & { + __experimental_legalAccepted?: boolean; + }, + ): Promise => { const identifier = await getMetamaskIdentifier(); return this.authenticateWithWeb3({ identifier, generateSignature: generateSignatureWithMetamask, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_metamask_signature', + __experimental_legalAccepted: params?.__experimental_legalAccepted, }); }; public authenticateWithCoinbaseWallet = async ( - params?: SignUpAuthenticateWithWeb3Params, + params?: SignUpAuthenticateWithWeb3Params & { + __experimental_legalAccepted?: boolean; + }, ): Promise => { const identifier = await getCoinbaseWalletIdentifier(); return this.authenticateWithWeb3({ @@ -249,6 +269,7 @@ export class SignUp extends BaseResource implements SignUpResource { generateSignature: generateSignatureWithCoinbaseWallet, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_coinbase_wallet_signature', + __experimental_legalAccepted: params?.__experimental_legalAccepted, }); }; @@ -259,7 +280,7 @@ export class SignUp extends BaseResource implements SignUpResource { continueSignUp = false, unsafeMetadata, emailAddress, - legalAccepted, + __experimental_legalAccepted, }: AuthenticateWithRedirectParams & { unsafeMetadata?: SignUpUnsafeMetadata; }): Promise => { @@ -270,7 +291,7 @@ export class SignUp extends BaseResource implements SignUpResource { actionCompleteRedirectUrl: redirectUrlComplete, unsafeMetadata, emailAddress, - legalAccepted, + __experimental_legalAccepted, }; return continueSignUp && this.id ? this.update(params) : this.create(params); }; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 3658b46aae3..8847ef372e7 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -193,7 +193,7 @@ function _SignUpContinue() { gap={8} > - {(showOauthProviders || showWeb3Providers) && ( + {(showOauthProviders || showWeb3Providers) && !onlyLegalConsentMissing && ( { > {shouldShow('legalAccepted') && ( - diff --git a/packages/clerk-js/src/ui/elements/Form.tsx b/packages/clerk-js/src/ui/elements/Form.tsx index dfa5397f462..9753e9b3d0c 100644 --- a/packages/clerk-js/src/ui/elements/Form.tsx +++ b/packages/clerk-js/src/ui/elements/Form.tsx @@ -4,7 +4,7 @@ import type { PropsWithChildren } from 'react'; import React, { forwardRef, useState } from 'react'; import type { LocalizationKey } from '../customizables'; -import { Button, Col, descriptors, Flex, Form as FormPrim, localizationKeys } from '../customizables'; +import { Button, Col, descriptors, Flex, Form as FormPrim, FormLabel, localizationKeys, Text } from '../customizables'; import { useLoadingStatus } from '../hooks'; import type { PropsOfComponent } from '../styledSystem'; import type { OTPInputProps } from './CodeControl'; @@ -201,6 +201,8 @@ const InputGroup = ( const Checkbox = ( props: CommonFieldRootProps & { description?: string | LocalizationKey; + termsLink?: string; + privacyLink?: string; }, ) => { return ( @@ -214,6 +216,35 @@ const Checkbox = ( ); }; +const LegalCheckbox = ( + props: CommonFieldRootProps & { + description?: string | LocalizationKey; + }, +) => { + return ( + + + + ({ + padding: `${t.space.$none} ${t.space.$2}`, + display: 'flex', + flexDirection: 'column', + })} + > + + + + + ); +}; + const RadioGroup = ( props: Omit, 'infoText' | 'type' | 'validatePassword' | 'label' | 'placeholder'>, ) => { @@ -294,6 +325,7 @@ export const Form = { InputGroup, RadioGroup, Checkbox, + LegalCheckbox: LegalCheckbox, SubmitButton: FormSubmit, ResetButton: FormReset, }; diff --git a/packages/types/src/redirects.ts b/packages/types/src/redirects.ts index ed58766cbca..9e9137d67be 100644 --- a/packages/types/src/redirects.ts +++ b/packages/types/src/redirects.ts @@ -81,9 +81,9 @@ export type AuthenticateWithRedirectParams = { emailAddress?: string; /** - * Whether the user has accepted the legal terms. + * Whether the user has accepted the legal requirements. */ - legalAccepted?: boolean; + __experimental_legalAccepted?: boolean; }; export type RedirectUrlProp = { diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index 5fef0fc7ada..e59174900ec 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -162,7 +162,7 @@ export type SignUpCreateParams = Partial< unsafeMetadata: SignUpUnsafeMetadata; ticket: string; token: string; - legalAccepted: boolean; + __experimental_legalAccepted: boolean; } & Omit>, 'legalAccepted'> >; From 0de78e2a9e2c623db79a1dd4682d9889d937ed1f Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Tue, 8 Oct 2024 15:30:22 +0200 Subject: [PATCH 06/27] Update sign up form --- .../ui/components/SignUp/SignUpContinue.tsx | 22 +++++++++---------- packages/clerk-js/src/ui/elements/Form.tsx | 1 + packages/localizations/src/en-US.ts | 6 +++++ packages/types/src/localization.ts | 6 +++++ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 8847ef372e7..0cde7e89e41 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -168,23 +168,21 @@ function _SignUpContinue() { const showOauthProviders = !hasVerifiedExternalAccount && oauthOptions.length > 0; const showWeb3Providers = !hasVerifiedWeb3 && web3Options.length > 0; + const headerTitle = !onlyLegalConsentMissing + ? localizationKeys('signUp.continue.title') + : localizationKeys('signUp.legalConsent.continue.title'); + + const headerSubtitle = !onlyLegalConsentMissing + ? localizationKeys('signUp.continue.subtitle') + : localizationKeys('signUp.legalConsent.continue.subtitle'); + return ( - - + + {card.error} + {/* TODO(@vaggelis): Handle the legal label */} Date: Mon, 14 Oct 2024 17:36:43 +0300 Subject: [PATCH 07/27] feat(clerk-js): Add legal consent checkbox to SignUp --- .../src/core/resources/DisplayConfig.ts | 4 + .../clerk-js/src/core/resources/SignUp.ts | 9 +- .../components/SignUp/SignUpSocialButtons.tsx | 3 +- .../src/ui/components/SignUp/SignUpStart.tsx | 12 ++- packages/clerk-js/src/ui/elements/Form.tsx | 83 ++++++++++++++++--- .../ui/localization/localizationModifiers.ts | 10 +++ packages/localizations/src/en-US.ts | 6 ++ packages/types/src/displayConfig.ts | 4 + packages/types/src/localization.ts | 6 ++ 9 files changed, 120 insertions(+), 17 deletions(-) diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index c5165acb447..73150b2e37e 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -44,6 +44,8 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource afterCreateOrganizationUrl!: string; googleOneTapClientId?: string; showDevModeWarning!: boolean; + termsUrl!: string; + privacyPolicyUrl!: string; public constructor(data: DisplayConfigJSON) { super(); @@ -87,6 +89,8 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource this.afterCreateOrganizationUrl = data.after_create_organization_url; this.googleOneTapClientId = data.google_one_tap_client_id; this.showDevModeWarning = data.show_devmode_warning; + this.termsUrl = data.terms_url; + this.privacyPolicyUrl = data.privacy_policy_url; return this; } } diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 19e25402757..2fa2080f506 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -112,9 +112,8 @@ export class SignUp extends BaseResource implements SignUpResource { paramsWithCaptcha.strategy = SignUp.clerk.client?.signIn.firstFactorVerification.strategy; } - if (params.__experimental_legalAccepted) { - paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; - } + paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; + paramsWithCaptcha.__experimental_legalAccepted = undefined; return this._basePost({ path: this.pathRoot, @@ -318,6 +317,10 @@ export class SignUp extends BaseResource implements SignUpResource { }; update = (params: SignUpUpdateParams): Promise => { + // @ts-expect-error - This is a temporary until the feature is stable + params.legalAccepted = params.__experimental_legalAccepted; + params.__experimental_legalAccepted = undefined; + return this._basePatch({ body: normalizeUnsafeMetadata(params), }); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpSocialButtons.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpSocialButtons.tsx index 5c600cc5aff..1d70139fe34 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpSocialButtons.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpSocialButtons.tsx @@ -11,7 +11,7 @@ import { SocialButtons } from '../../elements/SocialButtons'; import { useRouter } from '../../router'; import { handleError } from '../../utils'; -export type SignUpSocialButtonsProps = SocialButtonsProps & { continueSignUp?: boolean }; +export type SignUpSocialButtonsProps = SocialButtonsProps & { continueSignUp?: boolean; legalAccepted?: boolean }; export const SignUpSocialButtons = React.memo((props: SignUpSocialButtonsProps) => { const clerk = useClerk(); @@ -35,6 +35,7 @@ export const SignUpSocialButtons = React.memo((props: SignUpSocialButtonsProps) redirectUrlComplete, strategy, unsafeMetadata: ctx.unsafeMetadata, + __experimental_legalAccepted: props.legalAccepted, }) .catch(err => handleError(err, [], card.setError)); }} diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 3875684ab8d..1a7491493cb 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -8,6 +8,7 @@ import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts' import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables'; import { Card, + Form, Header, LoadingCard, SocialButtonsReversibleContainerWithDivider, @@ -79,7 +80,7 @@ function _SignUpStart(): JSX.Element { label: localizationKeys('formFieldLabel__phoneNumber'), placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'), }), - legalAccepted: useFormControl('legalAccepted', '', { + legalAccepted: useFormControl('legalConsent', '', { type: 'checkbox', label: 'I agree to the Terms of Service and Privacy Policy', defaultChecked: false, @@ -278,6 +279,7 @@ function _SignUpStart(): JSX.Element { enableOAuthProviders={showOauthProviders} enableWeb3Providers={showWeb3Providers} continueSignUp={missingRequirementsWithTicket} + legalAccepted={Boolean(formState.legalAccepted.value)} /> )} {shouldShowForm && ( @@ -290,6 +292,14 @@ function _SignUpStart(): JSX.Element { /> )} + {!shouldShowForm && ( + + + + )} {!shouldShowForm && } diff --git a/packages/clerk-js/src/ui/elements/Form.tsx b/packages/clerk-js/src/ui/elements/Form.tsx index db6096c406a..e13b53b0230 100644 --- a/packages/clerk-js/src/ui/elements/Form.tsx +++ b/packages/clerk-js/src/ui/elements/Form.tsx @@ -3,8 +3,21 @@ import type { FieldId } from '@clerk/types'; import type { PropsWithChildren } from 'react'; import React, { forwardRef, useState } from 'react'; +import { useEnvironment } from '../../ui/contexts'; import type { LocalizationKey } from '../customizables'; -import { Button, Col, descriptors, Flex, Form as FormPrim, FormLabel, localizationKeys, Text } from '../customizables'; +import { + Button, + Col, + descriptors, + Flex, + Form as FormPrim, + FormLabel, + Link, + localizationKeys, + Text, + useAppearance, + useLocalizations, +} from '../customizables'; import { useLoadingStatus } from '../hooks'; import type { PropsOfComponent } from '../styledSystem'; import type { OTPInputProps } from './CodeControl'; @@ -216,29 +229,75 @@ const Checkbox = ( ); }; +const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: string }) => { + const { t } = useLocalizations(); + return ( + + {t(localizationKeys('signUp.legalConsent.checkbox.label__prefixText'))} + {props.termsUrl && ( + <> + {' '} + + + )} + + {props.termsUrl && props.privacyPolicyUrl && ( + <> {t(localizationKeys('signUp.legalConsent.checkbox.label__conjunctionText'))} + )} + + {props.privacyPolicyUrl && ( + + )} + + ); +}; + const LegalCheckbox = ( props: CommonFieldRootProps & { description?: string | LocalizationKey; }, ) => { + const { displayConfig } = useEnvironment(); + const { parsedLayout } = useAppearance(); + + const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl || '/terms'; + const privacyPolicy = parsedLayout.termsPageUrl || displayConfig.termsUrl || '/privacy'; + return ( - + ({ + gap: t.space.$1x5, + })} + > ({ - padding: `${t.space.$none} ${t.space.$2}`, - display: 'flex', - flexDirection: 'column', - })} + sx={{ + textAlign: 'initial', + }} > - {/* TODO(@vaggelis): Handle the legal label */} - diff --git a/packages/clerk-js/src/ui/localization/localizationModifiers.ts b/packages/clerk-js/src/ui/localization/localizationModifiers.ts index 1e84382923c..a468b1deb91 100644 --- a/packages/clerk-js/src/ui/localization/localizationModifiers.ts +++ b/packages/clerk-js/src/ui/localization/localizationModifiers.ts @@ -27,9 +27,19 @@ const numeric = (val: Date | number | string, locale?: string) => { } }; +const termsOfService = (val: string) => { + return `__${val}__`; +}; + +const privacyPolicy = (val: string) => { + return `__${val}__`; +}; + export const MODIFIERS = { titleize, timeString, weekday, numeric, + termsOfService, + privacyPolicy, } as const; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index c0407aae783..0cba5aa2af2 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -520,6 +520,12 @@ export const enUS: LocalizationResource = { subtitle: 'Please read and accept the terms to continue', title: 'Legal consent', }, + checkbox: { + label__prefixText: 'I agree to the', + label__privacyPolicyText: 'Privacy Policy', + label__termsOfServiceText: 'Terms of Service', + label__conjunctionText: 'and', + }, }, }, socialButtonsBlockButton: 'Continue with {{provider|titleize}}', diff --git a/packages/types/src/displayConfig.ts b/packages/types/src/displayConfig.ts index 8d3f2c7a58a..bd25208f273 100644 --- a/packages/types/src/displayConfig.ts +++ b/packages/types/src/displayConfig.ts @@ -38,6 +38,8 @@ export interface DisplayConfigJSON { after_create_organization_url: string; google_one_tap_client_id?: string; show_devmode_warning: boolean; + terms_url: string; + privacy_policy_url: string; } export interface DisplayConfigResource extends ClerkResource { @@ -78,4 +80,6 @@ export interface DisplayConfigResource extends ClerkResource { afterCreateOrganizationUrl: string; googleOneTapClientId?: string; showDevModeWarning: boolean; + termsUrl: string; + privacyPolicyUrl: string; } diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 51870cb5616..c7f1ee1b66b 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -156,6 +156,12 @@ type _LocalizationResource = { title: LocalizationValue; subtitle: LocalizationValue; }; + checkbox: { + label__prefixText: LocalizationValue; + label__termsOfServiceText: LocalizationValue; + label__privacyPolicyText: LocalizationValue; + label__conjunctionText: LocalizationValue; + }; }; }; signIn: { From 79ec2ae9199fcd4015a7433cbcccf4105f1c507c Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Tue, 15 Oct 2024 20:48:41 +0300 Subject: [PATCH 08/27] chore(clerk-js): Make legal accepted as experimental --- packages/clerk-js/src/core/clerk.ts | 3 +++ packages/clerk-js/src/core/resources/SignUp.ts | 3 +++ .../src/ui/components/SignUp/SignUpContinue.tsx | 3 ++- .../clerk-js/src/ui/components/SignUp/SignUpForm.tsx | 8 ++++---- .../clerk-js/src/ui/components/SignUp/SignUpStart.tsx | 10 +++++----- .../src/ui/components/SignUp/signUpFormHelpers.ts | 6 +++--- packages/types/src/clerk.ts | 4 ++++ packages/types/src/elementIds.ts | 2 +- 8 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 5ed5a1034b7..36ff3a1eef0 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1400,6 +1400,7 @@ export class Clerk implements ClerkInterface { return this.client?.signUp.create({ strategy: 'google_one_tap', token: params.token, + __experimental_legalAccepted: params.__experimental_legalAccepted, }); } throw err; @@ -1420,6 +1421,7 @@ export class Clerk implements ClerkInterface { customNavigate, unsafeMetadata, strategy, + __experimental_legalAccepted, }: ClerkAuthenticateWithWeb3Params): Promise => { if (!this.client || !this.environment) { return; @@ -1442,6 +1444,7 @@ export class Clerk implements ClerkInterface { generateSignature, unsafeMetadata, strategy, + __experimental_legalAccepted, }); if ( diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 2fa2080f506..fe620d99702 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -318,9 +318,12 @@ export class SignUp extends BaseResource implements SignUpResource { update = (params: SignUpUpdateParams): Promise => { // @ts-expect-error - This is a temporary until the feature is stable + console.trace('params', params); params.legalAccepted = params.__experimental_legalAccepted; params.__experimental_legalAccepted = undefined; + console.log('params', params); + return this._basePatch({ body: normalizeUnsafeMetadata(params), }); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 0cde7e89e41..60e50067d14 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -71,7 +71,7 @@ function _SignUpContinue() { placeholder: localizationKeys('formFieldInputPlaceholder__password'), validatePassword: true, }), - legalAccepted: useFormControl('legalAccepted', '', { + __experimental_legalAccepted: useFormControl('__experimental_legalAccepted', '', { type: 'checkbox', label: 'I agree to the Terms of Service and Privacy Policy', defaultChecked: false, @@ -149,6 +149,7 @@ function _SignUpContinue() { card.setLoading(); card.setError(undefined); + return signUp .update(buildRequest(fieldsToSubmit)) .then(res => diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index 85c85d14d7e..bdb505699a2 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -120,11 +120,11 @@ export const SignUpForm = (props: SignUpFormProps) => { width: '100%', }} > - {shouldShow('legalAccepted') && ( - + {shouldShow('__experimental_legalAccepted') && ( + )} diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 1a7491493cb..4414baacdb8 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -80,7 +80,7 @@ function _SignUpStart(): JSX.Element { label: localizationKeys('formFieldLabel__phoneNumber'), placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'), }), - legalAccepted: useFormControl('legalConsent', '', { + __experimental_legalAccepted: useFormControl('__experimental_legalAccepted', '', { type: 'checkbox', label: 'I agree to the Terms of Service and Privacy Policy', defaultChecked: false, @@ -279,7 +279,7 @@ function _SignUpStart(): JSX.Element { enableOAuthProviders={showOauthProviders} enableWeb3Providers={showWeb3Providers} continueSignUp={missingRequirementsWithTicket} - legalAccepted={Boolean(formState.legalAccepted.value)} + legalAccepted={Boolean(formState.__experimental_legalAccepted.value)} /> )} {shouldShowForm && ( @@ -293,10 +293,10 @@ function _SignUpStart(): JSX.Element { )} {!shouldShowForm && ( - + )} diff --git a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts index 7bd4f1161c8..5318a5890ad 100644 --- a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts +++ b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts @@ -16,7 +16,7 @@ const FieldKeys = [ 'lastName', 'password', 'ticket', - 'legalAccepted', + '__experimental_legalAccepted', ] as const; export type FieldKey = (typeof FieldKeys)[number]; @@ -91,7 +91,7 @@ export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpRe } if (hasLegalAccepted) { - delete fields.legalAccepted; + delete fields.__experimental_legalAccepted; } // Hide any non-required fields @@ -148,7 +148,7 @@ function getField(fieldKey: FieldKey, fieldProps: FieldDeterminationProps): Fiel return getPasswordField(fieldProps.attributes); case 'ticket': return getTicketField(fieldProps.hasTicket); - case 'legalAccepted': + case '__experimental_legalAccepted': return getLegalAcceptedField(fieldProps.legalConsentRequired); case 'username': case 'firstName': diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 841ddb1e255..8dbcb282057 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -1287,6 +1287,7 @@ export interface ClerkAuthenticateWithWeb3Params { signUpContinueUrl?: string; unsafeMetadata?: SignUpUnsafeMetadata; strategy: Web3Strategy; + __experimental_legalAccepted?: boolean; } export interface AuthenticateWithMetamaskParams { @@ -1294,6 +1295,7 @@ export interface AuthenticateWithMetamaskParams { redirectUrl?: string; signUpContinueUrl?: string; unsafeMetadata?: SignUpUnsafeMetadata; + __experimental_legalAccepted?: boolean; } export interface AuthenticateWithCoinbaseWalletParams { @@ -1301,10 +1303,12 @@ export interface AuthenticateWithCoinbaseWalletParams { redirectUrl?: string; signUpContinueUrl?: string; unsafeMetadata?: SignUpUnsafeMetadata; + __experimental_legalAccepted?: boolean; } export interface AuthenticateWithGoogleOneTapParams { token: string; + __experimental_legalAccepted?: boolean; } export interface LoadedClerk extends Clerk { diff --git a/packages/types/src/elementIds.ts b/packages/types/src/elementIds.ts index bf673e5d264..69bc36ce171 100644 --- a/packages/types/src/elementIds.ts +++ b/packages/types/src/elementIds.ts @@ -21,7 +21,7 @@ export type FieldId = | 'enrollmentMode' | 'affiliationEmailAddress' | 'deleteExistingInvitationsSuggestions' - | 'legalConsent'; + | '__experimental_legalAccepted'; export type ProfileSectionId = | 'profile' | 'username' From 857dfcaef9d75ee9e4168ad8d97776a93cab05f9 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 13:59:34 +0300 Subject: [PATCH 09/27] chore(repo): Add changeset --- .changeset/curvy-ghosts-relax.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/curvy-ghosts-relax.md diff --git a/.changeset/curvy-ghosts-relax.md b/.changeset/curvy-ghosts-relax.md new file mode 100644 index 00000000000..378f799a0ff --- /dev/null +++ b/.changeset/curvy-ghosts-relax.md @@ -0,0 +1,7 @@ +--- +"@clerk/localizations": minor +"@clerk/clerk-js": minor +"@clerk/types": minor +--- + +Adding experimental support for legal consent for `` component From 921d9b5708f186a50e3f243f7f7ff7198f1e7c8a Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 14:02:40 +0300 Subject: [PATCH 10/27] chore(clerk-js): Remove localization modifiers --- .../src/ui/localization/localizationModifiers.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/clerk-js/src/ui/localization/localizationModifiers.ts b/packages/clerk-js/src/ui/localization/localizationModifiers.ts index a468b1deb91..1e84382923c 100644 --- a/packages/clerk-js/src/ui/localization/localizationModifiers.ts +++ b/packages/clerk-js/src/ui/localization/localizationModifiers.ts @@ -27,19 +27,9 @@ const numeric = (val: Date | number | string, locale?: string) => { } }; -const termsOfService = (val: string) => { - return `__${val}__`; -}; - -const privacyPolicy = (val: string) => { - return `__${val}__`; -}; - export const MODIFIERS = { titleize, timeString, weekday, numeric, - termsOfService, - privacyPolicy, } as const; From bc610715a79b24a44f8d48aa9a290336e01edfd6 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 20:57:19 +0300 Subject: [PATCH 11/27] chore(clerk-js): Style the polished checkbox --- packages/clerk-js/src/core/resources/SignUp.ts | 5 +---- .../src/ui/components/SignUp/SignUpForm.tsx | 7 +------ .../ui/components/SignUp/SignUpSocialButtons.tsx | 1 + .../src/ui/components/SignUp/SignUpStart.tsx | 2 +- packages/clerk-js/src/ui/elements/Form.tsx | 14 ++++++-------- packages/clerk-js/src/ui/polishedAppearance.ts | 15 +++++++++++++++ packages/types/src/signUp.ts | 5 ++++- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index fe620d99702..ff132bc4e15 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -317,13 +317,10 @@ export class SignUp extends BaseResource implements SignUpResource { }; update = (params: SignUpUpdateParams): Promise => { - // @ts-expect-error - This is a temporary until the feature is stable - console.trace('params', params); + // @ts-expect-error - We need to remove the __experimental_legalAccepted key from the params params.legalAccepted = params.__experimental_legalAccepted; params.__experimental_legalAccepted = undefined; - console.log('params', params); - return this._basePatch({ body: normalizeUnsafeMetadata(params), }); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index bdb505699a2..b8e18fc7c52 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -114,12 +114,7 @@ export const SignUpForm = (props: SignUpFormProps) => { )} - + {shouldShow('__experimental_legalAccepted') && ( handleError(err, [], card.setError)); }} diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 4414baacdb8..ff8ee165b42 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -279,7 +279,7 @@ function _SignUpStart(): JSX.Element { enableOAuthProviders={showOauthProviders} enableWeb3Providers={showWeb3Providers} continueSignUp={missingRequirementsWithTicket} - legalAccepted={Boolean(formState.__experimental_legalAccepted.value)} + legalAccepted={Boolean(formState.__experimental_legalAccepted.checked)} /> )} {shouldShowForm && ( diff --git a/packages/clerk-js/src/ui/elements/Form.tsx b/packages/clerk-js/src/ui/elements/Form.tsx index e13b53b0230..303530982da 100644 --- a/packages/clerk-js/src/ui/elements/Form.tsx +++ b/packages/clerk-js/src/ui/elements/Form.tsx @@ -233,7 +233,7 @@ const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: strin const { t } = useLocalizations(); return ( {t(localizationKeys('signUp.legalConsent.checkbox.label__prefixText'))} @@ -282,18 +282,16 @@ const LegalCheckbox = ( return ( ({ - gap: t.space.$1x5, - })} + align='center' + justify='center' > ({ + paddingLeft: t.space.$1x5, + })} > Promise; authenticateWithWeb3: ( - params: AuthenticateWithWeb3Params & { unsafeMetadata?: SignUpUnsafeMetadata }, + params: AuthenticateWithWeb3Params & { + unsafeMetadata?: SignUpUnsafeMetadata; + __experimental_legalAccepted?: boolean; + }, ) => Promise; authenticateWithMetamask: (params?: SignUpAuthenticateWithWeb3Params) => Promise; From 11d6e6b7934cbf3f59ae0bede63bc8d7b880262d Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 21:17:36 +0300 Subject: [PATCH 12/27] chore(clerk-js): Fix styling --- .../src/ui/components/SignUp/SignUpForm.tsx | 7 +++++- packages/clerk-js/src/ui/elements/Form.tsx | 25 +++++++++++-------- .../clerk-js/src/ui/polishedAppearance.ts | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index b8e18fc7c52..bdb505699a2 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -114,7 +114,12 @@ export const SignUpForm = (props: SignUpFormProps) => { )} - + {shouldShow('__experimental_legalAccepted') && ( )} @@ -255,14 +256,18 @@ const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: strin )} {props.privacyPolicyUrl && ( - + <> + {' '} + + )} ); @@ -276,8 +281,8 @@ const LegalCheckbox = ( const { displayConfig } = useEnvironment(); const { parsedLayout } = useAppearance(); - const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl || '/terms'; - const privacyPolicy = parsedLayout.termsPageUrl || displayConfig.termsUrl || '/privacy'; + const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl; + const privacyPolicy = parsedLayout.privacyPageUrl || displayConfig.privacyPolicyUrl; return ( diff --git a/packages/clerk-js/src/ui/polishedAppearance.ts b/packages/clerk-js/src/ui/polishedAppearance.ts index de3247ca5b0..a090ec78edd 100644 --- a/packages/clerk-js/src/ui/polishedAppearance.ts +++ b/packages/clerk-js/src/ui/polishedAppearance.ts @@ -152,7 +152,7 @@ export const polishedAppearance: Appearance = { '&:checked': { backgroundImage: `url("data:image/svg+xml,%3Csvg width='16' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.25 8L6.5 9.75L9.75 4.25' stroke='${theme.colors.$whiteAlpha900}' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3C/svg%3E")`, borderColor: theme.colors.$transparent, - backgroundColor: theme.colors.$primary500, + backgroundColor: theme.colors.$primary900, backgroundSize: '100% 100%', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', From e0038e521ed930678bf2800c77cccecc96c233e0 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 21:34:15 +0300 Subject: [PATCH 13/27] chore(clerk-js): Add TODO comments to remind me to do action after stable release --- packages/clerk-js/src/core/resources/SignUp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index ff132bc4e15..9001087bbe9 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -112,6 +112,7 @@ export class SignUp extends BaseResource implements SignUpResource { paramsWithCaptcha.strategy = SignUp.clerk.client?.signIn.firstFactorVerification.strategy; } + // TODO(@vaggelis): Remove this once the legalAccepted is stable paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; paramsWithCaptcha.__experimental_legalAccepted = undefined; @@ -317,6 +318,7 @@ export class SignUp extends BaseResource implements SignUpResource { }; update = (params: SignUpUpdateParams): Promise => { + // TODO(@vaggelis): Remove this once the legalAccepted is stable // @ts-expect-error - We need to remove the __experimental_legalAccepted key from the params params.legalAccepted = params.__experimental_legalAccepted; params.__experimental_legalAccepted = undefined; From 9ffd36716f05bc52734e6cc3256083b38c1c78b3 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Wed, 16 Oct 2024 23:12:40 +0300 Subject: [PATCH 14/27] test(clerk-js): Add simple tests --- .../SignUp/__tests__/SignUpStart.test.tsx | 15 +++++++++++++++ .../clerk-js/src/ui/utils/test/fixtureHelpers.ts | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx index 4aaec2a2d85..88f5b310f98 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx @@ -256,6 +256,21 @@ describe('SignUpStart', () => { }); }); + describe('Legal consent', () => { + it('shows sign up component with legal consent checkbox', async () => { + const { wrapper } = await createFixtures(f => { + f.withTermsPrivacyPolicyUrls({ + privacyPolicy: 'https://clerk.dev/privacy', + termsOfService: 'https://clerk.dev/tos', + }); + }); + + render(, { wrapper }); + screen.getByText('Terms of Service'); + screen.getByText('Privacy Policy'); + }); + }); + describe('ticket flow', () => { it('calls the appropriate resource function upon detecting the ticket', async () => { const { wrapper, fixtures } = await createFixtures(f => { diff --git a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts index 58b662d3568..bfc660652ab 100644 --- a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts @@ -285,7 +285,15 @@ const createDisplayConfigFixtureHelpers = (environment: EnvironmentJSON) => { const withPreferredSignInStrategy = (opts: { strategy: DisplayConfigJSON['preferred_sign_in_strategy'] }) => { dc.preferred_sign_in_strategy = opts.strategy; }; - return { withSupportEmail, withoutClerkBranding, withPreferredSignInStrategy }; + + const withTermsPrivacyPolicyUrls = (opts: { + termsOfService?: DisplayConfigJSON['terms_url']; + privacyPolicy?: DisplayConfigJSON['privacy_policy_url']; + }) => { + dc.terms_url = opts.termsOfService || ''; + dc.privacy_policy_url = opts.privacyPolicy || ''; + }; + return { withSupportEmail, withoutClerkBranding, withPreferredSignInStrategy, withTermsPrivacyPolicyUrls }; }; const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) => { @@ -487,6 +495,10 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { us.sign_up.mode = SIGN_UP_MODES.RESTRICTED; }; + const withLegalConsent = () => { + us.sign_up.legal_consent_enabled = true; + }; + // TODO: Add the rest, consult pkg/generate/auth_config.go return { @@ -505,5 +517,6 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { withPasskey, withPasskeySettings, withRestrictedMode, + withLegalConsent, }; }; From 163fd5d4a7e8e4556fb65a2806cf17709bdeca76 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 10:44:44 +0300 Subject: [PATCH 15/27] fix(clerk-js): Check missingFields is not null --- packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 60e50067d14..8f3d3ba3f4b 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -80,7 +80,7 @@ function _SignUpContinue() { } as const; const onlyLegalConsentMissing = useMemo( - () => signUp.missingFields.length === 1 && signUp.missingFields[0] === 'legal_accepted', + () => signUp.missingFields && signUp.missingFields.length === 1 && signUp.missingFields[0] === 'legal_accepted', [signUp.missingFields], ); From 1e025725af69354b2df4ee61e2a0add5e0f47ada Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 11:41:06 +0300 Subject: [PATCH 16/27] fix(clerk-js): Fix tests --- .../SignUp/__tests__/SignUpContinue.test.tsx | 17 +++++++++++++++++ .../SignUp/__tests__/SignUpStart.test.tsx | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx index 75e6b33fa5a..6ba58574a62 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx @@ -78,6 +78,23 @@ describe('SignUpContinue', () => { expect(button.parentElement?.tagName.toUpperCase()).toBe('BUTTON'); }); + it('renders the component if there is a persisted sign up and legal accepted is missing', async () => { + const { wrapper } = await createFixtures(f => { + f.withEmailAddress({ required: true }); + f.withPassword({ required: true }); + f.startSignUpWithEmailAddress(); + f.withLegalConsent(); + f.withTermsPrivacyPolicyUrls({ + privacyPolicy: 'https://clerk.dev/privacy', + termsOfService: 'https://clerk.dev/tos', + }); + }); + render(, { wrapper }); + screen.getByText(/missing/i); + screen.getByText(/Terms Of Service/i); + screen.getByText(/Privacy Policy/i); + }); + it.each(OAUTH_PROVIDERS)('shows the "Continue with $name" social OAuth button', async ({ provider, name }) => { const { wrapper } = await createFixtures(f => { f.withEmailAddress({ required: true }); diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx index 88f5b310f98..c36ee29d6a4 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx @@ -220,7 +220,7 @@ describe('SignUpStart', () => { }); props.setProps({ initialValues: { emailAddress: 'foo@clerk.com' } }); - render(, { wrapper }); + const screen = render(, { wrapper }); screen.getByDisplayValue(/foo@clerk.com/i); }); @@ -230,7 +230,7 @@ describe('SignUpStart', () => { }); props.setProps({ initialValues: { phoneNumber: '+306911111111' } }); - render(, { wrapper }); + const screen = render(, { wrapper }); screen.getByDisplayValue(/691 1111111/i); }); @@ -251,8 +251,8 @@ describe('SignUpStart', () => { f.withRestrictedMode(); }); - render(, { wrapper }); - screen.getByText('Access restricted'); + const screen = render(, { wrapper }); + screen.getByText('Restricted access'); }); }); @@ -265,7 +265,7 @@ describe('SignUpStart', () => { }); }); - render(, { wrapper }); + const screen = render(, { wrapper }); screen.getByText('Terms of Service'); screen.getByText('Privacy Policy'); }); From 016fee1b96f231da7830073d3c0dd85bec1e5543 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 12:02:49 +0300 Subject: [PATCH 17/27] fix(clerk-js): Fix styling for checkbox --- .../SignUp/__tests__/SignUpContinue.test.tsx | 2 +- .../clerk-js/src/ui/polishedAppearance.ts | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx index 6ba58574a62..805945067a1 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx @@ -89,7 +89,7 @@ describe('SignUpContinue', () => { termsOfService: 'https://clerk.dev/tos', }); }); - render(, { wrapper }); + const screen = render(, { wrapper }); screen.getByText(/missing/i); screen.getByText(/Terms Of Service/i); screen.getByText(/Privacy Policy/i); diff --git a/packages/clerk-js/src/ui/polishedAppearance.ts b/packages/clerk-js/src/ui/polishedAppearance.ts index a090ec78edd..623aeec6e80 100644 --- a/packages/clerk-js/src/ui/polishedAppearance.ts +++ b/packages/clerk-js/src/ui/polishedAppearance.ts @@ -37,6 +37,30 @@ const inputShadowStyles = ( }; }; +const checkboxShadowStyles = ( + theme: InternalTheme, + colors: { idle1: string; idle2: string; hover1: string; hover2: string; focus: string }, +) => { + const idleShadow = [ + `0px 0px 0px 1px ${colors.idle1}`, + theme.shadows.$input.replace('{{color}}', colors.idle2), + ].toString(); + const hoverShadow = [ + `0px 0px 0px 1px ${colors.hover1}`, + theme.shadows.$input.replace('{{color}}', colors.hover2), + ].toString(); + + return { + boxShadow: idleShadow, + '&:hover': { + boxShadow: hoverShadow, + }, + '&:focus-visible': { + boxShadow: [hoverShadow, theme.shadows.$focusRing.replace('{{color}}', colors.focus)].toString(), + }, + }; +}; + const inputStyles = (theme: InternalTheme) => ({ borderWidth: 0, ...inputShadowStyles(theme, { @@ -144,11 +168,19 @@ export const polishedAppearance: Appearance = { }, }, checkbox: { - padding: `${theme.space.$1}`, + ...checkboxShadowStyles(theme, { + idle1: theme.colors.$neutralAlpha150, + idle2: theme.colors.$neutralAlpha100, + hover1: theme.colors.$neutralAlpha300, + hover2: theme.colors.$neutralAlpha150, + focus: theme.colors.$neutralAlpha150, + }), + padding: theme.space.$1, width: theme.sizes.$3x5, height: theme.sizes.$3x5, appearance: 'none', borderRadius: theme.radii.$sm, + border: 'none', '&:checked': { backgroundImage: `url("data:image/svg+xml,%3Csvg width='16' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.25 8L6.5 9.75L9.75 4.25' stroke='${theme.colors.$whiteAlpha900}' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3C/svg%3E")`, borderColor: theme.colors.$transparent, From d3e4c7ba91befe4036d861993d642e61486cb9cf Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 12:08:31 +0300 Subject: [PATCH 18/27] chore(clerk-js): Move Legal consent checkbox to its own component --- .../src/ui/components/SignUp/SignUpForm.tsx | 4 +- .../src/ui/components/SignUp/SignUpStart.tsx | 3 +- packages/clerk-js/src/ui/elements/Form.tsx | 95 +----------------- .../src/ui/elements/LegalConsentCheckbox.tsx | 97 +++++++++++++++++++ packages/clerk-js/src/ui/elements/index.ts | 1 + 5 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx index bdb505699a2..e23778e1843 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpForm.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Col, localizationKeys, useAppearance } from '../../customizables'; -import { Form } from '../../elements'; +import { Form, LegalCheckbox } from '../../elements'; import { CaptchaElement } from '../../elements/CaptchaElement'; import { mqu } from '../../styledSystem'; import type { FormControlState } from '../../utils'; @@ -122,7 +122,7 @@ export const SignUpForm = (props: SignUpFormProps) => { > {shouldShow('__experimental_legalAccepted') && ( - diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index ff8ee165b42..b8888bace28 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -10,6 +10,7 @@ import { Card, Form, Header, + LegalCheckbox, LoadingCard, SocialButtonsReversibleContainerWithDivider, withCardStateProvider, @@ -294,7 +295,7 @@ function _SignUpStart(): JSX.Element { {!shouldShowForm && ( - diff --git a/packages/clerk-js/src/ui/elements/Form.tsx b/packages/clerk-js/src/ui/elements/Form.tsx index cd44df834e1..20a62c60d8e 100644 --- a/packages/clerk-js/src/ui/elements/Form.tsx +++ b/packages/clerk-js/src/ui/elements/Form.tsx @@ -3,21 +3,8 @@ import type { FieldId } from '@clerk/types'; import type { PropsWithChildren } from 'react'; import React, { forwardRef, useState } from 'react'; -import { useEnvironment } from '../../ui/contexts'; import type { LocalizationKey } from '../customizables'; -import { - Button, - Col, - descriptors, - Flex, - Form as FormPrim, - FormLabel, - Link, - localizationKeys, - Text, - useAppearance, - useLocalizations, -} from '../customizables'; +import { Button, Col, descriptors, Flex, Form as FormPrim, localizationKeys } from '../customizables'; import { useLoadingStatus } from '../hooks'; import type { PropsOfComponent } from '../styledSystem'; import type { OTPInputProps } from './CodeControl'; @@ -229,85 +216,6 @@ const Checkbox = ( ); }; -const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: string }) => { - const { t } = useLocalizations(); - return ( - - {t(localizationKeys('signUp.legalConsent.checkbox.label__prefixText'))} - {props.termsUrl && ( - <> - {' '} - - - )} - - {props.termsUrl && props.privacyPolicyUrl && ( - <> {t(localizationKeys('signUp.legalConsent.checkbox.label__conjunctionText'))} - )} - - {props.privacyPolicyUrl && ( - <> - {' '} - - - )} - - ); -}; - -const LegalCheckbox = ( - props: CommonFieldRootProps & { - description?: string | LocalizationKey; - }, -) => { - const { displayConfig } = useEnvironment(); - const { parsedLayout } = useAppearance(); - - const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl; - const privacyPolicy = parsedLayout.privacyPageUrl || displayConfig.privacyPolicyUrl; - - return ( - - - - ({ - paddingLeft: t.space.$1x5, - })} - > - - - - - ); -}; - const RadioGroup = ( props: Omit, 'infoText' | 'type' | 'validatePassword' | 'label' | 'placeholder'>, ) => { @@ -388,7 +296,6 @@ export const Form = { InputGroup, RadioGroup, Checkbox, - LegalCheckbox: LegalCheckbox, SubmitButton: FormSubmit, ResetButton: FormReset, }; diff --git a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx new file mode 100644 index 00000000000..d5de0bf65e3 --- /dev/null +++ b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +import { useEnvironment } from '../../ui/contexts'; +import type { LocalizationKey } from '../customizables'; +import { + descriptors, + Flex, + FormLabel, + Link, + localizationKeys, + Text, + useAppearance, + useLocalizations, +} from '../customizables'; +import type { PropsOfComponent } from '../styledSystem'; +import { Field } from './FieldControl'; + +const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: string }) => { + const { t } = useLocalizations(); + return ( + + {t(localizationKeys('signUp.legalConsent.checkbox.label__prefixText'))} + {props.termsUrl && ( + <> + {' '} + + + )} + + {props.termsUrl && props.privacyPolicyUrl && ( + <> {t(localizationKeys('signUp.legalConsent.checkbox.label__conjunctionText'))} + )} + + {props.privacyPolicyUrl && ( + <> + {' '} + + + )} + + ); +}; + +type CommonFieldRootProps = Omit, 'children' | 'elementDescriptor' | 'elementId'>; + +export const LegalCheckbox = ( + props: CommonFieldRootProps & { + description?: string | LocalizationKey; + }, +) => { + const { displayConfig } = useEnvironment(); + const { parsedLayout } = useAppearance(); + + const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl; + const privacyPolicy = parsedLayout.privacyPageUrl || displayConfig.privacyPolicyUrl; + + return ( + + + + ({ + paddingLeft: t.space.$1x5, + })} + > + + + + + ); +}; diff --git a/packages/clerk-js/src/ui/elements/index.ts b/packages/clerk-js/src/ui/elements/index.ts index 6d86e286eea..0836b5465e7 100644 --- a/packages/clerk-js/src/ui/elements/index.ts +++ b/packages/clerk-js/src/ui/elements/index.ts @@ -56,3 +56,4 @@ export * from './ProfileCard'; export * from './Gauge'; export * from './Animated'; export * from './DevModeNotice'; +export * from './LegalConsentCheckbox'; From 0bc578c682a66fcd2fa2766e24781c9a4661150a Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 12:21:03 +0300 Subject: [PATCH 19/27] chore(clerk-js): Apply lint fixes --- packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx index d5de0bf65e3..9540705fdce 100644 --- a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx +++ b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { useEnvironment } from '../../ui/contexts'; import type { LocalizationKey } from '../customizables'; import { From b731ad0eff88df653fd35e3c19b081a3136eb84d Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 16:31:45 +0300 Subject: [PATCH 20/27] fix(clerk-js,types,localization): Mark legal consent as experimental --- .../clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 4 ++-- .../clerk-js/src/ui/elements/LegalConsentCheckbox.tsx | 8 ++++---- packages/localizations/src/en-US.ts | 2 +- packages/types/src/localization.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index 8f3d3ba3f4b..eceef780ec3 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -171,11 +171,11 @@ function _SignUpContinue() { const headerTitle = !onlyLegalConsentMissing ? localizationKeys('signUp.continue.title') - : localizationKeys('signUp.legalConsent.continue.title'); + : localizationKeys('signUp.__experimental_legalConsent.continue.title'); const headerSubtitle = !onlyLegalConsentMissing ? localizationKeys('signUp.continue.subtitle') - : localizationKeys('signUp.legalConsent.continue.subtitle'); + : localizationKeys('signUp.__experimental_legalConsent.continue.subtitle'); return ( diff --git a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx index 9540705fdce..bdf7538e934 100644 --- a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx +++ b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx @@ -20,12 +20,12 @@ const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: strin variant='body' as='span' > - {t(localizationKeys('signUp.legalConsent.checkbox.label__prefixText'))} + {t(localizationKeys('signUp.__experimental_legalConsent.checkbox.label__prefixText'))} {props.termsUrl && ( <> {' '} {t(localizationKeys('signUp.legalConsent.checkbox.label__conjunctionText'))} + <> {t(localizationKeys('signUp.__experimental_legalConsent.checkbox.label__conjunctionText'))} )} {props.privacyPolicyUrl && ( <> {' '} Date: Thu, 17 Oct 2024 17:28:39 +0300 Subject: [PATCH 21/27] fix(clerk-js): Check if the __experimental_legalAccepted is not undefined --- packages/clerk-js/src/core/resources/SignUp.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 9001087bbe9..2dbaf053378 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -113,8 +113,10 @@ export class SignUp extends BaseResource implements SignUpResource { } // TODO(@vaggelis): Remove this once the legalAccepted is stable - paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; - paramsWithCaptcha.__experimental_legalAccepted = undefined; + if (typeof params.__experimental_legalAccepted !== 'undefined') { + paramsWithCaptcha.legalAccepted = params.__experimental_legalAccepted; + paramsWithCaptcha.__experimental_legalAccepted = undefined; + } return this._basePost({ path: this.pathRoot, @@ -319,9 +321,11 @@ export class SignUp extends BaseResource implements SignUpResource { update = (params: SignUpUpdateParams): Promise => { // TODO(@vaggelis): Remove this once the legalAccepted is stable - // @ts-expect-error - We need to remove the __experimental_legalAccepted key from the params - params.legalAccepted = params.__experimental_legalAccepted; - params.__experimental_legalAccepted = undefined; + if (typeof params.__experimental_legalAccepted !== 'undefined') { + // @ts-expect-error - We need to remove the __experimental_legalAccepted key from the params + params.legalAccepted = params.__experimental_legalAccepted; + params.__experimental_legalAccepted = undefined; + } return this._basePatch({ body: normalizeUnsafeMetadata(params), From 9b1e072d9a4497a3c7f40e38d67a0c6c9e5e0d5d Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 17:30:46 +0300 Subject: [PATCH 22/27] tests(clerk-js): Revert changes for SignUpStart --- .../components/SignUp/__tests__/SignUpStart.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx index c36ee29d6a4..88f5b310f98 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx @@ -220,7 +220,7 @@ describe('SignUpStart', () => { }); props.setProps({ initialValues: { emailAddress: 'foo@clerk.com' } }); - const screen = render(, { wrapper }); + render(, { wrapper }); screen.getByDisplayValue(/foo@clerk.com/i); }); @@ -230,7 +230,7 @@ describe('SignUpStart', () => { }); props.setProps({ initialValues: { phoneNumber: '+306911111111' } }); - const screen = render(, { wrapper }); + render(, { wrapper }); screen.getByDisplayValue(/691 1111111/i); }); @@ -251,8 +251,8 @@ describe('SignUpStart', () => { f.withRestrictedMode(); }); - const screen = render(, { wrapper }); - screen.getByText('Restricted access'); + render(, { wrapper }); + screen.getByText('Access restricted'); }); }); @@ -265,7 +265,7 @@ describe('SignUpStart', () => { }); }); - const screen = render(, { wrapper }); + render(, { wrapper }); screen.getByText('Terms of Service'); screen.getByText('Privacy Policy'); }); From a75f7fd0c90625fefb9e357a658b80d7b3797ef2 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 17 Oct 2024 20:19:35 +0300 Subject: [PATCH 23/27] tests(clerk-js): Fix SignUpContinue tests --- .../SignUp/__tests__/SignUpContinue.test.tsx | 3 +-- .../components/SignUp/__tests__/SignUpStart.test.tsx | 1 + packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx index 805945067a1..e058ca32fdc 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx @@ -82,7 +82,7 @@ describe('SignUpContinue', () => { const { wrapper } = await createFixtures(f => { f.withEmailAddress({ required: true }); f.withPassword({ required: true }); - f.startSignUpWithEmailAddress(); + f.startSignUpWithMissingLegalAccepted(); f.withLegalConsent(); f.withTermsPrivacyPolicyUrls({ privacyPolicy: 'https://clerk.dev/privacy', @@ -90,7 +90,6 @@ describe('SignUpContinue', () => { }); }); const screen = render(, { wrapper }); - screen.getByText(/missing/i); screen.getByText(/Terms Of Service/i); screen.getByText(/Privacy Policy/i); }); diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx index 88f5b310f98..a79adcde688 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx @@ -259,6 +259,7 @@ describe('SignUpStart', () => { describe('Legal consent', () => { it('shows sign up component with legal consent checkbox', async () => { const { wrapper } = await createFixtures(f => { + f.withLegalConsent(); f.withTermsPrivacyPolicyUrls({ privacyPolicy: 'https://clerk.dev/privacy', termsOfService: 'https://clerk.dev/tos', diff --git a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts index bfc660652ab..2a595481de0 100644 --- a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts @@ -262,7 +262,16 @@ const createSignUpFixtureHelpers = (baseClient: ClientJSON) => { } as SignUpJSON; }; - return { startSignUpWithEmailAddress, startSignUpWithPhoneNumber }; + const startSignUpWithMissingLegalAccepted = () => { + baseClient.sign_up = { + id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', + status: 'missing_requirements', + legal_accepted_at: null, + missing_fields: ['legal_accepted'], + } as SignUpJSON; + }; + + return { startSignUpWithEmailAddress, startSignUpWithPhoneNumber, startSignUpWithMissingLegalAccepted }; }; const createAuthConfigFixtureHelpers = (environment: EnvironmentJSON) => { From a71579bd5d7a91728dab0a8a1802e1d78e090d6e Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Fri, 18 Oct 2024 11:49:18 +0300 Subject: [PATCH 24/27] chore(clerk-js): Remove label from form control --- packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 2 +- packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx index eceef780ec3..8cec478f8dc 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx @@ -73,7 +73,7 @@ function _SignUpContinue() { }), __experimental_legalAccepted: useFormControl('__experimental_legalAccepted', '', { type: 'checkbox', - label: 'I agree to the Terms of Service and Privacy Policy', + label: '', defaultChecked: false, isRequired: userSettings.signUp.legal_consent_enabled || false, }), diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index b8888bace28..1d748d56c7e 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -83,7 +83,7 @@ function _SignUpStart(): JSX.Element { }), __experimental_legalAccepted: useFormControl('__experimental_legalAccepted', '', { type: 'checkbox', - label: 'I agree to the Terms of Service and Privacy Policy', + label: '', defaultChecked: false, isRequired: userSettings.signUp.legal_consent_enabled || false, }), From cfed76edaae1a454fd028ce7c4c6d85665ae91e9 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Fri, 18 Oct 2024 14:20:48 +0300 Subject: [PATCH 25/27] chore(clerk-js,types,localization): Introduce LinkRenderer --- .../src/ui/elements/LegalConsentCheckbox.tsx | 65 +++++++++---------- .../clerk-js/src/ui/elements/LinkRenderer.tsx | 44 +++++++++++++ .../elements/__tests__/LinkRenderer.test.tsx | 44 +++++++++++++ .../ui/localization/localizationModifiers.ts | 5 ++ packages/localizations/src/en-US.ts | 8 +-- packages/types/src/localization.ts | 7 +- 6 files changed, 131 insertions(+), 42 deletions(-) create mode 100644 packages/clerk-js/src/ui/elements/LinkRenderer.tsx create mode 100644 packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx diff --git a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx index bdf7538e934..aabf7e4fb76 100644 --- a/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx +++ b/packages/clerk-js/src/ui/elements/LegalConsentCheckbox.tsx @@ -4,7 +4,6 @@ import { descriptors, Flex, FormLabel, - Link, localizationKeys, Text, useAppearance, @@ -12,47 +11,44 @@ import { } from '../customizables'; import type { PropsOfComponent } from '../styledSystem'; import { Field } from './FieldControl'; +import { LinkRenderer } from './LinkRenderer'; const LegalCheckboxLabel = (props: { termsUrl?: string; privacyPolicyUrl?: string }) => { + const { termsUrl, privacyPolicyUrl } = props; const { t } = useLocalizations(); + let localizationKey: LocalizationKey | undefined; + + if (termsUrl && privacyPolicyUrl) { + localizationKey = localizationKeys( + 'signUp.__experimental_legalConsent.checkbox.label__termsOfServiceAndPrivacyPolicy', + { + termsOfServiceLink: props.termsUrl, + privacyPolicyLink: props.privacyPolicyUrl, + }, + ); + } else if (termsUrl) { + localizationKey = localizationKeys('signUp.__experimental_legalConsent.checkbox.label__onlyTermsOfService', { + termsOfServiceLink: props.termsUrl, + }); + } else if (privacyPolicyUrl) { + localizationKey = localizationKeys('signUp.__experimental_legalConsent.checkbox.label__onlyPrivacyPolicy', { + privacyPolicyLink: props.privacyPolicyUrl, + }); + } + return ( - {t(localizationKeys('signUp.__experimental_legalConsent.checkbox.label__prefixText'))} - {props.termsUrl && ( - <> - {' '} - - - )} - - {props.termsUrl && props.privacyPolicyUrl && ( - <> {t(localizationKeys('signUp.__experimental_legalConsent.checkbox.label__conjunctionText'))} - )} - - {props.privacyPolicyUrl && ( - <> - {' '} - - - )} + ({ + textDecoration: 'underline', + textUnderlineOffset: t.space.$1, + })} + /> ); }; @@ -82,6 +78,7 @@ export const LegalCheckbox = ( htmlFor={props.itemID} sx={t => ({ paddingLeft: t.space.$1x5, + textAlign: 'left', })} > , 'href' | 'children'> { + text: string; +} + +const LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g; // parses [text](url) + +export const LinkRenderer: React.FC = memo(({ text, ...linkProps }) => { + const memoizedLinkProps = useMemo(() => linkProps, [linkProps]); + + const renderedContent = useMemo(() => { + const parts: (string | JSX.Element)[] = []; + let lastIndex = 0; + + text.replace(LINK_REGEX, (match, linkText, url, offset) => { + if (offset > lastIndex) { + parts.push(text.slice(lastIndex, offset)); + } + parts.push( + + {linkText} + , + ); + lastIndex = offset + match.length; + return match; + }); + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts; + }, [text, memoizedLinkProps]); + + return renderedContent; +}); diff --git a/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx new file mode 100644 index 00000000000..ddb3fec86cf --- /dev/null +++ b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx @@ -0,0 +1,44 @@ +import { describe, it } from '@jest/globals'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { bindCreateFixtures } from '../../utils/test/createFixtures'; +import { LinkRenderer } from '../LinkRenderer'; + +const { createFixtures } = bindCreateFixtures('UserProfile'); + +describe('LinkRenderer', () => { + it('renders a simple link', async () => { + const { wrapper } = await createFixtures(); + + const screen = render(, { + wrapper, + }); + + expect(screen.queryByRole('link', { name: 'Terms of Service' })).toBeInTheDocument(); + }); + + it('renders multiple links', async () => { + const { wrapper } = await createFixtures(); + + const screen = render( + , + { wrapper }, + ); + + expect(screen.queryByRole('link', { name: 'Terms of Service' })).toBeInTheDocument(); + expect(screen.queryByRole('link', { name: 'Privacy Policy' })).toBeInTheDocument(); + }); + + it('does not render links with broken format', async () => { + const { wrapper } = await createFixtures(); + + const screen = render( + , + { wrapper }, + ); + + screen.findByText('[Terms of Service]https://example.com/terms)'); + expect(screen.queryByRole('link', { name: 'Privacy Policy' })).toBeInTheDocument(); + }); +}); diff --git a/packages/clerk-js/src/ui/localization/localizationModifiers.ts b/packages/clerk-js/src/ui/localization/localizationModifiers.ts index 1e84382923c..ed266d10ea3 100644 --- a/packages/clerk-js/src/ui/localization/localizationModifiers.ts +++ b/packages/clerk-js/src/ui/localization/localizationModifiers.ts @@ -27,9 +27,14 @@ const numeric = (val: Date | number | string, locale?: string) => { } }; +const link = (val: string, label?: string) => { + return `[${label}](${val})`; +}; + export const MODIFIERS = { titleize, timeString, weekday, numeric, + link, } as const; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index ceab555bf8a..be3a0961b0f 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -521,10 +521,10 @@ export const enUS: LocalizationResource = { title: 'Legal consent', }, checkbox: { - label__prefixText: 'I agree to the', - label__privacyPolicyText: 'Privacy Policy', - label__termsOfServiceText: 'Terms of Service', - label__conjunctionText: 'and', + label__termsOfServiceAndPrivacyPolicy: + 'I agree to the {{ termsOfServiceLink || link("Terms of Service") }} and {{ privacyPolicyLink || link("Privacy Policy") }}', + label__onlyTermsOfService: 'I agree to the {{ termsOfServiceLink || link("Terms of Service") }}', + label__onlyPrivacyPolicy: 'I agree to the {{ privacyPolicyLink || link("Privacy Policy") }}', }, }, }, diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 46a3088bb1b..ef95d7564ab 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -157,10 +157,9 @@ type _LocalizationResource = { subtitle: LocalizationValue; }; checkbox: { - label__prefixText: LocalizationValue; - label__termsOfServiceText: LocalizationValue; - label__privacyPolicyText: LocalizationValue; - label__conjunctionText: LocalizationValue; + label__termsOfServiceAndPrivacyPolicy: LocalizationValue; + label__onlyPrivacyPolicy: LocalizationValue; + label__onlyTermsOfService: LocalizationValue; }; }; }; From 94247d1166830e5eb0d2f0410b6dd32cc14961ec Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Fri, 18 Oct 2024 14:21:41 +0300 Subject: [PATCH 26/27] test(clerk-js): Update tests fixture --- .../clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx index ddb3fec86cf..3c6ebab89ec 100644 --- a/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx +++ b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { LinkRenderer } from '../LinkRenderer'; -const { createFixtures } = bindCreateFixtures('UserProfile'); +const { createFixtures } = bindCreateFixtures('SignUp'); describe('LinkRenderer', () => { it('renders a simple link', async () => { From 248b505cc3a023b83da5666d582c634340194c7d Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Fri, 18 Oct 2024 18:01:46 +0300 Subject: [PATCH 27/27] fix(clerk-js): Increase bundlewatch limit for ui-common --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 7e0a980381c..7f6ab8562b5 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "./dist/clerk.browser.js", "maxSize": "68kB" }, { "path": "./dist/clerk.headless.js", "maxSize": "44kB" }, - { "path": "./dist/ui-common*.js", "maxSize": "86KB" }, + { "path": "./dist/ui-common*.js", "maxSize": "87KB" }, { "path": "./dist/vendors*.js", "maxSize": "70KB" }, { "path": "./dist/coinbase*.js", "maxSize": "58KB" }, { "path": "./dist/createorganization*.js", "maxSize": "5KB" },