From 2637a67ef167d3d1893bcda9f958e1cf96ddd0a6 Mon Sep 17 00:00:00 2001 From: nikospapcom Date: Tue, 9 Jul 2024 14:49:27 +0300 Subject: [PATCH 1/3] feat(types,clerk-js): Introduce Custom OAuth providers --- .changeset/unlucky-hornets-roll.md | 14 ++++ .../clerk-js/src/ui/common/InitialIcon.tsx | 34 ++++++++++ packages/clerk-js/src/ui/common/index.ts | 1 + .../UserProfile/ConnectedAccountsMenu.tsx | 67 ++++++++++--------- .../UserProfile/ConnectedAccountsSection.tsx | 24 ++++--- .../src/ui/elements/SocialButtons.tsx | 27 +++++--- .../hooks/useEnabledThirdPartyProviders.tsx | 33 ++++++++- packages/types/src/oauth.ts | 4 +- packages/types/src/strategies.ts | 3 +- packages/types/src/userSettings.ts | 2 + 10 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 .changeset/unlucky-hornets-roll.md create mode 100644 packages/clerk-js/src/ui/common/InitialIcon.tsx diff --git a/.changeset/unlucky-hornets-roll.md b/.changeset/unlucky-hornets-roll.md new file mode 100644 index 00000000000..ade78d24b7f --- /dev/null +++ b/.changeset/unlucky-hornets-roll.md @@ -0,0 +1,14 @@ +--- +"@clerk/clerk-js": patch +"@clerk/types": patch +--- + +Added support for Custom OAuth providers + +- Updated strategy types to include CustomOAuthStrategy: + - Added `export type CustomOAuthStrategy = `oauth_custom_${string}`;` + - Modified `OAuthStrategy` to include CustomOAuthStrategy: + `export type OAuthStrategy = `oauth_${OAuthProvider}` | CustomOAuthStrategy;` +- Added new type: `export type CustomOauthProvider = `custom_${string}`;` and extend `OAuthProvider` type to include `CustomOauthProvider` +- Added support for displaying provider initials when logo_url is null for custom OAuth providers +- Created new `InitialIcon` component in order to display custom oauth provider initials if provider `logo_url` is null \ No newline at end of file diff --git a/packages/clerk-js/src/ui/common/InitialIcon.tsx b/packages/clerk-js/src/ui/common/InitialIcon.tsx new file mode 100644 index 00000000000..8e87a602dd2 --- /dev/null +++ b/packages/clerk-js/src/ui/common/InitialIcon.tsx @@ -0,0 +1,34 @@ +import { Box, Text } from '../customizables'; +import { common } from '../styledSystem'; + +type InitialIconProps = { + initials: string; +}; + +export const InitialIcon = ({ initials }: InitialIconProps) => { + return ( + ({ + ...common.centeredFlex('inline-flex'), + width: t.space.$4, + height: t.space.$4, + borderRadius: t.radii.$sm, + color: t.colors.$colorTextOnPrimaryBackground, + backgroundColor: t.colors.$primary500, + })} + > + ({ + ...common.centeredFlex('inline-flex'), + width: '100%', + fontSize: t.fontSizes.$sm, + fontWeight: t.fontWeights.$semibold, + })} + > + {initials} + + + ); +}; diff --git a/packages/clerk-js/src/ui/common/index.ts b/packages/clerk-js/src/ui/common/index.ts index 95597333ac5..3903c53dfdc 100644 --- a/packages/clerk-js/src/ui/common/index.ts +++ b/packages/clerk-js/src/ui/common/index.ts @@ -4,6 +4,7 @@ export * from './CalloutWithAction'; export * from './forms'; export * from './Gate'; export * from './InfiniteListSpinner'; +export * from './InitialIcon'; export * from './redirects'; export * from './verification'; export * from './withRedirect'; diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx index 973efa5bc41..21b56814aa3 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx @@ -2,6 +2,7 @@ import { useUser } from '@clerk/shared/react'; import type { OAuthProvider, OAuthStrategy } from '@clerk/types'; import { appendModalState } from '../../../utils'; +import { InitialIcon } from '../../common'; import { useUserProfileContext } from '../../contexts'; import { descriptors, Image, localizationKeys } from '../../customizables'; import { ProfileSection, useCardState } from '../../elements'; @@ -61,36 +62,42 @@ export const AddConnectedAccount = () => { triggerLocalizationKey={localizationKeys('userProfile.start.connectedAccountsSection.primaryButton')} id='connectedAccounts' > - {unconnectedStrategies.map(strategy => ( - connect(strategy)} - isDisabled={card.isLoading} - variant='ghost' - isLoading={card.loadingMetadata === strategy} - focusRing={false} - closeAfterClick={false} - localizationKey={localizationKeys('userProfile.connectedAccountPage.socialButtonsBlockButton', { - provider: strategyToDisplayData[strategy].name, - })} - sx={t => ({ - justifyContent: 'start', - gap: t.space.$2, - })} - leftIcon={ - {`Connect ({ width: theme.sizes.$4 })} - /> - } - /> - ))} + {unconnectedStrategies.map(strategy => { + const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? ( + {`Connect ({ width: theme.sizes.$4 })} + /> + ) : ( + + ); + + return ( + connect(strategy)} + isDisabled={card.isLoading} + variant='ghost' + isLoading={card.loadingMetadata === strategy} + focusRing={false} + closeAfterClick={false} + localizationKey={localizationKeys('userProfile.connectedAccountPage.socialButtonsBlockButton', { + provider: strategyToDisplayData[strategy].name, + })} + sx={t => ({ + justifyContent: 'start', + gap: t.space.$2, + })} + leftIcon={imageOrInitial} + /> + ); + })} ); }; diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx index d29508e7235..3735024f09c 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx @@ -2,6 +2,7 @@ import { useUser } from '@clerk/shared/react'; import type { ExternalAccountResource, OAuthProvider, OAuthScope, OAuthStrategy } from '@clerk/types'; import { appendModalState } from '../../../utils'; +import { InitialIcon } from '../../common'; import { useUserProfileContext } from '../../contexts'; import { Badge, Box, descriptors, Flex, Image, localizationKeys, Text } from '../../customizables'; import { Card, ProfileSection, ThreeDotsMenu, useCardState, withCardStateProvider } from '../../elements'; @@ -9,7 +10,7 @@ import { Action } from '../../elements/Action'; import { useActionContext } from '../../elements/Action/ActionRoot'; import { useEnabledThirdPartyProviders } from '../../hooks'; import { useRouter } from '../../router'; -import type { PropsOfComponent } from '../../styledSystem'; +import { type PropsOfComponent } from '../../styledSystem'; import { handleError } from '../../utils'; import { AddConnectedAccount } from './ConnectedAccountsMenu'; import { RemoveConnectedAccountForm } from './RemoveResourceForm'; @@ -59,17 +60,24 @@ export const ConnectedAccountsSection = withCardStateProvider(() => { ? error : localizationKeys('userProfile.start.connectedAccountsSection.subtitle__reauthorize'); + const ImageOrInitial = () => + providerToDisplayData[account.provider].iconUrl ? ( + {providerToDisplayData[account.provider].name} ({ width: theme.sizes.$4, flexShrink: 0 })} + /> + ) : ( + + ); + return ( ({ overflow: 'hidden', gap: t.space.$2 })}> - {providerToDisplayData[account.provider].name} ({ width: theme.sizes.$4, flexShrink: 0 })} - /> + { ? firstStrategyRef : null; + const imageOrInitial = strategyToDisplayData[strategy].iconUrl ? ( + {`Sign ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} + /> + ) : ( + + ); + return ( { isDisabled={card.isLoading} label={label} textLocalizationKey={localizedText} - icon={ - {`Sign ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} - /> - } + icon={imageOrInitial} /> ); })} diff --git a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx index 1063ba7a23e..c8d90a20fc0 100644 --- a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx +++ b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx @@ -37,16 +37,43 @@ const strategyToDisplayData: ThirdPartyStrategyToDataMap = fromEntries( ) as ThirdPartyStrategyToDataMap; export const useEnabledThirdPartyProviders = () => { - const { socialProviderStrategies, web3FirstFactors, authenticatableSocialStrategies } = useEnvironment().userSettings; + const { socialProviderStrategies, web3FirstFactors, authenticatableSocialStrategies, social } = + useEnvironment().userSettings; // Filter out any OAuth strategies that are not yet known, they are not included in our types. const knownSocialProviderStrategies = socialProviderStrategies.filter(s => oauthStrategies.includes(s)); + const customSocialProviderStrategies = socialProviderStrategies.filter( + s => !oauthStrategies.includes(s) && s.startsWith('oauth_custom_'), + ); + const knownAuthenticatableSocialStrategies = authenticatableSocialStrategies.filter(s => oauthStrategies.includes(s)); + const customAuthenticatableSocialStrategies = authenticatableSocialStrategies.filter( + s => !oauthStrategies.includes(s) && s.startsWith('oauth_custom_'), + ); + + customSocialProviderStrategies.map(s => { + const providerName = s.replace('oauth_', '') as OAuthProvider; + + providerToDisplayData[providerName] = { + strategy: s, + name: social[s].name, + iconUrl: social[s].logo_url || '', + }; + }); + + customAuthenticatableSocialStrategies.map(s => { + const providerId = s.replace('oauth_', '') as OAuthProvider; + strategyToDisplayData[s] = { + id: providerId, + iconUrl: social[s].logo_url || '', + name: social[s].name, + }; + }); return { - strategies: [...knownSocialProviderStrategies, ...web3FirstFactors], + strategies: [...knownSocialProviderStrategies, ...web3FirstFactors, ...customSocialProviderStrategies], web3Strategies: [...web3FirstFactors], - authenticatableOauthStrategies: [...knownAuthenticatableSocialStrategies], + authenticatableOauthStrategies: [...knownAuthenticatableSocialStrategies, ...customAuthenticatableSocialStrategies], strategyToDisplayData: strategyToDisplayData, providerToDisplayData: providerToDisplayData, }; diff --git a/packages/types/src/oauth.ts b/packages/types/src/oauth.ts index 9cc3728b603..397ebdcc9fc 100644 --- a/packages/types/src/oauth.ts +++ b/packages/types/src/oauth.ts @@ -36,6 +36,7 @@ export type SlackOauthProvider = 'slack'; export type LinearOauthProvider = 'linear'; export type XOauthProvider = 'x'; export type EnstallOauthProvider = 'enstall'; +export type CustomOauthProvider = `custom_${string}`; export type OAuthProvider = | FacebookOauthProvider @@ -64,7 +65,8 @@ export type OAuthProvider = | SlackOauthProvider | LinearOauthProvider | XOauthProvider - | EnstallOauthProvider; + | EnstallOauthProvider + | CustomOauthProvider; export const OAUTH_PROVIDERS: OAuthProviderData[] = [ { diff --git a/packages/types/src/strategies.ts b/packages/types/src/strategies.ts index ce2457dc61a..c46801542da 100644 --- a/packages/types/src/strategies.ts +++ b/packages/types/src/strategies.ts @@ -12,8 +12,9 @@ export type TOTPStrategy = 'totp'; export type BackupCodeStrategy = 'backup_code'; export type ResetPasswordPhoneCodeStrategy = 'reset_password_phone_code'; export type ResetPasswordEmailCodeStrategy = 'reset_password_email_code'; +export type CustomOAuthStrategy = `oauth_custom_${string}`; -export type OAuthStrategy = `oauth_${OAuthProvider}`; +export type OAuthStrategy = `oauth_${OAuthProvider}` | CustomOAuthStrategy; export type Web3Strategy = `web3_${Web3Provider}_signature`; export type SamlStrategy = 'saml'; diff --git a/packages/types/src/userSettings.ts b/packages/types/src/userSettings.ts index 948a38d9880..ca61c419537 100644 --- a/packages/types/src/userSettings.ts +++ b/packages/types/src/userSettings.ts @@ -21,6 +21,8 @@ export type OAuthProviderSettings = { required: boolean; authenticatable: boolean; strategy: OAuthStrategy; + name: string; + logo_url: string | null; }; export type AttributeDataJSON = { From 14cd59a2ac2d1a2cf1b1d3648250606b6006f5fa Mon Sep 17 00:00:00 2001 From: nikospapcom Date: Wed, 10 Jul 2024 14:11:57 +0300 Subject: [PATCH 2/3] chore(clerk-js): Sort authenticatableOauthStrategies by name and introduce tests for useEnabledThirdPartyProviders --- .changeset/unlucky-hornets-roll.md | 14 +- .../useEnabledThirdPartyProviders.test.tsx | 121 ++++++++++++++++++ .../hooks/useEnabledThirdPartyProviders.tsx | 14 +- 3 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx diff --git a/.changeset/unlucky-hornets-roll.md b/.changeset/unlucky-hornets-roll.md index ade78d24b7f..c5c32ab93d6 100644 --- a/.changeset/unlucky-hornets-roll.md +++ b/.changeset/unlucky-hornets-roll.md @@ -1,14 +1,14 @@ --- -"@clerk/clerk-js": patch -"@clerk/types": patch +"@clerk/clerk-js": minor +"@clerk/types": minor --- Added support for Custom OAuth providers -- Updated strategy types to include CustomOAuthStrategy: - - Added `export type CustomOAuthStrategy = `oauth_custom_${string}`;` - - Modified `OAuthStrategy` to include CustomOAuthStrategy: +- Updated strategy types to include `CustomOAuthStrategy`: + - Added the `CustomOAuthStrategy` type with the value `oauth_custom_${string}` + - Modified `OAuthStrategy` to include `CustomOAuthStrategy`: `export type OAuthStrategy = `oauth_${OAuthProvider}` | CustomOAuthStrategy;` -- Added new type: `export type CustomOauthProvider = `custom_${string}`;` and extend `OAuthProvider` type to include `CustomOauthProvider` -- Added support for displaying provider initials when logo_url is null for custom OAuth providers +- Added the `CustomOauthProvider` type with value `custom_${string}` and extended `OAuthProvider` type to include `CustomOauthProvider` +- Added support for displaying provider initials when `logo_url` is null for custom OAuth providers - Created new `InitialIcon` component in order to display custom oauth provider initials if provider `logo_url` is null \ No newline at end of file diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx new file mode 100644 index 00000000000..6675ba6ff81 --- /dev/null +++ b/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx @@ -0,0 +1,121 @@ +import { OAUTH_PROVIDERS } from '@clerk/types'; +import { renderHook } from '@testing-library/react'; + +import { bindCreateFixtures } from '../../../testUtils'; +import { useEnabledThirdPartyProviders } from '../useEnabledThirdPartyProviders'; + +const { createFixtures } = bindCreateFixtures('SignUp'); + +describe('useEnabledThirdPartyProviders', () => { + it('should returns correct strategies', async () => { + const { wrapper } = await createFixtures(f => { + f.withSocialProvider({ provider: 'apple' }); + f.withSocialProvider({ provider: 'facebook' }); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual(['oauth_apple', 'oauth_facebook']); + }); + + it('should returns correct sorting for all default strategies', async () => { + const { wrapper } = await createFixtures(f => { + OAUTH_PROVIDERS.map(p => f.withSocialProvider({ provider: p.provider })); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual([ + 'oauth_apple', + 'oauth_atlassian', + 'oauth_bitbucket', + 'oauth_box', + 'oauth_coinbase', + 'oauth_discord', + 'oauth_dropbox', + 'oauth_enstall', + 'oauth_facebook', + 'oauth_github', + 'oauth_gitlab', + 'oauth_google', + 'oauth_hubspot', + 'oauth_instagram', + 'oauth_line', + 'oauth_linear', + 'oauth_linkedin', + 'oauth_linkedin_oidc', + 'oauth_microsoft', + 'oauth_notion', + 'oauth_slack', + 'oauth_spotify', + 'oauth_tiktok', + 'oauth_twitch', + 'oauth_twitter', + 'oauth_x', + 'oauth_xero', + ]); + }); + + it('should returns correct custom strategies', async () => { + const { wrapper } = await createFixtures(f => { + f.withSocialProvider({ provider: 'custom_google' }); + f.withSocialProvider({ provider: 'custom_acme' }); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual(['oauth_custom_acme', 'oauth_custom_google']); + }); + + it('should returns correct default and custom strategies', async () => { + const { wrapper } = await createFixtures(f => { + f.withSocialProvider({ provider: 'apple' }); + f.withSocialProvider({ provider: 'facebook' }); + f.withSocialProvider({ provider: 'custom_google' }); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual(['oauth_apple', 'oauth_facebook', 'oauth_custom_google']); + }); + + it('should returns sorted default and custom strategies correctly', async () => { + const { wrapper } = await createFixtures(f => { + f.withSocialProvider({ provider: 'custom_google' }); + f.withSocialProvider({ provider: 'apple' }); + f.withSocialProvider({ provider: 'facebook' }); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual(['oauth_apple', 'oauth_facebook', 'oauth_custom_google']); + }); + + it('should returns sorted default and custom strategies correctly', async () => { + const { wrapper } = await createFixtures(f => { + f.withSocialProvider({ provider: 'custom_google' }); + f.withSocialProvider({ provider: 'custom_patreon' }); + f.withSocialProvider({ provider: 'apple' }); + f.withSocialProvider({ provider: 'facebook' }); + f.withSocialProvider({ provider: 'microsoft' }); + f.withSocialProvider({ provider: 'slack' }); + }); + const { result } = renderHook(() => useEnabledThirdPartyProviders(), { wrapper }); + + const { authenticatableOauthStrategies } = result.current; + + expect(authenticatableOauthStrategies).toStrictEqual([ + 'oauth_apple', + 'oauth_facebook', + 'oauth_custom_google', + 'oauth_microsoft', + 'oauth_custom_patreon', + 'oauth_slack', + ]); + }); +}); diff --git a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx index c8d90a20fc0..146fcffe97f 100644 --- a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx +++ b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx @@ -70,10 +70,22 @@ export const useEnabledThirdPartyProviders = () => { }; }); + const authenticatableOauthStrategies = [ + ...knownAuthenticatableSocialStrategies, + ...customAuthenticatableSocialStrategies, + ]; + + // Sort the authenticatableOauthStrategies by name + authenticatableOauthStrategies.sort((a, b) => { + const aName = a.replace(/^oauth_custom_|^oauth_/, ''); + const bName = b.replace(/^oauth_custom_|^oauth_/, ''); + return aName.localeCompare(bName); + }); + return { strategies: [...knownSocialProviderStrategies, ...web3FirstFactors, ...customSocialProviderStrategies], web3Strategies: [...web3FirstFactors], - authenticatableOauthStrategies: [...knownAuthenticatableSocialStrategies, ...customAuthenticatableSocialStrategies], + authenticatableOauthStrategies, strategyToDisplayData: strategyToDisplayData, providerToDisplayData: providerToDisplayData, }; From c63ced4ab417222663df5d74f79476b6f9b3996e Mon Sep 17 00:00:00 2001 From: nikospapcom Date: Wed, 10 Jul 2024 15:46:09 +0300 Subject: [PATCH 3/3] chore(clerk-js,types): Add element descriptor to ProviderInitialIcon and address design system issues --- .changeset/unlucky-hornets-roll.md | 2 +- .../clerk-js/src/ui/common/InitialIcon.tsx | 34 --------------- .../src/ui/common/ProviderInitialIcon.tsx | 42 +++++++++++++++++++ packages/clerk-js/src/ui/common/index.ts | 2 +- .../UserProfile/ConnectedAccountsMenu.tsx | 9 +++- .../UserProfile/ConnectedAccountsSection.tsx | 7 +++- .../ui/customizables/elementDescriptors.ts | 1 + .../src/ui/elements/SocialButtons.tsx | 9 +++- .../hooks/useEnabledThirdPartyProviders.tsx | 5 +-- packages/types/src/appearance.ts | 1 + 10 files changed, 67 insertions(+), 45 deletions(-) delete mode 100644 packages/clerk-js/src/ui/common/InitialIcon.tsx create mode 100644 packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx diff --git a/.changeset/unlucky-hornets-roll.md b/.changeset/unlucky-hornets-roll.md index c5c32ab93d6..be1562f4d31 100644 --- a/.changeset/unlucky-hornets-roll.md +++ b/.changeset/unlucky-hornets-roll.md @@ -11,4 +11,4 @@ Added support for Custom OAuth providers `export type OAuthStrategy = `oauth_${OAuthProvider}` | CustomOAuthStrategy;` - Added the `CustomOauthProvider` type with value `custom_${string}` and extended `OAuthProvider` type to include `CustomOauthProvider` - Added support for displaying provider initials when `logo_url` is null for custom OAuth providers -- Created new `InitialIcon` component in order to display custom oauth provider initials if provider `logo_url` is null \ No newline at end of file +- Created new `ProviderInitialIcon` internal component in order to display custom oauth provider initials if provider `logo_url` is null \ No newline at end of file diff --git a/packages/clerk-js/src/ui/common/InitialIcon.tsx b/packages/clerk-js/src/ui/common/InitialIcon.tsx deleted file mode 100644 index 8e87a602dd2..00000000000 --- a/packages/clerk-js/src/ui/common/InitialIcon.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Box, Text } from '../customizables'; -import { common } from '../styledSystem'; - -type InitialIconProps = { - initials: string; -}; - -export const InitialIcon = ({ initials }: InitialIconProps) => { - return ( - ({ - ...common.centeredFlex('inline-flex'), - width: t.space.$4, - height: t.space.$4, - borderRadius: t.radii.$sm, - color: t.colors.$colorTextOnPrimaryBackground, - backgroundColor: t.colors.$primary500, - })} - > - ({ - ...common.centeredFlex('inline-flex'), - width: '100%', - fontSize: t.fontSizes.$sm, - fontWeight: t.fontWeights.$semibold, - })} - > - {initials} - - - ); -}; diff --git a/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx b/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx new file mode 100644 index 00000000000..5976741eacb --- /dev/null +++ b/packages/clerk-js/src/ui/common/ProviderInitialIcon.tsx @@ -0,0 +1,42 @@ +import type { OAuthProvider, Web3Provider } from '@clerk/types'; + +import { Box, descriptors, Text } from '../customizables'; +import type { PropsOfComponent } from '../styledSystem'; +import { common } from '../styledSystem'; + +type ProviderInitialIconProps = PropsOfComponent & { + value: string; + id: Web3Provider | OAuthProvider; +}; + +export const ProviderInitialIcon = (props: ProviderInitialIconProps) => { + const { value, id, ...rest } = props; + + return ( + ({ + ...common.centeredFlex('inline-flex'), + width: t.space.$4, + height: t.space.$4, + borderRadius: t.radii.$sm, + color: t.colors.$colorTextOnPrimaryBackground, + backgroundColor: t.colors.$primary500, + })} + {...rest} + > + + {value[0]} + + + ); +}; diff --git a/packages/clerk-js/src/ui/common/index.ts b/packages/clerk-js/src/ui/common/index.ts index 3903c53dfdc..13a2268f6b2 100644 --- a/packages/clerk-js/src/ui/common/index.ts +++ b/packages/clerk-js/src/ui/common/index.ts @@ -4,7 +4,7 @@ export * from './CalloutWithAction'; export * from './forms'; export * from './Gate'; export * from './InfiniteListSpinner'; -export * from './InitialIcon'; +export * from './ProviderInitialIcon'; export * from './redirects'; export * from './verification'; export * from './withRedirect'; diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx index 21b56814aa3..f49eab628b0 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx @@ -2,7 +2,7 @@ import { useUser } from '@clerk/shared/react'; import type { OAuthProvider, OAuthStrategy } from '@clerk/types'; import { appendModalState } from '../../../utils'; -import { InitialIcon } from '../../common'; +import { ProviderInitialIcon } from '../../common'; import { useUserProfileContext } from '../../contexts'; import { descriptors, Image, localizationKeys } from '../../customizables'; import { ProfileSection, useCardState } from '../../elements'; @@ -74,7 +74,12 @@ export const AddConnectedAccount = () => { sx={theme => ({ width: theme.sizes.$4 })} /> ) : ( - + ); return ( diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx index 3735024f09c..0e10ec4b36e 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx @@ -2,7 +2,7 @@ import { useUser } from '@clerk/shared/react'; import type { ExternalAccountResource, OAuthProvider, OAuthScope, OAuthStrategy } from '@clerk/types'; import { appendModalState } from '../../../utils'; -import { InitialIcon } from '../../common'; +import { ProviderInitialIcon } from '../../common'; import { useUserProfileContext } from '../../contexts'; import { Badge, Box, descriptors, Flex, Image, localizationKeys, Text } from '../../customizables'; import { Card, ProfileSection, ThreeDotsMenu, useCardState, withCardStateProvider } from '../../elements'; @@ -70,7 +70,10 @@ export const ConnectedAccountsSection = withCardStateProvider(() => { sx={theme => ({ width: theme.sizes.$4, flexShrink: 0 })} /> ) : ( - + ); return ( diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index cd979b3d740..9a0bb68e5b6 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -51,6 +51,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'socialButtonsBlockButton', 'socialButtonsBlockButtonText', 'socialButtonsProviderIcon', + 'socialButtonsProviderInitialIcon', 'enterpriseButtonsProviderIcon', 'alternativeMethods', diff --git a/packages/clerk-js/src/ui/elements/SocialButtons.tsx b/packages/clerk-js/src/ui/elements/SocialButtons.tsx index 626b30c0e2a..1323b8fc39f 100644 --- a/packages/clerk-js/src/ui/elements/SocialButtons.tsx +++ b/packages/clerk-js/src/ui/elements/SocialButtons.tsx @@ -2,7 +2,7 @@ import type { OAuthProvider, OAuthStrategy, Web3Provider, Web3Strategy } from '@ import type { Ref } from 'react'; import React, { forwardRef, isValidElement } from 'react'; -import { InitialIcon } from '../common'; +import { ProviderInitialIcon } from '../common'; import type { LocalizationKey } from '../customizables'; import { Button, @@ -138,7 +138,12 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { sx={theme => ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} /> ) : ( - + ); return ( diff --git a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx index 146fcffe97f..c07c2674e8c 100644 --- a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx +++ b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx @@ -51,9 +51,8 @@ export const useEnabledThirdPartyProviders = () => { s => !oauthStrategies.includes(s) && s.startsWith('oauth_custom_'), ); - customSocialProviderStrategies.map(s => { + customSocialProviderStrategies.forEach(s => { const providerName = s.replace('oauth_', '') as OAuthProvider; - providerToDisplayData[providerName] = { strategy: s, name: social[s].name, @@ -61,7 +60,7 @@ export const useEnabledThirdPartyProviders = () => { }; }); - customAuthenticatableSocialStrategies.map(s => { + customAuthenticatableSocialStrategies.forEach(s => { const providerId = s.replace('oauth_', '') as OAuthProvider; strategyToDisplayData[s] = { id: providerId, diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index c479de540ef..2276dea23b6 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -169,6 +169,7 @@ export type ElementsConfig = { socialButtonsBlockButton: WithOptions; socialButtonsBlockButtonText: WithOptions; socialButtonsProviderIcon: WithOptions; + socialButtonsProviderInitialIcon: WithOptions; enterpriseButtonsProviderIcon: WithOptions;