diff --git a/packages/clerk-js/src/ui/userProfile/UserProfile.test.tsx b/packages/clerk-js/src/ui/userProfile/UserProfile.test.tsx index 0ffd31c77a..2f24a31829 100644 --- a/packages/clerk-js/src/ui/userProfile/UserProfile.test.tsx +++ b/packages/clerk-js/src/ui/userProfile/UserProfile.test.tsx @@ -12,6 +12,7 @@ import { } from '@clerk/types'; import { AuthConfig } from 'core/resources/AuthConfig'; import { ExternalAccount } from 'core/resources/ExternalAccount'; +import { UserSettings } from 'core/resources/UserSettings'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -19,16 +20,18 @@ import { UserProfile } from './UserProfile'; const mockNavigate = jest.fn(); const mockUseCoreSignIn = jest.fn( - () => - ({ + () => ( + { status: null, - } as SignInResource), + } as SignInResource + ), ); const mockUseCoreSignUp = jest.fn( - () => - ({ + () => ( + { status: null, - } as SignUpResource), + } as SignUpResource + ), ); const mockUseCoreSessionList = jest.fn(() => [] as SessionResource[]); @@ -66,11 +69,47 @@ jest.mock('ui/contexts', () => ({ }, }, }, + userSettings: { + attributes: { + phone_number: { + // this should be true since it is a first factor but keeping it false for the needs of the test case + enabled: false, + used_for_second_factor: true, + second_factors: ['phone_code'] + }, + email_address: { + // this should be true since it is a first factor but keeping it false for the needs of the test case + enabled: false, + used_for_first_factor: true, + first_factors: ['email_code'] + }, + first_name: { + enabled: true, + }, + last_name: { + enabled: false, + }, + username: { + enabled: false, + }, + password: { + enabled: false, + }, + web3_wallet: { + enabled: false + } + }, + social: { + oauth_google: { + enabled: true, + }, + oauth_facebook: { + enabled: true, + } + } + } as Partial, authConfig: { - secondFactors: ['phone_code'], - singleSessionMode: true, - firstFactors: ['email_address', 'oauth_google', 'oauth_facebook'], - emailAddressVerificationStrategies: ['email_code'], + singleSessionMode: true } as Partial, })), withCoreUserGuard: (a: any) => a, diff --git a/packages/clerk-js/src/ui/userProfile/account/Account.test.tsx b/packages/clerk-js/src/ui/userProfile/account/Account.test.tsx index 132e131325..8f806cfbc7 100644 --- a/packages/clerk-js/src/ui/userProfile/account/Account.test.tsx +++ b/packages/clerk-js/src/ui/userProfile/account/Account.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@clerk/shared/testUtils'; -import type { AuthConfigResource, UserResource } from '@clerk/types'; +import type { UserResource, UserSettingsResource } from '@clerk/types'; import React from 'react'; import { Account } from './Account'; @@ -26,6 +26,21 @@ jest.mock('ui/hooks', () => { }; }); +const OTHER_ATTRIBUTES = { + username: { + enabled: false, + }, + email_address: { + enabled: false + }, + phone_number: { + enabled: false + }, + web3_wallet: { + enabled: false + } +}; + describe('', () => { afterEach(() => { jest.clearAllMocks(); @@ -34,9 +49,18 @@ describe('', () => { it('renders personal information for auth config that requires name', () => { mockEnvironment.mockImplementation(() => { return { - authConfig: { - firstName: 'required', - } as AuthConfigResource, + userSettings: { + attributes: { + first_name: { + enabled: true, + required: true + }, + last_name: { + enabled: false + }, + ...OTHER_ATTRIBUTES + } + } as UserSettingsResource, }; }); render(); @@ -46,9 +70,17 @@ describe('', () => { it('renders personal information for auth config that has name turned on', () => { mockEnvironment.mockImplementation(() => { return { - authConfig: { - lastName: 'on', - } as AuthConfigResource, + userSettings: { + attributes: { + last_name: { + enabled: true, + }, + first_name: { + enabled: false, + }, + ...OTHER_ATTRIBUTES + } + } as UserSettingsResource }; }); render(); @@ -58,10 +90,17 @@ describe('', () => { it('does not render personal information for auth config that has named turned off', () => { mockEnvironment.mockImplementation(() => { return { - authConfig: { - firstName: 'off', - lastName: 'off', - } as AuthConfigResource, + userSettings: { + attributes: { + first_name: { + enabled: false, + }, + last_name: { + enabled: false, + }, + ...OTHER_ATTRIBUTES + } + } as UserSettingsResource }; }); render(); diff --git a/packages/clerk-js/src/ui/userProfile/account/Account.tsx b/packages/clerk-js/src/ui/userProfile/account/Account.tsx index 3b47f6c91e..0066d9d648 100644 --- a/packages/clerk-js/src/ui/userProfile/account/Account.tsx +++ b/packages/clerk-js/src/ui/userProfile/account/Account.tsx @@ -1,4 +1,4 @@ -import type { AuthConfigResource } from '@clerk/types'; +import type { UserSettingsResource } from '@clerk/types'; import React from 'react'; import { useEnvironment } from 'ui/contexts'; import { PersonalInformationCard } from 'ui/userProfile/account/personalInformation'; @@ -6,7 +6,7 @@ import { ProfileCard } from 'ui/userProfile/account/profileCard'; import { PageHeading } from 'ui/userProfile/pageHeading'; export const Account = (): JSX.Element => { - const { authConfig } = useEnvironment(); + const { userSettings } = useEnvironment(); return ( <> @@ -15,13 +15,14 @@ export const Account = (): JSX.Element => { subtitle='Manage settings related to your account' /> - {shouldShowPersonalInformation(authConfig) && } + {shouldShowPersonalInformation(userSettings) && } ); }; function shouldShowPersonalInformation( - authConfig: AuthConfigResource, + userSettings: UserSettingsResource, ): boolean { - return authConfig.firstName !== 'off' || authConfig.lastName !== 'off'; + const { attributes: { first_name, last_name } } = userSettings; + return first_name.enabled || last_name.enabled; } diff --git a/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.test.tsx b/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.test.tsx index b834b1a6b2..f777642318 100644 --- a/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.test.tsx +++ b/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.test.tsx @@ -1,6 +1,10 @@ import { renderJSON } from '@clerk/shared/testUtils'; import { EmailAddressResource, UserResource } from '@clerk/types'; import * as React from 'react'; +import { PartialDeep } from 'type-fest'; +import { + useEnvironment +} from 'ui/contexts'; import { ProfileCard } from './ProfileCard'; @@ -12,15 +16,8 @@ jest.mock('ui/hooks', () => { jest.mock('ui/contexts', () => { return { - useEnvironment: () => ({ - authConfig: { - emailAddress: 'required', - phoneNumber: 'on', - username: 'off', - }, - displayConfig: {}, - }), - useCoreUser: (): Partial => { + useEnvironment: jest.fn(), + useCoreUser: (): PartialDeep => { return { isPrimaryIdentification: jest.fn(() => true), emailAddresses: [ @@ -37,15 +34,65 @@ jest.mock('ui/contexts', () => { ], phoneNumbers: [], externalAccounts: [], + web3Wallets: [{ web3Wallet: '0x123456789' }] }; }, useCoreClerk: jest.fn(), }; }); +(useEnvironment as jest.Mock).mockImplementation(() => ({ + displayConfig: {}, + userSettings: { + attributes: { + email_address: { + enabled: true + }, + phone_number: { + enabled: true + }, + username: { + enabled: false + }, + web3_wallet: { + enabled: false + } + } + } +})); + describe('', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('renders the ProfileCard', () => { const tree = renderJSON(); expect(tree).toMatchSnapshot(); }); + + it('renders the ProfileCard ', () => { + (useEnvironment as jest.Mock).mockImplementation(() => ({ + displayConfig: {}, + userSettings: { + attributes: { + email_address: { + enabled: true + }, + phone_number: { + enabled: true + }, + username: { + enabled: false + }, + web3_wallet: { + enabled: true + } + } + } + })); + + const tree = renderJSON(); + expect(tree).toMatchSnapshot(); + }); }); diff --git a/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.tsx b/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.tsx index 2e9c9f8759..3966fd25a0 100644 --- a/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.tsx +++ b/packages/clerk-js/src/ui/userProfile/account/profileCard/ProfileCard.tsx @@ -21,12 +21,9 @@ function getWeb3WalletAddress(user: UserResource): string { return ''; } -function checkIfSectionIsOn(section: string) { - return section === 'on' || section === 'required'; -} - export function ProfileCard(): JSX.Element { - const { authConfig } = useEnvironment(); + const { userSettings } = useEnvironment(); + const { attributes } = userSettings; const user = useCoreUser(); const { navigate } = useNavigate(); const web3Wallet = getWeb3WalletAddress(user); @@ -100,10 +97,10 @@ export function ProfileCard(): JSX.Element { ); - const showWebWallet = !!web3Wallet; - const showUsername = checkIfSectionIsOn(authConfig.username); - const showEmail = checkIfSectionIsOn(authConfig.emailAddress); - const showPhone = checkIfSectionIsOn(authConfig.phoneNumber); + const showWebWallet = attributes.web3_wallet.enabled; + const showUsername = attributes.username.enabled; + const showEmail = attributes.email_address.enabled; + const showPhone = attributes.phone_number.enabled; return ( renders the ProfileCard 1`] = ` +
+
+

+ Profile +

+

+ Manage profile settings +

+
+
+
+
+ Photo +
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+ + + + +
+
+`; + exports[` renders the ProfileCard 1`] = `
{ jest.mock('ui/contexts/EnvironmentContext', () => { return { useEnvironment: jest.fn(() => ({ - authConfig: { - firstFactors: ['email_link'], - emailAddressVerificationStrategies: ['email_link'], - }, - })), + userSettings: { + attributes: { + email_address: { + enabled: true, + verifications: ['email_link'], + } + } + } + }) as PartialDeep), }; }); diff --git a/packages/clerk-js/src/ui/userProfile/emailAdressess/EmailDetail.test.tsx b/packages/clerk-js/src/ui/userProfile/emailAdressess/EmailDetail.test.tsx index 62464a23cf..1549f7c832 100644 --- a/packages/clerk-js/src/ui/userProfile/emailAdressess/EmailDetail.test.tsx +++ b/packages/clerk-js/src/ui/userProfile/emailAdressess/EmailDetail.test.tsx @@ -1,14 +1,14 @@ import { render, renderJSON, screen, userEvent } from '@clerk/shared/testUtils'; -import { EmailAddressResource } from '@clerk/types'; -import { SignInStrategyName } from '@clerk/types'; +import { EmailAddressResource, EnvironmentResource, SignInStrategyName, UserSettingsResource } from '@clerk/types'; import { ClerkAPIResponseError } from 'core/resources/Error'; import React from 'react'; - +import { PartialDeep } from 'type-fest'; import { EmailDetail } from './EmailDetail'; + const mockNavigate = jest.fn(); const mockUseCoreUser = jest.fn(); -let mockFirstFactors = [] as SignInStrategyName[]; +let mockFirstFactors = {} as PartialDeep; jest.mock('ui/hooks', () => ({ useNavigate: () => { @@ -39,11 +39,8 @@ jest.mock('ui/contexts', () => { return { useCoreUser: () => mockUseCoreUser(), useEnvironment: jest.fn(() => ({ - authConfig: { - firstFactors: mockFirstFactors, - emailAddressVerificationStrategies: mockFirstFactors, - }, - })), + userSettings: mockFirstFactors + }) as PartialDeep), useUserProfileContext: jest.fn(() => { return { routing: 'path', @@ -75,7 +72,14 @@ describe('', () => { }); it('displays verification errors', async () => { - mockFirstFactors = ['email_code']; + mockFirstFactors = { + attributes: { + email_address: { + used_for_first_factor: true, + first_factors: ['email_code'] + } + } + } mockUseCoreUser.mockImplementation(() => { return { primaryEmailAddressId: '1', diff --git a/packages/clerk-js/src/ui/userProfile/emailAdressess/utils.ts b/packages/clerk-js/src/ui/userProfile/emailAdressess/utils.ts index 285df08801..6a3e0f071d 100644 --- a/packages/clerk-js/src/ui/userProfile/emailAdressess/utils.ts +++ b/packages/clerk-js/src/ui/userProfile/emailAdressess/utils.ts @@ -4,7 +4,7 @@ export function magicLinksEnabledForInstance( env: EnvironmentResource, ): boolean { // TODO: email verification should have a supported strategies field - const { authConfig } = env; - const { emailAddressVerificationStrategies } = authConfig; - return emailAddressVerificationStrategies.includes('email_link'); + const { userSettings } = env; + const { email_address } = userSettings.attributes; + return email_address.enabled && email_address.verifications.includes('email_link'); } diff --git a/packages/clerk-js/src/ui/userProfile/security/ChangePassword.test.tsx b/packages/clerk-js/src/ui/userProfile/security/ChangePassword.test.tsx new file mode 100644 index 0000000000..fc55af4ba1 --- /dev/null +++ b/packages/clerk-js/src/ui/userProfile/security/ChangePassword.test.tsx @@ -0,0 +1,62 @@ +import { render, screen } from '@clerk/shared/testUtils'; +import { EnvironmentResource, UserResource } from '@clerk/types'; +import * as React from 'react'; +import { PartialDeep } from 'type-fest'; +import { useEnvironment } from 'ui/contexts'; +import { ChangePassword } from 'ui/userProfile/security/ChangePassword'; + + +jest.mock('ui/hooks', () => ({ + useNavigate: () => ({ navigate: jest.fn() }), +})); + +jest.mock('ui/router/RouteContext'); + +jest.mock('ui/contexts', () => { + return { + useCoreUser: (): Partial => ({ + passwordEnabled: true + }), + useEnvironment: jest.fn(), + }; +}); + +describe('', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the ChangePassword page with showing remove password cta', async () => { + ( + useEnvironment as jest.Mock> + ).mockImplementation(() => ({ + userSettings: { + attributes: { + password: { + enabled: true + } + } + } + })); + + render(); + expect(screen.getByText('Remove password')).toBeInTheDocument(); + }); + + it('renders the ChangePassword page without showing remove password cta', async () => { + ( + useEnvironment as jest.Mock> + ).mockImplementation(() => ({ + userSettings: { + attributes: { + password: { + enabled: false + } + } + } + })); + + render(); + expect(screen.getByText('Remove password')).toBeInTheDocument(); + }); +}); diff --git a/packages/clerk-js/src/ui/userProfile/security/ChangePassword.tsx b/packages/clerk-js/src/ui/userProfile/security/ChangePassword.tsx index c34d7bdd89..0f46dad505 100644 --- a/packages/clerk-js/src/ui/userProfile/security/ChangePassword.tsx +++ b/packages/clerk-js/src/ui/userProfile/security/ChangePassword.tsx @@ -11,7 +11,8 @@ import { PageHeading } from 'ui/userProfile/pageHeading'; export function ChangePassword(): JSX.Element { const user = useCoreUser(); - const { authConfig } = useEnvironment(); + const { userSettings } = useEnvironment(); + const { attributes } = userSettings; const { navigate } = useNavigate(); const password = useFieldState('password', ''); @@ -32,8 +33,7 @@ export function ChangePassword(): JSX.Element { } }; - const showRemovePassword = - user.passwordEnabled && authConfig.password !== 'required'; + const showRemovePassword = user.passwordEnabled && !attributes.password.required; const onClickRemovePassword = async () => { try { diff --git a/packages/clerk-js/src/ui/userProfile/security/Security.test.tsx b/packages/clerk-js/src/ui/userProfile/security/Security.test.tsx index d5ec43cd6c..402ec8ac9a 100644 --- a/packages/clerk-js/src/ui/userProfile/security/Security.test.tsx +++ b/packages/clerk-js/src/ui/userProfile/security/Security.test.tsx @@ -55,16 +55,24 @@ describe('', () => { useEnvironment as jest.Mock>, true, ).mockImplementation( - () => - ({ - authConfig: { - secondFactors: ['phone_code'], - password: 'on', + () => ( + { + userSettings: { + attributes: { + phone_number: { + used_for_second_factor: true, + second_factors: ['phone_code'] + }, + password: { + enabled: true + } + } }, displayConfig: { branded: true, }, - } as PartialDeep), + } as PartialDeep + ) ); const tree = renderJSON(); expect(tree).toMatchSnapshot(); diff --git a/packages/clerk-js/src/ui/userProfile/security/Security.tsx b/packages/clerk-js/src/ui/userProfile/security/Security.tsx index c62419f131..4b96bb4edb 100644 --- a/packages/clerk-js/src/ui/userProfile/security/Security.tsx +++ b/packages/clerk-js/src/ui/userProfile/security/Security.tsx @@ -8,16 +8,16 @@ import { PageHeading } from 'ui/userProfile/pageHeading'; import { ActiveDevicesCard } from './DevicesAndActivity/ActiveDevicesCard'; export function Security(): JSX.Element { - const { authConfig } = useEnvironment(); + const { userSettings } = useEnvironment(); + const { attributes } = userSettings; const { navigate } = useNavigate(); const user = useCoreUser(); - const showPasswordRow = - authConfig.password === 'on' || authConfig.password === 'required'; + const showPasswordRow = attributes.password.enabled; const showSecondFactorRow = - authConfig.secondFactors.length > 0 && - authConfig.secondFactors.includes('phone_code'); + attributes.phone_number.used_for_second_factor && + attributes.phone_number.second_factors.includes('phone_code'); const buildPasswordRow = () => ( - authConfig.secondFactors.length > 0 && - authConfig.secondFactors.includes('phone_code'); + phone_number.used_for_second_factor && + phone_number.second_factors.includes('phone_code'); return (