diff --git a/.changeset/cold-pianos-return.md b/.changeset/cold-pianos-return.md new file mode 100644 index 00000000000..a44aeecdc31 --- /dev/null +++ b/.changeset/cold-pianos-return.md @@ -0,0 +1,10 @@ +--- +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/localizations': patch +'@clerk/types': patch +--- + +Introduce `WhatsApp` as an alternative channel for phone code delivery. + +The new `channel` property accompanies the `phone_code` strategy. Possible values: `whatsapp` and `sms`. diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 49a0edb0e76..7f9b0ead95f 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,10 +1,10 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "594.2kB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "68.3KB" }, + { "path": "./dist/clerk.js", "maxSize": "595.5kB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "68.5KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "52KB" }, - { "path": "./dist/ui-common*.js", "maxSize": "104.4KB" }, + { "path": "./dist/ui-common*.js", "maxSize": "105KB" }, { "path": "./dist/vendors*.js", "maxSize": "39.5KB" }, { "path": "./dist/coinbase*.js", "maxSize": "38KB" }, { "path": "./dist/createorganization*.js", "maxSize": "5KB" }, @@ -13,7 +13,7 @@ { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" }, { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" }, { "path": "./dist/signin*.js", "maxSize": "14KB" }, - { "path": "./dist/signup*.js", "maxSize": "6.76KB" }, + { "path": "./dist/signup*.js", "maxSize": "7.5KB" }, { "path": "./dist/userbutton*.js", "maxSize": "5KB" }, { "path": "./dist/userprofile*.js", "maxSize": "16.5KB" }, { "path": "./dist/userverification*.js", "maxSize": "5KB" }, diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index c594e5e5d13..473fb61d638 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -120,6 +120,7 @@ export class SignIn extends BaseResource implements SignInResource { config = { phoneNumberId: factor.phoneNumberId, default: factor.default, + channel: factor.channel, } as PhoneCodeConfig; break; case 'web3_metamask_signature': diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 152a5e28233..358f73756c9 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -5,6 +5,7 @@ import type { OAuthStrategy, PasskeySettingsData, PasswordSettingsData, + PhoneCodeChannel, SamlSettings, SignInData, SignUpData, @@ -173,6 +174,17 @@ export class UserSettings extends BaseResource implements UserSettingsResource { .flat() as any as Web3Strategy[]; } + get alternativePhoneCodeChannels(): PhoneCodeChannel[] { + if (!this.attributes) { + return []; + } + + return Object.entries(this.attributes) + .filter(([name, attr]) => attr.used_for_first_factor && name === 'phone_number') + .map(([, desc]) => desc?.channels?.filter(factor => factor !== 'sms') || []) + .flat() as any as PhoneCodeChannel[]; + } + public constructor(data: UserSettingsJSON | UserSettingsJSONSnapshot | null = null) { super(); this.fromJSON(data); diff --git a/packages/clerk-js/src/core/resources/Verification.ts b/packages/clerk-js/src/core/resources/Verification.ts index e30bc9e6ffe..9de4244d3f9 100644 --- a/packages/clerk-js/src/core/resources/Verification.ts +++ b/packages/clerk-js/src/core/resources/Verification.ts @@ -2,6 +2,7 @@ import { errorToJSON, parseError } from '@clerk/shared/error'; import type { ClerkAPIError, PasskeyVerificationResource, + PhoneCodeChannel, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialCreationOptionsWithoutExtensions, SignUpVerificationJSON, @@ -32,6 +33,7 @@ export class Verification extends BaseResource implements VerificationResource { expireAt: Date | null = null; error: ClerkAPIError | null = null; verifiedAtClient: string | null = null; + channel?: PhoneCodeChannel; constructor(data: VerificationJSON | VerificationJSONSnapshot | null) { super(); @@ -57,6 +59,7 @@ export class Verification extends BaseResource implements VerificationResource { this.attempts = data.attempts; this.expireAt = unixEpochToDate(data.expire_at || undefined); this.error = data.error ? parseError(data.error) : null; + this.channel = data.channel || undefined; } return this; } diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap index e7578beaa23..6f858efc82d 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap @@ -229,6 +229,7 @@ Client { "createdSessionId": null, "firstFactorVerification": Verification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, @@ -259,6 +260,7 @@ Client { "resetPassword": [Function], "secondFactorVerification": Verification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, @@ -335,6 +337,7 @@ Client { "verifications": SignUpVerifications { "emailAddress": SignUpVerification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, @@ -361,6 +364,7 @@ Client { }, "externalAccount": Verification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, @@ -385,6 +389,7 @@ Client { }, "phoneNumber": SignUpVerification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, @@ -411,6 +416,7 @@ Client { }, "web3Wallet": SignUpVerification { "attempts": null, + "channel": undefined, "error": { "code": "", "longMessage": undefined, diff --git a/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx b/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx index e3345fe3eff..4cf2d30ca5d 100644 --- a/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx +++ b/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx @@ -1,4 +1,4 @@ -import type { OAuthProvider, Web3Provider } from '@clerk/types'; +import type { OAuthProvider, PhoneCodeProvider, Web3Provider } from '@clerk/types'; import { Box, descriptors, Text } from '../customizables'; import type { PropsOfComponent } from '../styledSystem'; @@ -6,7 +6,7 @@ import { common } from '../styledSystem'; type ProviderInitialIconProps = PropsOfComponent & { value: string; - id: Web3Provider | OAuthProvider; + id: Web3Provider | OAuthProvider | PhoneCodeProvider; }; export const ProviderInitialIcon = (props: ProviderInitialIconProps) => { diff --git a/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx b/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx index c064e0b1db4..3e79573c71b 100644 --- a/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx @@ -85,6 +85,7 @@ const AlternativeMethodsList = (props: AlternativeMethodListProps) => { {firstPartyFactors && firstPartyFactors.map((factor, i) => ( diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInAlternativePhoneCodePhoneNumberCard.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInAlternativePhoneCodePhoneNumberCard.tsx new file mode 100644 index 00000000000..6dc8fb2abc1 --- /dev/null +++ b/packages/clerk-js/src/ui/components/SignIn/SignInAlternativePhoneCodePhoneNumberCard.tsx @@ -0,0 +1,102 @@ +import type { PhoneCodeChannelData } from '@clerk/types'; + +import { Button, Col, descriptors, Flex, Image, localizationKeys } from '../../customizables'; +import { Card, Form, Header, useCardState } from '../../elements'; +import { CaptchaElement } from '../../elements/CaptchaElement'; +import { useEnabledThirdPartyProviders } from '../../hooks'; +import type { FormControlState } from '../../utils'; + +type SignUpAlternativePhoneCodePhoneNumberCardProps = { + handleSubmit: React.FormEventHandler; + phoneNumberFormState: FormControlState; + onUseAnotherMethod: () => void; + phoneCodeProvider: PhoneCodeChannelData; +}; + +export const SignInAlternativePhoneCodePhoneNumberCard = (props: SignUpAlternativePhoneCodePhoneNumberCardProps) => { + const { handleSubmit, phoneNumberFormState, onUseAnotherMethod, phoneCodeProvider } = props; + const { providerToDisplayData, strategyToDisplayData } = useEnabledThirdPartyProviders(); + const provider = phoneCodeProvider.name; + const channel = phoneCodeProvider.channel; + const card = useCardState(); + + return ( + + + + + {`${strategyToDisplayData[channel].name} ({ + width: theme.sizes.$7, + height: theme.sizes.$7, + maxWidth: '100%', + marginBottom: theme.sizes.$6, + })} + /> + + + + + {card.error} + + + + + + + + + + + + + + +