diff --git a/.changeset/smooth-pets-rhyme.md b/.changeset/smooth-pets-rhyme.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/smooth-pets-rhyme.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.github/labeler.yml b/.github/labeler.yml index 67c9d5241d4..c099aaf634a 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -47,8 +47,3 @@ actions: integration: - integration/** - -# TODO @retheme: Remove -retheme: - - packages/clerk-js/src/ui.retheme/** - - packages/**/*.retheme.* diff --git a/.github/workflows/ui-retheme-build.yml b/.github/workflows/ui-retheme-build.yml deleted file mode 100644 index 4d234eab220..00000000000 --- a/.github/workflows/ui-retheme-build.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Build Retheme packages -run-name: Build Retheme packages - -on: - pull_request: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.actor }} - cancel-in-progress: true - -jobs: - build-retheme: - runs-on: ${{ vars.RUNNER_NORMAL }} - timeout-minutes: ${{ fromJSON(vars.TIMEOUT_MINUTES_NORMAL) }} - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup - id: config - uses: ./.github/actions/init - with: - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} - registry-url: "https://registry.npmjs.org" - - - name: Build packages - run: | - CLERK_RETHEME=1 npx turbo build --force diff --git a/.github/workflows/ui-retheme-changes-reminder.yml b/.github/workflows/ui-retheme-changes-reminder.yml deleted file mode 100644 index c78d12f76ec..00000000000 --- a/.github/workflows/ui-retheme-changes-reminder.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: UI Retheme Changes Reminder - -on: - pull_request: - branches: - - main - paths: - - 'packages/clerk-js/src/ui/**' - # files with matching `*.retheme.ts` retheme variant - - 'packages/localizations/src/en-US.ts' - - 'packages/localizations/src/index.ts' - - 'packages/types/src/appearance.ts' - - 'packages/types/src/clerk.ts' - - 'packages/types/src/index.ts' - - 'packages/types/src/localization.ts' -jobs: - check-changes: - runs-on: ubuntu-latest - steps: - - name: Check if comment already made - id: check_comment - uses: actions/github-script@v7 - with: - script: | - const issue_number = context.issue.number; - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number - }); - return comments.data.some(comment => comment.body.includes('ui-retheme-reminder')); - - - name: Post a comment if changes detected and no comment exists - if: steps.check_comment.outputs.result != 'true' - uses: peter-evans/create-or-update-comment@v3.0.0 - with: - issue-number: ${{ github.event.pull_request.number }} - body: | - - # ⚠️ Changes detected under the ClerkJS `ui` directory! - **Don't forget to apply the same changes under the `/ui.retheme` directory:** - `packages/clerk-js/src/ui/**` ➡️ `packages/clerk-js/src/ui.retheme/**` - - Also, you may need to update the following files: - - `packages/localizations/src/en-US.retheme.ts` - - `packages/localizations/src/index.retheme.ts` - - `packages/types/src/appearance.retheme.ts` - - `packages/types/src/clerk.retheme.ts` - - `packages/types/src/index.retheme.ts` - - `packages/types/src/localization.retheme.ts` diff --git a/packages/clerk-js/jest.config.js b/packages/clerk-js/jest.config.js index f2ab60ef488..146b7b13bd8 100644 --- a/packages/clerk-js/jest.config.js +++ b/packages/clerk-js/jest.config.js @@ -1,7 +1,5 @@ const { name } = require('./package.json'); -const uiRetheme = process.env.CLERK_RETHEME === '1' || process.env.CLERK_RETHEME === 'true'; - /** @type {import('ts-jest').JestConfigWithTsJest} */ const config = { displayName: name.replace('@clerk', ''), @@ -16,7 +14,7 @@ const config = { '/ui/.*/__tests__/.*.test.[jt]sx?$', '/(core|utils)/.*.test.[jt]sx?$', ], - testPathIgnorePatterns: ['/node_modules/', uiRetheme ? '/src/ui/' : '/src/ui.retheme/'], + testPathIgnorePatterns: ['/node_modules/'], collectCoverage: false, coverageProvider: 'v8', coverageDirectory: 'coverage', diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 1c3256db6c3..2bdef3e036c 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -33,7 +33,7 @@ "build": "npm run build:bundle && npm run build:declarations", "build:analyze": "webpack-bundle-analyzer stats.json dist/", "build:bundle": "npm run clean && webpack --config webpack.config.js --env production", - "build:declarations": "if [ \"$CLERK_RETHEME\" = \"true\" ] || [ \"$CLERK_RETHEME\" = \"1\" ]; then tsc -p tsconfig.declarations.retheme.json; else tsc -p tsconfig.declarations.json; fi", + "build:declarations": "tsc -p tsconfig.declarations.json", "build:stats": "webpack --config webpack.config.js --env production --json=stats.json --env variant=\"clerk.browser\"", "bundlewatch": "npx bundlewatch --config bundlewatch.config.json", "clean": "rimraf ./dist", diff --git a/packages/clerk-js/src/ui.retheme/Components.tsx b/packages/clerk-js/src/ui.retheme/Components.tsx deleted file mode 100644 index e88dd1dfab1..00000000000 --- a/packages/clerk-js/src/ui.retheme/Components.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import { createDeferredPromise } from '@clerk/shared'; -import { useSafeLayoutEffect } from '@clerk/shared/react'; -import type { - Appearance, - Clerk, - ClerkOptions, - CreateOrganizationProps, - EnvironmentResource, - OrganizationProfileProps, - SignInProps, - SignUpProps, - UserProfileProps, -} from '@clerk/types'; -import React, { Suspense } from 'react'; - -import { clerkUIErrorDOMElementNotFound } from '../core/errors'; -import { buildVirtualRouterUrl } from '../utils'; -import type { AppearanceCascade } from './customizables/parseAppearance'; -// NOTE: Using `./hooks` instead of `./hooks/useClerkModalStateParams` will increase the bundle size -import { useClerkModalStateParams } from './hooks/useClerkModalStateParams'; -import type { ClerkComponentName } from './lazyModules/components'; -import { - CreateOrganizationModal, - ImpersonationFab, - OrganizationProfileModal, - preloadComponent, - SignInModal, - SignUpModal, - UserProfileModal, -} from './lazyModules/components'; -import { - LazyComponentRenderer, - LazyImpersonationFabProvider, - LazyModalRenderer, - LazyProviders, -} from './lazyModules/providers'; -import type { AvailableComponentProps } from './types'; - -const ROOT_ELEMENT_ID = 'clerk-components'; - -export type ComponentControls = { - mountComponent: (params: { - appearanceKey: Uncapitalize; - name: ClerkComponentName; - node: HTMLDivElement; - props?: AvailableComponentProps; - }) => void; - unmountComponent: (params: { node: HTMLDivElement }) => void; - updateProps: (params: { - appearance?: Appearance | undefined; - options?: ClerkOptions | undefined; - node?: HTMLDivElement; - props?: unknown; - }) => void; - openModal: ( - modal: T, - props: T extends 'signIn' ? SignInProps : T extends 'signUp' ? SignUpProps : UserProfileProps, - ) => void; - closeModal: (modal: 'signIn' | 'signUp' | 'userProfile' | 'organizationProfile' | 'createOrganization') => void; - // Special case, as the impersonation fab mounts automatically - mountImpersonationFab: () => void; -}; - -interface HtmlNodeOptions { - key: string; - name: ClerkComponentName; - appearanceKey: Uncapitalize; - props?: AvailableComponentProps; -} - -interface ComponentsProps { - clerk: Clerk; - environment: EnvironmentResource; - options: ClerkOptions; - onComponentsMounted: () => void; -} - -interface ComponentsState { - appearance: Appearance | undefined; - options: ClerkOptions | undefined; - signInModal: null | SignInProps; - signUpModal: null | SignUpProps; - userProfileModal: null | UserProfileProps; - organizationProfileModal: null | OrganizationProfileProps; - createOrganizationModal: null | CreateOrganizationProps; - nodes: Map; - impersonationFab: boolean; -} - -let portalCt = 0; - -function assertDOMElement(element: HTMLElement): asserts element { - if (!element) { - clerkUIErrorDOMElementNotFound(); - } -} - -export const mountComponentRenderer = (clerk: Clerk, environment: EnvironmentResource, options: ClerkOptions) => { - // TODO @ui-retheme: remove - console.log('%c You are using the retheme components ', 'background: blue; color: white;font-size:2rem;'); - - // TODO: Init of components should start - // before /env and /client requests - let clerkRoot = document.getElementById(ROOT_ELEMENT_ID); - - if (!clerkRoot) { - clerkRoot = document.createElement('div'); - clerkRoot.setAttribute('id', 'clerk-components'); - document.body.appendChild(clerkRoot); - } - - let componentsControlsResolver: Promise | undefined; - - return { - ensureMounted: async (opts?: { preloadHint: ClerkComponentName }) => { - const { preloadHint } = opts || {}; - // This mechanism ensures that mountComponentControls will only be called once - // and any calls to .mount before mountComponentControls resolves will fire in order. - // Otherwise, we risk having components rendered multiple times, or having - // .unmountComponent incorrectly called before the component is rendered - if (!componentsControlsResolver) { - const deferredPromise = createDeferredPromise(); - if (preloadHint) { - void preloadComponent(preloadHint); - } - componentsControlsResolver = import('./lazyModules/common').then(({ createRoot }) => { - createRoot(clerkRoot!).render( - , - ); - return deferredPromise.promise.then(() => componentsControls); - }); - } - return componentsControlsResolver.then(controls => controls); - }, - }; -}; - -export type MountComponentRenderer = typeof mountComponentRenderer; - -const componentsControls = {} as ComponentControls; - -const componentNodes = Object.freeze({ - SignUp: 'signUpModal', - SignIn: 'signInModal', - UserProfile: 'userProfileModal', - OrganizationProfile: 'organizationProfileModal', - CreateOrganization: 'createOrganizationModal', -}) as any; - -const Components = (props: ComponentsProps) => { - const [state, setState] = React.useState({ - appearance: props.options.appearance, - options: props.options, - signInModal: null, - signUpModal: null, - userProfileModal: null, - organizationProfileModal: null, - createOrganizationModal: null, - nodes: new Map(), - impersonationFab: false, - }); - const { signInModal, signUpModal, userProfileModal, organizationProfileModal, createOrganizationModal, nodes } = - state; - - const { urlStateParam, clearUrlStateParam, decodedRedirectParams } = useClerkModalStateParams(); - - useSafeLayoutEffect(() => { - if (decodedRedirectParams) { - setState(s => ({ - ...s, - [componentNodes[decodedRedirectParams.componentName]]: true, - })); - } - - componentsControls.mountComponent = params => { - const { node, name, props, appearanceKey } = params; - - assertDOMElement(node); - setState(s => { - s.nodes.set(node, { key: `p${++portalCt}`, name, props, appearanceKey }); - return { ...s, nodes }; - }); - }; - - componentsControls.unmountComponent = params => { - const { node } = params; - setState(s => { - s.nodes.delete(node); - return { ...s, nodes }; - }); - }; - - componentsControls.updateProps = ({ node, props, ...restProps }) => { - if (node && props && typeof props === 'object') { - const nodeOptions = state.nodes.get(node); - if (nodeOptions) { - nodeOptions.props = { ...props }; - setState(s => ({ ...s })); - return; - } - } - setState(s => ({ ...s, ...restProps })); - }; - - componentsControls.closeModal = name => { - clearUrlStateParam(); - setState(s => ({ ...s, [name + 'Modal']: null })); - }; - - componentsControls.openModal = (name, props) => { - setState(s => ({ ...s, [name + 'Modal']: props })); - }; - - componentsControls.mountImpersonationFab = () => { - setState(s => ({ ...s, impersonationFab: true })); - }; - - props.onComponentsMounted(); - }, []); - - const mountedSignInModal = ( - componentsControls.closeModal('signIn')} - onExternalNavigate={() => componentsControls.closeModal('signIn')} - startPath={buildVirtualRouterUrl({ base: '/sign-in', path: urlStateParam?.path })} - componentName={'SignInModal'} - > - - - - ); - - const mountedSignUpModal = ( - componentsControls.closeModal('signUp')} - onExternalNavigate={() => componentsControls.closeModal('signUp')} - startPath={buildVirtualRouterUrl({ base: '/sign-up', path: urlStateParam?.path })} - componentName={'SignUpModal'} - > - - - - ); - - const mountedUserProfileModal = ( - componentsControls.closeModal('userProfile')} - onExternalNavigate={() => componentsControls.closeModal('userProfile')} - startPath={buildVirtualRouterUrl({ base: '/user', path: urlStateParam?.path })} - componentName={'SignUpModal'} - modalContainerSx={{ alignItems: 'center' }} - modalContentSx={t => ({ height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, margin: 0 })} - > - - - ); - - const mountedOrganizationProfileModal = ( - componentsControls.closeModal('organizationProfile')} - onExternalNavigate={() => componentsControls.closeModal('organizationProfile')} - startPath={buildVirtualRouterUrl({ base: '/organizationProfile', path: urlStateParam?.path })} - componentName={'OrganizationProfileModal'} - modalContainerSx={{ alignItems: 'center' }} - modalContentSx={t => ({ height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, margin: 0 })} - > - - - ); - - const mountedCreateOrganizationModal = ( - componentsControls.closeModal('createOrganization')} - onExternalNavigate={() => componentsControls.closeModal('createOrganization')} - startPath={buildVirtualRouterUrl({ base: '/createOrganization', path: urlStateParam?.path })} - componentName={'CreateOrganizationModal'} - modalContainerSx={{ alignItems: 'center' }} - modalContentSx={t => ({ height: `min(${t.sizes.$120}, calc(100% - ${t.sizes.$12}))`, margin: 0 })} - > - - - ); - - return ( - - - {[...nodes].map(([node, component]) => { - return ( - - ); - })} - - {signInModal && mountedSignInModal} - {signUpModal && mountedSignUpModal} - {userProfileModal && mountedUserProfileModal} - {organizationProfileModal && mountedOrganizationProfileModal} - {createOrganizationModal && mountedCreateOrganizationModal} - {state.impersonationFab && ( - - - - )} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/BlockButtons.tsx b/packages/clerk-js/src/ui.retheme/common/BlockButtons.tsx deleted file mode 100644 index 615c041cb62..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/BlockButtons.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { descriptors, Icon } from '../customizables'; -import { ArrowBlockButton } from '../elements'; -import { Plus } from '../icons'; -import type { PropsOfComponent } from '../styledSystem'; - -type BlockButtonProps = PropsOfComponent; - -export const BlockButton = (props: BlockButtonProps) => { - const { id, ...rest } = props; - return ( - - ); -}; - -export const AddBlockButton = (props: BlockButtonProps) => { - const { leftIcon, ...rest } = props; - return ( - ({ justifyContent: 'flex-start', gap: theme.space.$2 })} - leftIcon={ - ({ - width: theme.sizes.$2x5, - height: theme.sizes.$2x5, - })} - /> - } - > - {props.children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/CalloutWithAction.tsx b/packages/clerk-js/src/ui.retheme/common/CalloutWithAction.tsx deleted file mode 100644 index a10622dc91b..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/CalloutWithAction.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { ComponentType, MouseEvent, PropsWithChildren } from 'react'; - -import { Col, Flex, Icon, Link, Text } from '../customizables'; -import type { LocalizationKey } from '../localization'; -import type { ThemableCssProp } from '../styledSystem'; - -type CalloutWithActionProps = { - text?: LocalizationKey | string; - textSx?: ThemableCssProp; - actionLabel?: LocalizationKey; - onClick?: (e: MouseEvent) => Promise; - icon: ComponentType; -}; -export const CalloutWithAction = (props: PropsWithChildren) => { - const { icon, text, textSx, actionLabel, onClick: onClickProp } = props; - - const onClick = (e: MouseEvent) => { - void onClickProp?.(e); - }; - - return ( - ({ - background: theme.colors.$blackAlpha50, - padding: `${theme.space.$2x5} ${theme.space.$4}`, - justifyContent: 'space-between', - alignItems: 'flex-start', - borderRadius: theme.radii.$md, - })} - > - - ({ marginTop: t.space.$1 })} - /> - - - {props.children} - - - {actionLabel && ( - - )} - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/CustomPageContentContainer.tsx b/packages/clerk-js/src/ui.retheme/common/CustomPageContentContainer.tsx deleted file mode 100644 index e134a08f89e..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/CustomPageContentContainer.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Col, descriptors } from '../customizables'; -import { CardAlert, NavbarMenuButtonRow, useCardState, withCardStateProvider } from '../elements'; -import type { CustomPageContent } from '../utils'; -import { ExternalElementMounter } from '../utils'; - -export const CustomPageContentContainer = withCardStateProvider( - ({ mount, unmount }: Omit) => { - const card = useCardState(); - return ( - - {card.error} - - - - - - ); - }, -); diff --git a/packages/clerk-js/src/ui.retheme/common/EmailLinkCompleteFlowCard.tsx b/packages/clerk-js/src/ui.retheme/common/EmailLinkCompleteFlowCard.tsx deleted file mode 100644 index af37d800d85..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/EmailLinkCompleteFlowCard.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { withCardStateProvider } from '../elements'; -import { localizationKeys } from '../localization'; -import type { EmailLinkVerifyProps } from './EmailLinkVerify'; -import { EmailLinkVerify } from './EmailLinkVerify'; - -const signInLocalizationKeys = { - verified: { - title: localizationKeys('signIn.emailLink.verified.title'), - subtitle: localizationKeys('signIn.emailLink.verified.subtitle'), - }, - verified_switch_tab: { - title: localizationKeys('signIn.emailLink.verified.title'), - subtitle: localizationKeys('signIn.emailLink.verifiedSwitchTab.subtitle'), - }, - loading: { - title: localizationKeys('signIn.emailLink.loading.title'), - subtitle: localizationKeys('signIn.emailLink.loading.subtitle'), - }, - failed: { - title: localizationKeys('signIn.emailLink.failed.title'), - subtitle: localizationKeys('signIn.emailLink.failed.subtitle'), - }, - expired: { - title: localizationKeys('signIn.emailLink.expired.title'), - subtitle: localizationKeys('signIn.emailLink.expired.subtitle'), - }, -}; - -const signUpLocalizationKeys = { - ...signInLocalizationKeys, - verified: { - ...signInLocalizationKeys.verified, - title: localizationKeys('signUp.emailLink.verified.title'), - }, - verified_switch_tab: { - ...signInLocalizationKeys.verified_switch_tab, - title: localizationKeys('signUp.emailLink.verified.title'), - }, - loading: { - ...signInLocalizationKeys.loading, - title: localizationKeys('signUp.emailLink.loading.title'), - }, -}; - -export const SignInEmailLinkFlowComplete = withCardStateProvider((props: Omit) => { - return ( - - ); -}); - -export const SignUpEmailLinkFlowComplete = withCardStateProvider((props: Omit) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/EmailLinkStatusCard.tsx b/packages/clerk-js/src/ui.retheme/common/EmailLinkStatusCard.tsx deleted file mode 100644 index 28448344ecf..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/EmailLinkStatusCard.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; - -import type { VerificationStatus } from '../../utils/getClerkQueryParam'; -import type { LocalizationKey } from '../customizables'; -import { Col, descriptors, Flex, Flow, Icon, localizationKeys, Spinner, Text } from '../customizables'; -import { Card, CardAlert, Header } from '../elements'; -import { useCardState } from '../elements/contexts'; -import { ExclamationTriangle, SwitchArrows, TickShield } from '../icons'; -import type { InternalTheme } from '../styledSystem'; -import { animations } from '../styledSystem'; - -type EmailLinkStatusCardProps = React.PropsWithChildren<{ - title: LocalizationKey; - subtitle: LocalizationKey; - status: VerificationStatus; -}>; - -const StatusToIcon: Record, React.ComponentType> = { - verified: TickShield, - verified_switch_tab: SwitchArrows, - expired: ExclamationTriangle, - failed: ExclamationTriangle, -}; - -const statusToColor = (theme: InternalTheme, status: Exclude) => - ({ - verified: theme.colors.$success500, - verified_switch_tab: theme.colors.$primary500, - expired: theme.colors.$warning500, - failed: theme.colors.$danger500, - }[status]); - -export const EmailLinkStatusCard = (props: EmailLinkStatusCardProps) => { - const card = useCardState(); - return ( - - - {card.error} - - - - - - - - - - ); -}; - -const StatusRow = (props: { status: VerificationStatus }) => { - return ( - - {props.status === 'loading' ? ( - ({ margin: `${theme.space.$12} 0` })} - elementDescriptor={descriptors.spinner} - /> - ) : ( - <> - - - - )} - - ); -}; - -const StatusIcon = (props: { status: Exclude }) => { - const { status } = props; - - return ( - ({ - width: theme.sizes.$24, - height: theme.sizes.$24, - borderRadius: theme.radii.$circle, - backgroundColor: theme.colors.$blackAlpha100, - color: statusToColor(theme, status), - animation: `${animations.dropdownSlideInScaleAndFade} 500ms ease`, - })} - > - ({ height: theme.sizes.$6, width: theme.sizes.$5 })} - /> - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/EmailLinkVerify.tsx b/packages/clerk-js/src/ui.retheme/common/EmailLinkVerify.tsx deleted file mode 100644 index 5aec89f70ba..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/EmailLinkVerify.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { EmailLinkErrorCode, isEmailLinkError } from '@clerk/shared/error'; -import { useClerk } from '@clerk/shared/react'; -import React from 'react'; - -import type { VerificationStatus } from '../../utils'; -import { completeSignUpFlow } from '../../utils'; -import { useCoreSignUp } from '../contexts'; -import type { LocalizationKey } from '../localization'; -import { useRouter } from '../router'; -import { sleep } from '../utils'; -import { EmailLinkStatusCard } from './EmailLinkStatusCard'; - -export type EmailLinkVerifyProps = { - redirectUrlComplete?: string; - redirectUrl?: string; - verifyEmailPath?: string; - verifyPhonePath?: string; - texts: Record; -}; - -export const EmailLinkVerify = (props: EmailLinkVerifyProps) => { - const { redirectUrl, redirectUrlComplete, verifyEmailPath, verifyPhonePath } = props; - const { handleEmailLinkVerification } = useClerk(); - const { navigate } = useRouter(); - const signUp = useCoreSignUp(); - const [verificationStatus, setVerificationStatus] = React.useState('loading'); - - const startVerification = async () => { - try { - // Avoid loading flickering - await sleep(750); - await handleEmailLinkVerification({ redirectUrlComplete, redirectUrl }, navigate); - setVerificationStatus('verified_switch_tab'); - await sleep(750); - return completeSignUpFlow({ - signUp, - verifyEmailPath, - verifyPhonePath, - navigate, - }); - } catch (err) { - let status: VerificationStatus = 'failed'; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = 'expired'; - } - setVerificationStatus(status); - } - }; - - React.useEffect(() => { - void startVerification(); - }, []); - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/Gate.tsx b/packages/clerk-js/src/ui.retheme/common/Gate.tsx deleted file mode 100644 index 9aefb2801d1..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/Gate.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useSession } from '@clerk/shared/react'; -import type { CheckAuthorization, MembershipRole, OrganizationPermissionKey } from '@clerk/types'; -import type { ComponentType, PropsWithChildren, ReactNode } from 'react'; -import React, { useEffect } from 'react'; - -import { useRouter } from '../router'; - -type GateParams = Parameters[0] | ((has: CheckAuthorization) => boolean); -type GateProps = PropsWithChildren< - ( - | { - condition?: never; - role: MembershipRole; - permission?: never; - } - | { - condition?: never; - role?: never; - permission: OrganizationPermissionKey; - } - | { - condition: (has: CheckAuthorization) => boolean; - role?: never; - permission?: never; - } - ) & { - fallback?: ReactNode; - redirectTo?: string; - } ->; - -export const useGate = (params: GateParams) => { - const { session } = useSession(); - - if (!session?.id) { - return { isAuthorizedUser: false }; - } - - /** - * if a function is passed and returns false then throw not found - */ - if (typeof params === 'function') { - if (params(session.checkAuthorization)) { - return { isAuthorizedUser: true }; - } - return { isAuthorizedUser: false }; - } - - return { - isAuthorizedUser: session?.checkAuthorization(params), - }; -}; - -export const Gate = (gateProps: GateProps) => { - const { children, fallback, redirectTo, ...restAuthorizedParams } = gateProps; - - const { isAuthorizedUser } = useGate( - typeof restAuthorizedParams.condition === 'function' ? restAuthorizedParams.condition : restAuthorizedParams, - ); - - const { navigate } = useRouter(); - - useEffect(() => { - // wait for promise to resolve - if (typeof isAuthorizedUser === 'boolean' && !isAuthorizedUser && redirectTo) { - void navigate(redirectTo); - } - }, [isAuthorizedUser, redirectTo]); - - // wait for promise to resolve - if (typeof isAuthorizedUser === 'boolean' && !isAuthorizedUser && fallback) { - return <>{fallback}; - } - - if (isAuthorizedUser) { - return <>{children}; - } - - return null; -}; - -export function withGate

(Component: ComponentType

, gateProps: GateProps): React.ComponentType

{ - const displayName = Component.displayName || Component.name || 'Component'; - const HOC = (props: P) => { - return ( - - - - ); - }; - - HOC.displayName = `withGate(${displayName})`; - - return HOC; -} diff --git a/packages/clerk-js/src/ui.retheme/common/InfiniteListSpinner.tsx b/packages/clerk-js/src/ui.retheme/common/InfiniteListSpinner.tsx deleted file mode 100644 index 97ae637e73f..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/InfiniteListSpinner.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { forwardRef } from 'react'; - -import { Box, descriptors, Spinner } from '../customizables'; - -export const InfiniteListSpinner = forwardRef((_, ref) => { - return ( - ({ - width: '100%', - height: t.space.$12, - position: 'relative', - })} - > - - - - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/NotificationCountBadge.tsx b/packages/clerk-js/src/ui.retheme/common/NotificationCountBadge.tsx deleted file mode 100644 index a3b609a0554..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/NotificationCountBadge.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Box, NotificationBadge } from '../customizables'; -import { useDelayedVisibility, usePrefersReducedMotion } from '../hooks'; -import type { ThemableCssProp } from '../styledSystem'; -import { animations } from '../styledSystem'; - -type NotificationCountBadgeProps = { - notificationCount: number; - containerSx?: ThemableCssProp; -}; - -export const NotificationCountBadge = ({ notificationCount, containerSx }: NotificationCountBadgeProps) => { - const prefersReducedMotion = usePrefersReducedMotion(); - const showNotification = useDelayedVisibility(notificationCount > 0, 350) || false; - - const enterExitAnimation: ThemableCssProp = t => ({ - animation: prefersReducedMotion - ? 'none' - : `${notificationCount ? animations.notificationAnimation : animations.outAnimation} ${ - t.transitionDuration.$textField - } ${t.transitionTiming.$slowBezier} 0s 1 normal forwards`, - }); - - return ( - ({ - position: 'absolute', - top: `-${t.space.$2}`, - right: `-${t.space.$1}`, - width: t.sizes.$2, - height: t.sizes.$2, - }), - containerSx, - ]} - > - {showNotification && {notificationCount}} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/PrintableComponent.tsx b/packages/clerk-js/src/ui.retheme/common/PrintableComponent.tsx deleted file mode 100644 index cc8154e234c..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/PrintableComponent.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; - -type OnPrintCallback = () => void; -type UsePrintableReturn = { - print: () => void; - printableProps: { onPrint: (cb: OnPrintCallback) => void }; -}; - -export const usePrintable = (): UsePrintableReturn => { - const callbacks: OnPrintCallback[] = []; - const onPrint = (cb: OnPrintCallback) => callbacks.push(cb); - const print = () => callbacks.forEach(cb => cb()); - return { print, printableProps: { onPrint } }; -}; - -export const PrintableComponent = (props: UsePrintableReturn['printableProps'] & React.PropsWithChildren) => { - const { children, onPrint } = props; - const ref = React.useRef(null); - - onPrint(() => { - printContentsOfElementViaIFrame(ref); - }); - - return ( -

- {children} -
- ); -}; - -const copyStyles = (iframe: HTMLIFrameElement, selector = '[data-emotion=cl-internal]') => { - if (!iframe.contentDocument) { - return; - } - const allStyleText = [...document.head.querySelectorAll(selector)].map(a => a.innerHTML).join('\n'); - const styleEl = iframe.contentDocument.createElement('style'); - styleEl.innerHTML = allStyleText; - iframe.contentDocument.head.prepend(styleEl); -}; - -const setPrintingStyles = (iframe: HTMLIFrameElement) => { - if (!iframe.contentDocument) { - return; - } - // A web-safe font that's universally supported - iframe.contentDocument.body.style.fontFamily = 'Arial'; - // Make the printing dialog display the background colors by default - iframe.contentDocument.body.style.cssText = `* {\n-webkit-print-color-adjust: exact !important;\ncolor-adjust: exact !important;\nprint-color-adjust: exact !important;\n}`; -}; - -const printContentsOfElementViaIFrame = (elementRef: React.MutableRefObject) => { - const content = elementRef.current; - if (!content) { - return; - } - - const frame = document.createElement('iframe'); - frame.style.position = 'fixed'; - frame.style.right = '-2000px'; - frame.style.bottom = '-2000px'; - // frame.style.width = '500px'; - // frame.style.height = '500px'; - // frame.style.border = '0px'; - - frame.onload = () => { - copyStyles(frame); - setPrintingStyles(frame); - if (frame.contentDocument && frame.contentWindow) { - frame.contentDocument.body.innerHTML = content.innerHTML; - frame.contentWindow.print(); - } - }; - - // TODO: Cleaning this iframe is not always possible because - // .print() will not block. Leaving this iframe inside the DOM - // shouldn't be an issue, but is there any reliable way to remove it? - window.document.body.appendChild(frame); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/QRCode.tsx b/packages/clerk-js/src/ui.retheme/common/QRCode.tsx deleted file mode 100644 index 89b1b3a3c60..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/QRCode.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { QRCodeSVG } from 'qrcode.react'; - -import { descriptors, Flex } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -type QRCodeProps = PropsOfComponent & { url: string; size?: number }; - -export const QRCode = (props: QRCodeProps) => { - const { size = 200, url, ...rest } = props; - return ( - - ({ backgroundColor: 'white', padding: t.space.$2x5 })} - > - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/RemoveResourcePage.tsx b/packages/clerk-js/src/ui.retheme/common/RemoveResourcePage.tsx deleted file mode 100644 index c95d9dfe864..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/RemoveResourcePage.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; - -import { Text } from '../customizables'; -import { ContentPage, Form, FormButtons, SuccessPage, useCardState, withCardStateProvider } from '../elements'; -import type { LocalizationKey } from '../localization'; -import { handleError } from '../utils'; -import { useWizard, Wizard } from './Wizard'; - -type RemovePageProps = { - title: LocalizationKey; - breadcrumbTitle?: LocalizationKey; - messageLine1: LocalizationKey; - messageLine2: LocalizationKey; - successMessage: LocalizationKey; - deleteResource: () => Promise; - Breadcrumbs: React.ComponentType | null; -}; - -export const RemoveResourcePage = withCardStateProvider((props: RemovePageProps) => { - const { title, messageLine1, messageLine2, breadcrumbTitle, successMessage, deleteResource } = props; - const wizard = useWizard(); - const card = useCardState(); - - const handleSubmit = async () => { - try { - await deleteResource().then(() => wizard.nextStep()); - } catch (e) { - handleError(e, [], card.setError); - } - }; - - return ( - - - - - - - - - - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/SSOCallback.tsx b/packages/clerk-js/src/ui.retheme/common/SSOCallback.tsx deleted file mode 100644 index dfa75aeaa1c..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/SSOCallback.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import type { HandleOAuthCallbackParams, HandleSamlCallbackParams } from '@clerk/types'; -import React from 'react'; - -import { Flow } from '../customizables'; -import { Card, CardAlert, LoadingCardContainer, useCardState, withCardStateProvider } from '../elements'; -import { useRouter } from '../router'; -import { handleError } from '../utils'; - -export const SSOCallback = withCardStateProvider(props => { - return ( - - - - ); -}); - -export const SSOCallbackCard = (props: HandleOAuthCallbackParams | HandleSamlCallbackParams) => { - const { handleRedirectCallback } = useClerk(); - const { navigate } = useRouter(); - const card = useCardState(); - - React.useEffect(() => { - let timeoutId: ReturnType; - handleRedirectCallback({ ...props }, navigate).catch(e => { - handleError(e, [], card.setError); - timeoutId = setTimeout(() => void navigate('../'), 4000); - }); - - return () => clearTimeout(timeoutId); - }, [handleError, handleRedirectCallback]); - - return ( - - - {card.error} - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/Wizard.tsx b/packages/clerk-js/src/ui.retheme/common/Wizard.tsx deleted file mode 100644 index c2c79c58053..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/Wizard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -type WizardProps = React.PropsWithChildren<{ - step: number; -}>; - -type UseWizardProps = { - defaultStep?: number; - onNextStep?: () => void; -}; - -export const useWizard = (params: UseWizardProps = {}) => { - const { defaultStep = 0, onNextStep } = params; - const [step, setStep] = React.useState(defaultStep); - - const nextStep = React.useCallback(() => { - onNextStep?.(); - setStep((s: number) => s + 1); - }, []); - - const prevStep = React.useCallback(() => setStep(s => s - 1), []); - const goToStep = React.useCallback((i: number) => setStep(i), []); - return { nextStep, prevStep, goToStep, props: { step } }; -}; - -export const Wizard = (props: WizardProps) => { - const { step, children } = props; - return <>{React.Children.toArray(children)[step]}; -}; diff --git a/packages/clerk-js/src/ui.retheme/common/__tests__/redirects.test.ts b/packages/clerk-js/src/ui.retheme/common/__tests__/redirects.test.ts deleted file mode 100644 index 31cff699efc..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/__tests__/redirects.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { buildEmailLinkRedirectUrl, buildSSOCallbackURL } from '../redirects'; - -describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => { - it('handles empty routing strategy based routing ', function () { - expect(buildEmailLinkRedirectUrl({ path: '', authQueryString: '' } as any, '')).toBe('http://localhost/#/verify'); - }); - - it('returns the magic link redirect url for components using path based routing ', function () { - expect(buildEmailLinkRedirectUrl({ routing: 'path', authQueryString: '' } as any, '')).toBe( - 'http://localhost/verify', - ); - - expect(buildEmailLinkRedirectUrl({ routing: 'path', path: '/sign-in', authQueryString: '' } as any, '')).toBe( - 'http://localhost/sign-in/verify', - ); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'path', - path: '', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - '', - ), - ).toBe('http://localhost/verify?redirectUrl=https://clerk.com'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'path', - path: '/sign-in', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - '', - ), - ).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'path', - path: '/sign-in', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - 'https://accounts.clerk.com/sign-in', - ), - ).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com'); - }); - - it('returns the magic link redirect url for components using hash based routing ', function () { - expect( - buildEmailLinkRedirectUrl( - { - routing: 'hash', - authQueryString: '', - } as any, - '', - ), - ).toBe('http://localhost/#/verify'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'hash', - path: '/sign-in', - authQueryString: null, - } as any, - '', - ), - ).toBe('http://localhost/#/verify'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'hash', - path: '', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - '', - ), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'hash', - path: '/sign-in', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - '', - ), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'hash', - path: '/sign-in', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - 'https://accounts.clerk.com/sign-in', - ), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); - }); - - it('returns the magic link redirect url for components using virtual routing ', function () { - expect( - buildEmailLinkRedirectUrl( - { - routing: 'virtual', - authQueryString: 'redirectUrl=https://clerk.com', - } as any, - 'https://accounts.clerk.com/sign-in', - ), - ).toBe('https://accounts.clerk.com/sign-in#/verify?redirectUrl=https://clerk.com'); - - expect( - buildEmailLinkRedirectUrl( - { - routing: 'virtual', - } as any, - 'https://accounts.clerk.com/sign-in', - ), - ).toBe('https://accounts.clerk.com/sign-in#/verify'); - }); -}); - -describe('buildSSOCallbackURL(ctx, baseUrl)', () => { - it('returns the SSO callback URL based on sign in|up component routing or the provided base URL', () => { - // Default callback URLS - expect(buildSSOCallbackURL({}, '')).toBe('http://localhost/#/sso-callback'); - expect(buildSSOCallbackURL({}, 'http://test.host')).toBe('http://localhost/#/sso-callback'); - expect(buildSSOCallbackURL({ authQueryString: 'redirect_url=%2Ffoo' }, 'http://test.host')).toBe( - 'http://localhost/#/sso-callback?redirect_url=%2Ffoo', - ); - - // Components mounted with hash routing - expect(buildSSOCallbackURL({ routing: 'hash' }, 'http://test.host')).toBe('http://localhost/#/sso-callback'); - expect(buildSSOCallbackURL({ routing: 'hash', authQueryString: 'redirect_url=%2Ffoo' }, 'http://test.host')).toBe( - 'http://localhost/#/sso-callback?redirect_url=%2Ffoo', - ); - - // Components mounted with path routing - expect(buildSSOCallbackURL({ routing: 'path', path: 'sign-in' }, 'http://test.host')).toBe( - 'http://localhost/sign-in/sso-callback', - ); - expect( - buildSSOCallbackURL( - { - routing: 'path', - path: 'sign-in', - authQueryString: 'redirect_url=%2Ffoo', - }, - 'http://test.host', - ), - ).toBe('http://localhost/sign-in/sso-callback?redirect_url=%2Ffoo'); - - // Components mounted with virtual routing - expect(buildSSOCallbackURL({ routing: 'virtual' }, 'http://test.host')).toBe('http://test.host/#/sso-callback'); - expect( - buildSSOCallbackURL({ routing: 'virtual', authQueryString: 'redirect_url=%2Ffoo' }, 'http://test.host'), - ).toBe('http://test.host/#/sso-callback?redirect_url=%2Ffoo'); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/__tests__/verification.test.ts b/packages/clerk-js/src/ui.retheme/common/__tests__/verification.test.ts deleted file mode 100644 index 6cb7970a463..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/__tests__/verification.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ClerkAPIResponseError } from '@clerk/shared/error'; - -import { isVerificationExpiredError, VerificationErrorMessage, verificationErrorMessage } from '../verification'; - -describe('verification utils', () => { - describe('verificationErrorMessage', () => { - it('returns expired error message', () => { - expect( - verificationErrorMessage( - new ClerkAPIResponseError('message', { - data: [{ code: 'verification_expired', message: 'message' }], - status: 400, - }), - ), - ).toEqual(VerificationErrorMessage.CodeExpired); - }); - - it('returns clerk API error message', () => { - const message = 'The message'; - const longMessage = 'The longest message'; - expect( - verificationErrorMessage( - new ClerkAPIResponseError(message, { - data: [{ code: 'whatever', long_message: longMessage, message }], - status: 400, - }), - ), - ).toEqual(longMessage); - - expect( - verificationErrorMessage( - new ClerkAPIResponseError(message, { - data: [{ code: 'whatever', message }], - status: 400, - }), - ), - ).toEqual(message); - }); - - it('falls back to default error message', () => { - expect(verificationErrorMessage(new Error('the error'))).toEqual(VerificationErrorMessage.Incorrect); - }); - }); - - describe('isVerificationExpiredError', () => { - it('returns true for expired code', () => { - const message = 'the message'; - expect( - isVerificationExpiredError( - new ClerkAPIResponseError(message, { - data: [{ code: 'verification_expired', message }], - status: 400, - }).errors[0], - ), - ).toEqual(true); - expect( - isVerificationExpiredError( - new ClerkAPIResponseError(message, { - data: [{ code: 'whatever', message }], - status: 400, - }).errors[0], - ), - ).toEqual(false); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/__tests__/withRedirectToHome.test.tsx b/packages/clerk-js/src/ui.retheme/common/__tests__/withRedirectToHome.test.tsx deleted file mode 100644 index 8dd9d54f6a0..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/__tests__/withRedirectToHome.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; - -import { render, screen } from '../../../testUtils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; -import { - withRedirectToHomeOrganizationGuard, - withRedirectToHomeSingleSessionGuard, - withRedirectToHomeUserGuard, -} from '../withRedirectToHome'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -describe('withRedirectToHome', () => { - describe('withRedirectToHomeSingleSessionGuard', () => { - it('redirects if a session is present and single session mode is enabled', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({}); - }); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is a session is not present', async () => { - const { wrapper } = await createFixtures(); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - - it('renders the children if multi session mode is enabled and a session is present', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({}); - f.withMultiSessionMode(); - }); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); - - describe('redirectToHomeUserGuard', () => { - it('redirects if no user is present', async () => { - const { wrapper, fixtures } = await createFixtures(); - - const WithHOC = withRedirectToHomeUserGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is a user is present', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({}); - }); - - const WithHOC = withRedirectToHomeUserGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); - - describe('withRedirectToHomeOrganizationGuard', () => { - it('redirects if no organization is active', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({}); - f.withOrganizations(); - }); - - const WithHOC = withRedirectToHomeOrganizationGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is an organization is active', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({ organization_memberships: ['Org1'] }); - f.withOrganizations(); - }); - - const WithHOC = withRedirectToHomeOrganizationGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/common/constants.ts b/packages/clerk-js/src/ui.retheme/common/constants.ts deleted file mode 100644 index 37a7cf642ba..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/constants.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { Attribute, Web3Provider } from '@clerk/types'; - -import type { LocalizationKey } from '../localization/localizationKeys'; -import { localizationKeys } from '../localization/localizationKeys'; - -type FirstFactorConfig = { - label: string | LocalizationKey; - type: string; - placeholder: string | LocalizationKey; - action?: string | LocalizationKey; -}; -const FirstFactorConfigs = Object.freeze({ - email_address_username: { - label: localizationKeys('formFieldLabel__emailAddress_username'), - placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress_username'), - type: 'text', - action: localizationKeys('signIn.start.actionLink__use_email_username'), - }, - email_address: { - label: localizationKeys('formFieldLabel__emailAddress'), - placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress'), - type: 'email', - action: localizationKeys('signIn.start.actionLink__use_email'), - }, - phone_number: { - label: localizationKeys('formFieldLabel__phoneNumber'), - placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'), - type: 'tel', - action: localizationKeys('signIn.start.actionLink__use_phone'), - }, - username: { - label: localizationKeys('formFieldLabel__username'), - placeholder: localizationKeys('formFieldInputPlaceholder__username'), - type: 'text', - action: localizationKeys('signIn.start.actionLink__use_username'), - }, - default: { - label: '', - placeholder: '', - type: 'text', - action: '', - }, -} as Record); - -export type SignInStartIdentifier = 'email_address' | 'username' | 'phone_number' | 'email_address_username'; -export const groupIdentifiers = (attributes: Attribute[]): SignInStartIdentifier[] => { - let newAttributes: string[] = [...attributes]; - //merge email_address and username attributes - if (['email_address', 'username'].every(r => newAttributes.includes(r))) { - newAttributes = newAttributes.filter(a => !['email_address', 'username'].includes(a)); - newAttributes.unshift('email_address_username'); - } - - return newAttributes as SignInStartIdentifier[]; -}; - -export const getIdentifierControlDisplayValues = ( - identifiers: SignInStartIdentifier[], - identifier: SignInStartIdentifier, -): { currentIdentifier: FirstFactorConfig; nextIdentifier?: FirstFactorConfig } => { - const index = identifiers.indexOf(identifier); - - if (index === -1) { - return { currentIdentifier: { ...FirstFactorConfigs['default'] }, nextIdentifier: undefined }; - } - - return { - currentIdentifier: { ...FirstFactorConfigs[identifier] }, - nextIdentifier: - identifiers.length > 1 ? { ...FirstFactorConfigs[identifiers[(index + 1) % identifiers.length]] } : undefined, - }; -}; - -export const PREFERRED_SIGN_IN_STRATEGIES = Object.freeze({ - Password: 'password', - OTP: 'otp', -}); - -interface Web3ProviderData { - id: string; - name: string; -} - -type Web3Providers = { - [key in Web3Provider]: Web3ProviderData; -}; - -export const WEB3_PROVIDERS: Web3Providers = Object.freeze({ - metamask: { - id: 'metamask', - name: 'MetaMask', - }, -}); - -export function getWeb3ProviderData(name: Web3Provider): Web3ProviderData | undefined | null { - return WEB3_PROVIDERS[name]; -} - -/** - * Returns the URL for a static SVG image - * using the new img.clerk.com service - */ -export function iconImageUrl(id: string): string { - return `https://img.clerk.com/static/${id}.svg`; -} diff --git a/packages/clerk-js/src/ui.retheme/common/forms.ts b/packages/clerk-js/src/ui.retheme/common/forms.ts deleted file mode 100644 index c750804ce53..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/forms.ts +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; - -export interface FieldState { - name: string; - required?: boolean; - value: T; - setValue: React.Dispatch>; - error: string | undefined; - setError: React.Dispatch>; -} - -export const buildRequest = (fieldStates: Array>): Record => { - const request: { [x: string]: any } = {}; - fieldStates.forEach(x => { - request[x.name] = x.value; - }); - return request; -}; - -export const useFieldState = (name: string, initialState: T): FieldState => { - const [value, setValue] = React.useState(initialState); - const [error, setError] = React.useState(undefined); - - return { - name, - value, - setValue, - error, - setError, - }; -}; - -// TODO: Replace origin useFieldState with this one -export const useFieldStateV2 = (name: string, required: boolean, initialState: T): FieldState => { - const [value, setValue] = React.useState(initialState); - const [error, setError] = React.useState(undefined); - - return { - name, - required, - value, - setValue, - error, - setError, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/common/index.ts b/packages/clerk-js/src/ui.retheme/common/index.ts deleted file mode 100644 index b08b98ac791..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export * from './BlockButtons'; -export * from './constants'; -export * from './CalloutWithAction'; -export * from './forms'; -export * from './Gate'; -export * from './InfiniteListSpinner'; -export * from './redirects'; -export * from './verification'; -export * from './withRedirectToHome'; -export * from './SSOCallback'; -export * from './EmailLinkVerify'; -export * from './EmailLinkStatusCard'; -export * from './Wizard'; -export * from './RemoveResourcePage'; -export * from './PrintableComponent'; -export * from './NotificationCountBadge'; -export * from './RemoveResourcePage'; -export * from './withOrganizationsEnabledGuard'; -export * from './QRCode'; diff --git a/packages/clerk-js/src/ui.retheme/common/redirects.ts b/packages/clerk-js/src/ui.retheme/common/redirects.ts deleted file mode 100644 index c5bf2858cfe..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/redirects.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { buildURL } from '../../utils/url'; -import type { SignInContextType, SignUpContextType, UserProfileContextType } from './../contexts'; - -const SSO_CALLBACK_PATH_ROUTE = '/sso-callback'; -const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify'; - -export function buildEmailLinkRedirectUrl( - ctx: SignInContextType | SignUpContextType | UserProfileContextType, - baseUrl: string | undefined = '', -): string { - const { routing, authQueryString, path } = ctx; - return buildRedirectUrl({ - routing, - baseUrl, - authQueryString, - path, - endpoint: MAGIC_LINK_VERIFY_PATH_ROUTE, - }); -} - -export function buildSSOCallbackURL( - ctx: Partial, - baseUrl: string | undefined = '', -): string { - const { routing, authQueryString, path } = ctx; - return buildRedirectUrl({ - routing, - baseUrl, - authQueryString, - path, - endpoint: SSO_CALLBACK_PATH_ROUTE, - }); -} - -type AuthQueryString = string | null | undefined; -type BuildRedirectUrlParams = { - routing: string | undefined; - authQueryString: AuthQueryString; - baseUrl: string; - path: string | undefined; - endpoint: string; -}; - -const buildRedirectUrl = ({ routing, authQueryString, baseUrl, path, endpoint }: BuildRedirectUrlParams): string => { - if (!routing || routing === 'hash') { - return buildHashBasedUrl(authQueryString, endpoint); - } - - if (routing === 'path') { - return buildPathBasedUrl(path || '', authQueryString, endpoint); - } - - return buildVirtualBasedUrl(baseUrl || '', authQueryString, endpoint); -}; - -const buildHashBasedUrl = (authQueryString: AuthQueryString, endpoint: string): string => { - // Strip hash to get the URL where we're mounted - const hash = endpoint + (authQueryString ? `?${authQueryString}` : ''); - return buildURL({ hash }, { stringify: true }); -}; - -const buildPathBasedUrl = (path: string, authQueryString: AuthQueryString, endpoint: string): string => { - const searchArg = authQueryString ? { search: '?' + authQueryString } : {}; - return buildURL( - { - pathname: path + endpoint, - ...searchArg, - }, - { stringify: true }, - ); -}; - -const buildVirtualBasedUrl = (base: string, authQueryString: AuthQueryString, endpoint: string): string => { - const hash = endpoint + (authQueryString ? `?${authQueryString}` : ''); - return buildURL({ base, hash }, { stringify: true }); -}; diff --git a/packages/clerk-js/src/ui.retheme/common/verification.ts b/packages/clerk-js/src/ui.retheme/common/verification.ts deleted file mode 100644 index 3bad86c5017..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/verification.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ClerkAPIError } from '@clerk/types'; - -import { getClerkAPIErrorMessage, getGlobalError } from '../utils'; - -export const VerificationErrorMessage = { - Incorrect: 'Incorrect, try again', - CodeExpired: 'The code has expired. Resend a new one.', -}; - -export function verificationErrorMessage(err: Error): string { - const globalErr = getGlobalError(err); - if (!globalErr) { - return VerificationErrorMessage.Incorrect; - } - if (isVerificationExpiredError(globalErr)) { - return VerificationErrorMessage.CodeExpired; - } - return getClerkAPIErrorMessage(globalErr); -} - -export function isVerificationExpiredError(err: ClerkAPIError): boolean { - return err.code === 'verification_expired'; -} diff --git a/packages/clerk-js/src/ui.retheme/common/withOrganizationsEnabledGuard.tsx b/packages/clerk-js/src/ui.retheme/common/withOrganizationsEnabledGuard.tsx deleted file mode 100644 index 72b8c660461..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/withOrganizationsEnabledGuard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import { useEnvironment } from '../contexts'; -import { useRouter } from '../router'; - -export function withOrganizationsEnabledGuard

( - WrappedComponent: React.ComponentType

, - name: string, - options: { mode: 'redirect' | 'hide' }, -): React.ComponentType

{ - const Hoc = (props: P) => { - const { navigate } = useRouter(); - const { organizationSettings, displayConfig } = useEnvironment(); - - React.useEffect(() => { - if (options.mode === 'redirect' && !organizationSettings.enabled) { - void navigate(displayConfig.homeUrl); - } - }, []); - - if (options.mode === 'hide' && !organizationSettings.enabled) { - return null; - } - - return ; - }; - Hoc.displayName = name; - return Hoc; -} diff --git a/packages/clerk-js/src/ui.retheme/common/withRedirectToHome.tsx b/packages/clerk-js/src/ui.retheme/common/withRedirectToHome.tsx deleted file mode 100644 index 9309dfe231d..00000000000 --- a/packages/clerk-js/src/ui.retheme/common/withRedirectToHome.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import type { ComponentType } from 'react'; -import React from 'react'; - -import { warnings } from '../../core/warnings'; -import type { ComponentGuard } from '../../utils'; -import { noOrganizationExists, noUserExists, sessionExistsAndSingleSessionModeEnabled } from '../../utils'; -import { useEnvironment, useOptions } from '../contexts'; -import { useRouter } from '../router'; -import type { AvailableComponentProps } from '../types'; - -function withRedirectToHome

( - Component: ComponentType

, - condition: ComponentGuard, - warning?: string, -): (props: P) => null | JSX.Element { - const displayName = Component.displayName || Component.name || 'Component'; - Component.displayName = displayName; - - const HOC = (props: P) => { - const { navigate } = useRouter(); - const clerk = useClerk(); - const environment = useEnvironment(); - const options = useOptions(); - - const shouldRedirect = condition(clerk, environment, options); - React.useEffect(() => { - if (shouldRedirect) { - if (warning && environment.displayConfig.instanceEnvironmentType === 'development') { - console.info(warning); - } - // TODO: Fix this properly - // eslint-disable-next-line @typescript-eslint/no-floating-promises - navigate(environment.displayConfig.homeUrl); - } - }, []); - - if (shouldRedirect) { - return null; - } - - return ; - }; - - HOC.displayName = `withRedirectToHome(${displayName})`; - - return HOC; -} - -export const withRedirectToHomeSingleSessionGuard =

(Component: ComponentType

) => - withRedirectToHome( - Component, - sessionExistsAndSingleSessionModeEnabled, - warnings.cannotRenderComponentWhenSessionExists, - ); - -export const withRedirectToHomeUserGuard =

(Component: ComponentType

) => - withRedirectToHome(Component, noUserExists, warnings.cannotRenderComponentWhenUserDoesNotExist); - -export const withRedirectToHomeOrganizationGuard =

(Component: ComponentType

) => - withRedirectToHome(Component, noOrganizationExists, warnings.cannotRenderComponentWhenOrgDoesNotExist); diff --git a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganization.tsx b/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganization.tsx deleted file mode 100644 index 981879f92bd..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganization.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { CreateOrganizationModalProps } from '@clerk/types'; - -import { withOrganizationsEnabledGuard } from '../../common'; -import { ComponentContext, withCoreUserGuard } from '../../contexts'; -import { Flow } from '../../customizables'; -import { ProfileCard, ProfileCardContent, withCardStateProvider } from '../../elements'; -import { Route, Switch } from '../../router'; -import type { CreateOrganizationCtx } from '../../types'; -import { CreateOrganizationPage } from './CreateOrganizationPage'; - -const _CreateOrganization = () => { - return ( - - - - - - - - - - ); -}; - -const AuthenticatedRoutes = withCoreUserGuard(() => { - return ( - ({ width: t.sizes.$120 })}> - - - - - ); -}); - -export const CreateOrganization = withOrganizationsEnabledGuard( - withCardStateProvider(_CreateOrganization), - 'CreateOrganization', - { mode: 'redirect' }, -); - -export const CreateOrganizationModal = (props: CreateOrganizationModalProps): JSX.Element => { - const createOrganizationProps: CreateOrganizationCtx = { - ...props, - routing: 'virtual', - componentName: 'CreateOrganization', - mode: 'modal', - }; - - return ( - - -

- -
- - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationForm.tsx deleted file mode 100644 index 62959279bfd..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationForm.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { useOrganization, useOrganizationList } from '@clerk/shared/react'; -import type { OrganizationResource } from '@clerk/types'; -import React from 'react'; - -import { useWizard, Wizard } from '../../common'; -import { Icon } from '../../customizables'; -import { ContentPage, Form, FormButtonContainer, IconButton, SuccessPage, useCardState } from '../../elements'; -import { QuestionMark, Upload } from '../../icons'; -import type { LocalizationKey } from '../../localization'; -import { localizationKeys } from '../../localization'; -import { colors, createSlug, handleError, useFormControl } from '../../utils'; -import { InviteMembersForm } from '../OrganizationProfile/InviteMembersForm'; -import { InvitationsSentMessage } from '../OrganizationProfile/InviteMembersPage'; -import { OrganizationProfileAvatarUploader } from '../OrganizationProfile/OrganizationProfileAvatarUploader'; - -type CreateOrganizationFormProps = { - skipInvitationScreen: boolean; - navigateAfterCreateOrganization: (organization: OrganizationResource) => Promise; - onCancel?: () => void; - onComplete?: () => void; - flow: 'default' | 'organizationList'; - startPage: { - headerTitle: LocalizationKey; - headerSubtitle?: LocalizationKey; - }; -}; - -export const CreateOrganizationForm = (props: CreateOrganizationFormProps) => { - const card = useCardState(); - const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); - - const lastCreatedOrganizationRef = React.useRef(null); - const { createOrganization, isLoaded, setActive } = useOrganizationList(); - const { organization } = useOrganization(); - const [file, setFile] = React.useState(); - - const nameField = useFormControl('name', '', { - type: 'text', - label: localizationKeys('formFieldLabel__organizationName'), - placeholder: localizationKeys('formFieldInputPlaceholder__organizationName'), - }); - - const slugField = useFormControl('slug', '', { - type: 'text', - label: localizationKeys('formFieldLabel__organizationSlug'), - placeholder: localizationKeys('formFieldInputPlaceholder__organizationSlug'), - }); - - const dataChanged = !!nameField.value; - const canSubmit = dataChanged; - - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!canSubmit) { - return; - } - - if (!isLoaded) { - return; - } - - try { - const organization = await createOrganization({ name: nameField.value, slug: slugField.value }); - if (file) { - await organization.setLogo({ file }); - } - - lastCreatedOrganizationRef.current = organization; - await setActive({ organization }); - - if (props.skipInvitationScreen ?? organization.maxAllowedMemberships === 1) { - return completeFlow(); - } - - wizard.nextStep(); - } catch (err) { - handleError(err, [nameField, slugField], card.setError); - } - }; - - const completeFlow = () => { - // We are confident that lastCreatedOrganizationRef.current will never be null - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - void props.navigateAfterCreateOrganization(lastCreatedOrganizationRef.current!); - - props.onComplete?.(); - }; - - const onAvatarRemove = () => { - card.setIdle(); - return setFile(null); - }; - - const onChangeName = (event: React.ChangeEvent) => { - nameField.setValue(event.target.value); - updateSlugField(createSlug(event.target.value)); - }; - - const onChangeSlug = (event: React.ChangeEvent) => { - updateSlugField(event.target.value); - }; - - const updateSlugField = (val: string) => { - slugField.setValue(val); - }; - - const headerTitleTextVariant = props.flow === 'organizationList' ? 'h2' : undefined; - const headerSubtitleTextVariant = props.flow === 'organizationList' ? 'subtitle' : undefined; - - return ( - - ({ minHeight: t.sizes.$60 })} - > - - await setFile(file)} - onAvatarRemove={file ? onAvatarRemove : null} - avatarPreviewPlaceholder={ - ({ - transitionDuration: theme.transitionDuration.$controls, - })} - /> - } - sx={theme => ({ - width: theme.sizes.$12, - height: theme.sizes.$12, - borderRadius: theme.radii.$md, - backgroundColor: theme.colors.$avatarBackground, - ':hover': { - backgroundColor: colors.makeTransparent(theme.colors.$avatarBackground, 0.2), - svg: { - transform: 'scale(1.2)', - }, - }, - })} - /> - } - /> - - - - - - - - - {props.onCancel && ( - - )} - - - - ({ minHeight: t.sizes.$60 })} - > - {organization && ( - - )} - - } - sx={t => ({ minHeight: t.sizes.$60 })} - onFinish={completeFlow} - /> - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationPage.tsx b/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationPage.tsx deleted file mode 100644 index 6b4c335cc66..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/CreateOrganizationPage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; - -import { useCreateOrganizationContext } from '../../contexts'; -import { localizationKeys } from '../../customizables'; -import { withCardStateProvider } from '../../elements'; -import { CreateOrganizationForm } from './CreateOrganizationForm'; - -export const CreateOrganizationPage = withCardStateProvider(() => { - const title = localizationKeys('createOrganization.title'); - const { closeCreateOrganization } = useClerk(); - - const { mode, navigateAfterCreateOrganization, skipInvitationScreen } = useCreateOrganizationContext(); - - return ( - { - if (mode === 'modal') { - closeCreateOrganization(); - } - }} - /> - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/__tests__/CreateOrganization.test.tsx b/packages/clerk-js/src/ui.retheme/components/CreateOrganization/__tests__/CreateOrganization.test.tsx deleted file mode 100644 index 03f542cc7bf..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/__tests__/CreateOrganization.test.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import type { OrganizationResource } from '@clerk/types'; -import { describe, jest } from '@jest/globals'; -import { waitFor } from '@testing-library/dom'; - -import { render } from '../../../../testUtils'; -import { bindCreateFixtures } from '../../../utils/test/createFixtures'; -import { CreateOrganization } from '../CreateOrganization'; - -const { createFixtures } = bindCreateFixtures('CreateOrganization'); - -export type FakeOrganizationParams = { - id: string; - createdAt?: Date; - imageUrl?: string; - slug: string; - name: string; - membersCount: number; - pendingInvitationsCount: number; - adminDeleteEnabled: boolean; - maxAllowedMemberships: number; -}; - -export const createFakeOrganization = (params: FakeOrganizationParams): OrganizationResource => { - return { - pathRoot: '', - id: params.id, - name: params.name, - slug: params.slug, - hasImage: !!params.imageUrl, - imageUrl: params.imageUrl || '', - membersCount: params.membersCount, - pendingInvitationsCount: params.pendingInvitationsCount, - publicMetadata: {}, - adminDeleteEnabled: params.adminDeleteEnabled, - maxAllowedMemberships: params?.maxAllowedMemberships, - createdAt: params?.createdAt || new Date(), - updatedAt: new Date(), - update: jest.fn() as any, - getMemberships: jest.fn() as any, - addMember: jest.fn() as any, - inviteMember: jest.fn() as any, - inviteMembers: jest.fn() as any, - updateMember: jest.fn() as any, - removeMember: jest.fn() as any, - createDomain: jest.fn() as any, - getDomain: jest.fn() as any, - getDomains: jest.fn() as any, - getMembershipRequests: jest.fn() as any, - destroy: jest.fn() as any, - setLogo: jest.fn() as any, - reload: jest.fn() as any, - }; -}; - -const getCreatedOrg = (params: Partial) => - createFakeOrganization({ - id: '1', - adminDeleteEnabled: false, - maxAllowedMemberships: 1, - membersCount: 1, - name: 'new org', - pendingInvitationsCount: 0, - slug: 'new-org', - ...params, - }); - -describe('CreateOrganization', () => { - it('renders component', async () => { - const { wrapper } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - const { getByText } = render(, { wrapper }); - expect(getByText('Create Organization')).toBeInTheDocument(); - }); - - it('skips invitation screen', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - fixtures.clerk.createOrganization.mockReturnValue( - Promise.resolve( - getCreatedOrg({ - maxAllowedMemberships: 3, - }), - ), - ); - - props.setProps({ skipInvitationScreen: true }); - const { getByRole, userEvent, getByLabelText, queryByText } = render(, { - wrapper, - }); - await userEvent.type(getByLabelText(/Organization name/i), 'new org'); - await userEvent.click(getByRole('button', { name: /create organization/i })); - - await waitFor(() => { - expect(queryByText(/Invite members/i)).not.toBeInTheDocument(); - }); - }); - - it('always visit invitation screen', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - fixtures.clerk.createOrganization.mockReturnValue( - Promise.resolve( - getCreatedOrg({ - maxAllowedMemberships: 1, - }), - ), - ); - - props.setProps({ skipInvitationScreen: false }); - const { getByRole, userEvent, getByLabelText, queryByText } = render(, { - wrapper, - }); - await userEvent.type(getByLabelText(/Organization name/i), 'new org'); - await userEvent.click(getByRole('button', { name: /create organization/i })); - - await waitFor(() => { - expect(queryByText(/Invite members/i)).toBeInTheDocument(); - }); - }); - - it('auto skip invitation screen', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - fixtures.clerk.createOrganization.mockReturnValue( - Promise.resolve( - getCreatedOrg({ - maxAllowedMemberships: 1, - }), - ), - ); - - const { getByRole, userEvent, getByLabelText, queryByText } = render(, { - wrapper, - }); - await userEvent.type(getByLabelText(/Organization name/i), 'new org'); - await userEvent.click(getByRole('button', { name: /create organization/i })); - - await waitFor(() => { - expect(queryByText(/Invite members/i)).not.toBeInTheDocument(); - }); - }); - - describe('navigation', () => { - it('constructs afterCreateOrganizationUrl from function', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - const createdOrg = getCreatedOrg({ - maxAllowedMemberships: 1, - }); - - fixtures.clerk.createOrganization.mockReturnValue(Promise.resolve(createdOrg)); - - props.setProps({ afterCreateOrganizationUrl: org => `/org/${org.id}`, skipInvitationScreen: true }); - const { getByRole, userEvent, getByLabelText } = render(, { - wrapper, - }); - await userEvent.type(getByLabelText(/Organization name/i), 'new org'); - await userEvent.click(getByRole('button', { name: /create organization/i })); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(`/org/${createdOrg.id}`); - }); - - it('constructs afterCreateOrganizationUrl from `:slug` ', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - const createdOrg = getCreatedOrg({ - maxAllowedMemberships: 1, - }); - - fixtures.clerk.createOrganization.mockReturnValue(Promise.resolve(createdOrg)); - - props.setProps({ afterCreateOrganizationUrl: '/org/:slug', skipInvitationScreen: true }); - const { getByRole, userEvent, getByLabelText } = render(, { - wrapper, - }); - await userEvent.type(getByLabelText(/Organization name/i), 'new org'); - await userEvent.click(getByRole('button', { name: /create organization/i })); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(`/org/${createdOrg.slug}`); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/index.tsx b/packages/clerk-js/src/ui.retheme/components/CreateOrganization/index.tsx deleted file mode 100644 index 3ce3f438c96..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/CreateOrganization/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './CreateOrganization'; diff --git a/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/ImpersonationFab.tsx b/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/ImpersonationFab.tsx deleted file mode 100644 index 8b1010caca0..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/ImpersonationFab.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { useClerk, useSession } from '@clerk/shared/react'; -import type { PointerEventHandler } from 'react'; -import React, { useEffect, useRef } from 'react'; - -import { getFullName, getIdentifier } from '../../../utils/user'; -import { withCoreUserGuard } from '../../contexts'; -import type { LocalizationKey } from '../../customizables'; -import { - Col, - descriptors, - Flex, - Icon, - Link, - localizationKeys, - Text, - useAppearance, - useLocalizations, -} from '../../customizables'; -import { Portal } from '../../elements/Portal'; -import { Eye } from '../../icons'; -import type { PropsOfComponent } from '../../styledSystem'; -import { InternalThemeProvider, mqu } from '../../styledSystem'; - -type EyeCircleProps = PropsOfComponent & { - width: string; - height: string; -}; - -const EyeCircle = ({ width, height, ...props }: EyeCircleProps) => { - const { sx, ...rest } = props; - return ( - ({ - width, - height, - backgroundColor: t.colors.$danger500, - borderRadius: t.radii.$circle, - }), - sx, - ]} - {...rest} - > - ({ - color: t.colors.$white, - })} - size={'lg'} - /> - - ); -}; - -type FabContentProps = { title: LocalizationKey; signOutText: LocalizationKey }; - -const FabContent = ({ title, signOutText }: FabContentProps) => { - const { session } = useSession(); - const { signOut } = useClerk(); - - return ( - ({ - width: '100%', - paddingLeft: t.sizes.$4, - paddingRight: t.sizes.$6, - whiteSpace: 'nowrap', - })} - > - - ({ - alignSelf: 'flex-start', - color: t.colors.$primary500, - ':hover': { - cursor: 'pointer', - }, - })} - localizationKey={signOutText} - onClick={async () => { - // clerk-js has been loaded at this point so we can safely access session - await signOut({ sessionId: session!.id }); - }} - /> - - ); -}; - -const _ImpersonationFab = () => { - const { session } = useSession(); - const { t } = useLocalizations(); - const { parsedInternalTheme } = useAppearance(); - const containerRef = useRef(null); - const actor = session?.actor; - const isImpersonating = !!actor; - - //essentials for calcs - const eyeWidth = parsedInternalTheme.sizes.$16; - const eyeHeight = eyeWidth; - const topProperty = '--cl-impersonation-fab-top'; - const rightProperty = '--cl-impersonation-fab-right'; - const defaultTop = 109; - const defaultRight = 23; - - const handleResize = () => { - const current = containerRef.current; - if (!current) { - return; - } - - const offsetRight = window.innerWidth - current.offsetLeft - current.offsetWidth; - const offsetBottom = window.innerHeight - current.offsetTop - current.offsetHeight; - - const outsideViewport = [current.offsetLeft, offsetRight, current.offsetTop, offsetBottom].some(o => o < 0); - - if (outsideViewport) { - document.documentElement.style.setProperty(rightProperty, `${defaultRight}px`); - document.documentElement.style.setProperty(topProperty, `${defaultTop}px`); - } - }; - - const onPointerDown: PointerEventHandler = () => { - window.addEventListener('pointermove', onPointerMove); - window.addEventListener( - 'pointerup', - () => { - window.removeEventListener('pointermove', onPointerMove); - handleResize(); - }, - { once: true }, - ); - }; - - const onPointerMove = React.useCallback((e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - const current = containerRef.current; - if (!current) { - return; - } - const rightOffestBasedOnViewportAndContent = `${ - window.innerWidth - current.offsetLeft - current.offsetWidth - e.movementX - }px`; - document.documentElement.style.setProperty(rightProperty, rightOffestBasedOnViewportAndContent); - document.documentElement.style.setProperty(topProperty, `${current.offsetTop - -e.movementY}px`); - }, []); - - const repositionFabOnResize = () => { - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }; - - useEffect(repositionFabOnResize, []); - - if (!isImpersonating || !session.user) { - return null; - } - - const title = localizationKeys('impersonationFab.title', { - identifier: getFullName(session.user) || getIdentifier(session.user), - }); - const titleLength = t(title).length; - - return ( - - ({ - touchAction: 'none', //for drag to work on mobile consistently - position: 'fixed', - overflow: 'hidden', - top: `var(${topProperty}, ${defaultTop}px)`, - right: `var(${rightProperty}, ${defaultRight}px)`, - zIndex: t.zIndices.$fab, - boxShadow: t.shadows.$fabShadow, - borderRadius: t.radii.$halfHeight, //to match the circular eye perfectly - backgroundColor: t.colors.$white, - fontFamily: t.fonts.$main, - ':hover': { - cursor: 'grab', - }, - ':hover #cl-impersonationText': { - transition: `max-width ${t.transitionDuration.$slowest} ease, opacity ${t.transitionDuration.$slower} ease ${t.transitionDuration.$slowest}`, - maxWidth: `min(calc(50vw - ${eyeWidth} - 2 * ${defaultRight}px), ${titleLength}ch)`, - [mqu.md]: { - maxWidth: `min(calc(100vw - ${eyeWidth} - 2 * ${defaultRight}px), ${titleLength}ch)`, - }, - opacity: 1, - }, - ':hover #cl-impersonationEye': { - transform: 'rotate(-180deg)', - }, - })} - > - ({ - transition: `transform ${t.transitionDuration.$slowest} ease`, - })} - /> - - ({ - transition: `max-width ${t.transitionDuration.$slowest} ease, opacity ${t.transitionDuration.$fast} ease`, - maxWidth: '0px', - opacity: 0, - })} - > - - - - - ); -}; - -export const ImpersonationFab = withCoreUserGuard(() => ( - - <_ImpersonationFab /> - -)); diff --git a/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/index.ts b/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/index.ts deleted file mode 100644 index 7356c19b226..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/ImpersonationFab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ImpersonationFab'; diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationList.tsx deleted file mode 100644 index b36a7bcdd4d..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationList.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { withOrganizationsEnabledGuard } from '../../common'; -import { withCoreUserGuard } from '../../contexts'; -import { Flow } from '../../customizables'; -import { Route, Switch } from '../../router'; -import { OrganizationListPage } from './OrganizationListPage'; - -const _OrganizationList = () => { - return ( - - - - - - - - - - ); -}; - -const AuthenticatedRoutes = withCoreUserGuard(OrganizationListPage); - -export const OrganizationList = withOrganizationsEnabledGuard(_OrganizationList, 'OrganizationList', { - mode: 'redirect', -}); diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationListPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationListPage.tsx deleted file mode 100644 index 3abb84084ee..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/OrganizationListPage.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { useOrganizationList } from '@clerk/shared/react'; -import { useState } from 'react'; - -import { useEnvironment, useOrganizationListContext } from '../../contexts'; -import { Box, Button, Col, descriptors, Flex, localizationKeys, Spinner } from '../../customizables'; -import { Card, CardAlert, Divider, Header, useCardState, withCardStateProvider } from '../../elements'; -import { useInView } from '../../hooks'; -import { CreateOrganizationForm } from '../CreateOrganization/CreateOrganizationForm'; -import { PreviewListItems, PreviewListSpinner } from './shared'; -import { InvitationPreview } from './UserInvitationList'; -import { MembershipPreview, PersonalAccountPreview } from './UserMembershipList'; -import { SuggestionPreview } from './UserSuggestionList'; -import { organizationListParams } from './utils'; - -const useOrganizationListInView = () => { - const { userMemberships, userInvitations, userSuggestions } = useOrganizationList(organizationListParams); - - const { ref } = useInView({ - threshold: 0, - onChange: inView => { - if (!inView) { - return; - } - if (userMemberships.hasNextPage) { - userMemberships.fetchNext?.(); - } else if (userInvitations.hasNextPage) { - userInvitations.fetchNext?.(); - } else { - userSuggestions.fetchNext?.(); - } - }, - }); - - return { - userMemberships, - userInvitations, - userSuggestions, - ref, - }; -}; - -export const OrganizationListPage = withCardStateProvider(() => { - const card = useCardState(); - const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView(); - const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading; - const hasAnyData = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count); - - const { hidePersonal } = useOrganizationListContext(); - - return ( - ({ - padding: `${t.space.$8} ${t.space.$none}`, - })} - gap={6} - > - {card.error} - {isLoading && ( - ({ - height: '100%', - minHeight: t.sizes.$60, - })} - > - - - )} - - {!isLoading && } - - ); -}); - -const OrganizationListFlows = ({ showListInitially }: { showListInitially: boolean }) => { - const environment = useEnvironment(); - const { navigateAfterSelectOrganization, skipInvitationScreen } = useOrganizationListContext(); - const [isCreateOrganizationFlow, setCreateOrganizationFlow] = useState(!showListInitially); - return ( - <> - {!isCreateOrganizationFlow && ( - setCreateOrganizationFlow(true)} /> - )} - - {isCreateOrganizationFlow && ( - ({ - padding: `${t.space.$none} ${t.space.$8}`, - })} - > - - navigateAfterSelectOrganization(org).then(() => setCreateOrganizationFlow(false)) - } - onCancel={ - showListInitially && isCreateOrganizationFlow ? () => setCreateOrganizationFlow(false) : undefined - } - /> - - )} - - ); -}; - -const OrganizationListPageList = (props: { onCreateOrganizationClick: () => void }) => { - const environment = useEnvironment(); - - const { ref, userMemberships, userSuggestions, userInvitations } = useOrganizationListInView(); - const { hidePersonal } = useOrganizationListContext(); - - const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading; - const hasNextPage = userMemberships?.hasNextPage || userInvitations?.hasNextPage || userSuggestions?.hasNextPage; - - const handleCreateOrganizationClicked = () => { - props.onCreateOrganizationClick(); - }; - return ( - <> - ({ - padding: `${t.space.$none} ${t.space.$8}`, - })} - > - - - - - - - {(userMemberships.count || 0) > 0 && - userMemberships.data?.map(inv => { - return ( - - ); - })} - - {!userMemberships.hasNextPage && - (userInvitations.count || 0) > 0 && - userInvitations.data?.map(inv => { - return ( - - ); - })} - - {!userMemberships.hasNextPage && - !userInvitations.hasNextPage && - (userSuggestions.count || 0) > 0 && - userSuggestions.data?.map(inv => { - return ( - - ); - })} - - {(hasNextPage || isLoading) && } - - - ({ - padding: `${t.space.$none} ${t.space.$8}`, - })} - /> - - ({ - padding: `${t.space.$none} ${t.space.$8}`, - })} - > - - ); - }), -); - -const NotificationCountBadgeSwitcherTrigger = () => { - /** - * Prefetch user invitations and suggestions - */ - const { userInvitations, userSuggestions } = useOrganizationList(organizationListParams); - const { organizationSettings } = useEnvironment(); - const { isAuthorizedUser: canAcceptRequests } = useGate({ - permission: 'org:sys_memberships:manage', - }); - const isDomainsEnabled = organizationSettings?.domains?.enabled; - const { membershipRequests } = useOrganization({ - membershipRequests: (isDomainsEnabled && canAcceptRequests) || undefined, - }); - - const notificationCount = - (userInvitations.count || 0) + (userSuggestions.count || 0) + (membershipRequests?.count || 0); - - return ( - ({ - marginLeft: `${t.space.$2}`, - })} - notificationCount={notificationCount} - /> - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OtherOrganizationActions.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OtherOrganizationActions.tsx deleted file mode 100644 index d5c4c0e0b72..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OtherOrganizationActions.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useUser } from '@clerk/shared/react'; -import React from 'react'; - -import { descriptors, localizationKeys } from '../../customizables'; -import { Action, SecondaryActions } from '../../elements'; -import { Add } from '../../icons'; -import { UserInvitationSuggestionList } from './UserInvitationSuggestionList'; -import type { UserMembershipListProps } from './UserMembershipList'; -import { UserMembershipList } from './UserMembershipList'; - -export interface OrganizationActionListProps extends UserMembershipListProps { - onCreateOrganizationClick: React.MouseEventHandler; -} - -const CreateOrganizationButton = ({ - onCreateOrganizationClick, -}: Pick) => { - const { user } = useUser(); - - if (!user?.createOrganizationEnabled) { - return null; - } - - return ( - ({ - color: t.colors.$blackAlpha600, - ':hover': { - color: t.colors.$blackAlpha600, - }, - })} - iconSx={t => ({ - width: t.sizes.$9, - height: t.sizes.$6, - })} - /> - ); -}; - -export const OrganizationActionList = (props: OrganizationActionListProps) => { - const { onCreateOrganizationClick, onPersonalWorkspaceClick, onOrganizationClick } = props; - - return ( - <> - - - - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx deleted file mode 100644 index 323038562e2..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { useOrganizationList } from '@clerk/shared/react'; -import type { OrganizationSuggestionResource, UserOrganizationInvitationResource } from '@clerk/types'; -import type { PropsWithChildren } from 'react'; - -import { InfiniteListSpinner } from '../../common'; -import { Box, Button, descriptors, Flex, localizationKeys, Text } from '../../customizables'; -import { Actions, OrganizationPreview, useCardState, withCardStateProvider } from '../../elements'; -import { useInView } from '../../hooks'; -import type { PropsOfComponent } from '../../styledSystem'; -import { common } from '../../styledSystem'; -import { handleError } from '../../utils'; -import { organizationListParams, populateCacheRemoveItem, populateCacheUpdateItem } from './utils'; - -const useFetchInvitations = () => { - const { userInvitations, userSuggestions } = useOrganizationList(organizationListParams); - - const { ref } = useInView({ - threshold: 0, - onChange: inView => { - if (!inView) { - return; - } - if (userInvitations.hasNextPage) { - userInvitations.fetchNext?.(); - } else { - userSuggestions.fetchNext?.(); - } - }, - }); - - return { - userInvitations, - userSuggestions, - ref, - }; -}; - -const AcceptRejectSuggestionButtons = (props: OrganizationSuggestionResource) => { - const card = useCardState(); - const { userSuggestions } = useOrganizationList({ - userSuggestions: organizationListParams.userSuggestions, - }); - - const handleAccept = () => { - return card - .runAsync(props.accept) - .then(updatedItem => userSuggestions?.setData?.(pages => populateCacheUpdateItem(updatedItem, pages))) - .catch(err => handleError(err, [], card.setError)); - }; - - if (props.status === 'accepted') { - return ( - - ); - } - - return ( - - ); - }), -); diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/__tests__/UserButton.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserButton/__tests__/UserButton.test.tsx deleted file mode 100644 index a0228519fa6..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/UserButton/__tests__/UserButton.test.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { describe } from '@jest/globals'; - -import { render } from '../../../../testUtils'; -import { bindCreateFixtures } from '../../../utils/test/createFixtures'; -import { UserButton } from '../UserButton'; - -const { createFixtures } = bindCreateFixtures('UserButton'); - -describe('UserButton', () => { - it('renders no button when there is no logged in user', async () => { - const { wrapper } = await createFixtures(); - const { queryByRole } = render(, { wrapper }); - expect(queryByRole('button')).toBeNull(); - }); - - it('renders button when there is a user', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({ email_addresses: ['test@clerk.com'] }); - }); - const { queryByRole } = render(, { wrapper }); - expect(queryByRole('button')).not.toBeNull(); - }); - - it('opens the user button popover when clicked', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({ - first_name: 'First', - last_name: 'Last', - username: 'username1', - email_addresses: ['test@clerk.com'], - }); - }); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - expect(getByText('Manage account')).not.toBeNull(); - }); - - it('opens user profile when "Manage account" is clicked', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({ - first_name: 'First', - last_name: 'Last', - username: 'username1', - email_addresses: ['test@clerk.com'], - }); - }); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - await userEvent.click(getByText('Manage account')); - expect(fixtures.clerk.openUserProfile).toHaveBeenCalled(); - }); - - it('signs out user when "Sign out" is clicked', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({ - first_name: 'First', - last_name: 'Last', - username: 'username1', - email_addresses: ['test@clerk.com'], - }); - }); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - await userEvent.click(getByText('Sign out')); - expect(fixtures.clerk.signOut).toHaveBeenCalled(); - }); - - it.todo('navigates to sign in url when "Add account" is clicked'); - - describe('Multi Session Popover', () => { - const initConfig = createFixtures.config(f => { - f.withMultiSessionMode(); - f.withUser({ - id: '1', - first_name: 'First1', - last_name: 'Last1', - username: 'username1', - email_addresses: ['test1@clerk.com'], - }); - f.withUser({ - id: '2', - first_name: 'First2', - last_name: 'Last2', - username: 'username2', - email_addresses: ['test2@clerk.com'], - }); - f.withUser({ - id: '3', - first_name: 'First3', - last_name: 'Last3', - username: 'username3', - email_addresses: ['test3@clerk.com'], - }); - }); - - it('renders all sessions', async () => { - const { wrapper } = await createFixtures(initConfig); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - expect(getByText('First1 Last1')).toBeDefined(); - expect(getByText('First2 Last2')).toBeDefined(); - expect(getByText('First3 Last3')).toBeDefined(); - }); - - it('changes the active session when clicking another session', async () => { - const { wrapper, fixtures } = await createFixtures(initConfig); - fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve()); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - await userEvent.click(getByText('First3 Last3')); - expect(fixtures.clerk.setActive).toHaveBeenCalledWith( - expect.objectContaining({ session: expect.objectContaining({ user: expect.objectContaining({ id: '3' }) }) }), - ); - }); - - it('signs out of the currently active session when clicking "Sign out"', async () => { - const { wrapper, fixtures } = await createFixtures(initConfig); - fixtures.clerk.signOut.mockReturnValueOnce(Promise.resolve()); - const { getByText, getByRole, userEvent } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: 'Open user button' })); - await userEvent.click(getByText('Sign out')); - expect(fixtures.clerk.signOut).toHaveBeenCalledWith(expect.any(Function), { sessionId: '0' }); - }); - }); - - describe('UserButtonTopLevelIdentifier', () => { - it('gives priority to showing first and last name next to the button over username and email', async () => { - const { wrapper, props } = await createFixtures(f => { - f.withUser({ - first_name: 'TestFirstName', - last_name: 'TestLastName', - username: 'username1', - email_addresses: ['test@clerk.com'], - }); - }); - props.setProps({ showName: true }); - const { getByText } = render(, { wrapper }); - expect(getByText('TestFirstName TestLastName')).toBeDefined(); - }); - - it('gives priority to showing username next to the button over email', async () => { - const { wrapper, props } = await createFixtures(f => { - f.withUser({ first_name: '', last_name: '', username: 'username1', email_addresses: ['test@clerk.com'] }); - }); - props.setProps({ showName: true }); - const { getByText } = render(, { wrapper }); - expect(getByText('username1')).toBeDefined(); - }); - - it('shows email next to the button if there is no username or first/last name', async () => { - const { wrapper, props } = await createFixtures(f => { - f.withUser({ first_name: '', last_name: '', username: '', email_addresses: ['test@clerk.com'] }); - }); - props.setProps({ showName: true }); - const { getByText } = render(, { wrapper }); - expect(getByText('test@clerk.com')).toBeDefined(); - }); - - it('does not show an identifier next to the button', async () => { - const { wrapper, props } = await createFixtures(f => { - f.withUser({ - first_name: 'TestFirstName', - last_name: 'TestLastName', - username: 'username1', - email_addresses: ['test@clerk.com'], - }); - }); - props.setProps({ showName: false }); - const { queryByText } = render(, { wrapper }); - expect(queryByText('TestFirstName TestLastName')).toBeNull(); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/index.ts b/packages/clerk-js/src/ui.retheme/components/UserButton/index.ts deleted file mode 100644 index ab6156ab876..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/UserButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './UserButton'; diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/useMultisessionActions.tsx b/packages/clerk-js/src/ui.retheme/components/UserButton/useMultisessionActions.tsx deleted file mode 100644 index e271bfd4d27..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/UserButton/useMultisessionActions.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useClerk, useSessionList } from '@clerk/shared/react'; -import type { ActiveSessionResource, UserButtonProps, UserResource } from '@clerk/types'; - -import { windowNavigate } from '../../../utils/windowNavigate'; -import { useCardState } from '../../elements'; -import { useRouter } from '../../router'; -import { sleep } from '../../utils'; - -type UseMultisessionActionsParams = { - user: UserResource | null | undefined; - actionCompleteCallback?: () => void; - navigateAfterSignOut?: () => any; - navigateAfterMultiSessionSingleSignOut?: () => any; - navigateAfterSwitchSession?: () => any; - userProfileUrl?: string; - signInUrl?: string; -} & Pick; - -export const useMultisessionActions = (opts: UseMultisessionActionsParams) => { - const { setActive, signOut, openUserProfile } = useClerk(); - const card = useCardState(); - const { sessions } = useSessionList(); - const { navigate } = useRouter(); - const activeSessions = sessions?.filter(s => s.status === 'active') as ActiveSessionResource[]; - const otherSessions = activeSessions.filter(s => s.user?.id !== opts.user?.id); - - const handleSignOutSessionClicked = (session: ActiveSessionResource) => () => { - if (otherSessions.length === 0) { - return signOut(opts.navigateAfterSignOut); - } - return signOut(opts.navigateAfterMultiSessionSingleSignOut, { sessionId: session.id }).finally(() => - card.setIdle(), - ); - }; - - const handleManageAccountClicked = () => { - if (opts.userProfileMode === 'navigation') { - return navigate(opts.userProfileUrl || '').finally(() => { - void (async () => { - await sleep(300); - opts.actionCompleteCallback?.(); - })(); - }); - } - - openUserProfile(opts.userProfileProps); - return opts.actionCompleteCallback?.(); - }; - - const handleSignOutAllClicked = () => { - return signOut(opts.navigateAfterSignOut); - }; - - const handleSessionClicked = (session: ActiveSessionResource) => async () => { - card.setLoading(); - return setActive({ session, beforeEmit: opts.navigateAfterSwitchSession }).finally(() => { - card.setIdle(); - opts.actionCompleteCallback?.(); - }); - }; - - const handleAddAccountClicked = () => { - windowNavigate(opts.signInUrl || window.location.href); - return sleep(2000); - }; - - return { - handleSignOutSessionClicked, - handleManageAccountClicked, - handleSignOutAllClicked, - handleSessionClicked, - handleAddAccountClicked, - otherSessions, - activeSessions, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/ActiveDevicesSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/ActiveDevicesSection.tsx deleted file mode 100644 index 2fda4613c58..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/UserProfile/ActiveDevicesSection.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useSession, useUser } from '@clerk/shared/react'; -import type { SessionWithActivitiesResource } from '@clerk/types'; -import React from 'react'; - -import { Badge, Col, descriptors, Flex, Icon, localizationKeys, Text, useLocalizations } from '../../customizables'; -import { FullHeightLoader, ProfileSection } from '../../elements'; -import { DeviceLaptop, DeviceMobile } from '../../icons'; -import { mqu } from '../../styledSystem'; -import { getRelativeToNowDateKey } from '../../utils'; -import { LinkButtonWithDescription } from './LinkButtonWithDescription'; -import { UserProfileAccordion } from './UserProfileAccordion'; -import { currentSessionFirst } from './utils'; - -export const ActiveDevicesSection = () => { - const { user } = useUser(); - const { session } = useSession(); - const [sessionsWithActivities, setSessionsWithActivities] = React.useState([]); - - React.useEffect(() => { - void user?.getSessions().then(sa => setSessionsWithActivities(sa)); - }, [user]); - - return ( - - {!sessionsWithActivities.length && } - {!!sessionsWithActivities.length && - sessionsWithActivities.sort(currentSessionFirst(session!.id)).map(sa => ( - - ))} - - ); -}; - -const DeviceAccordion = (props: { session: SessionWithActivitiesResource }) => { - const isCurrent = useSession().session?.id === props.session.id; - const revoke = async () => { - if (isCurrent || !props.session) { - return; - } - return props.session.revoke(); - }; - - return ( - } - > - - {isCurrent && ( - - )} - {!isCurrent && ( - - )} - - - ); -}; - -const DeviceInfo = (props: { session: SessionWithActivitiesResource }) => { - const { session } = useSession(); - const isCurrent = session?.id === props.session.id; - const isCurrentlyImpersonating = !!session?.actor; - const isImpersonationSession = !!props.session.actor; - const { city, country, browserName, browserVersion, deviceType, ipAddress, isMobile } = props.session.latestActivity; - const title = deviceType ? deviceType : isMobile ? 'Mobile device' : 'Desktop device'; - const browser = `${browserName || ''} ${browserVersion || ''}`.trim() || 'Web browser'; - const location = [city || '', country || ''].filter(Boolean).join(', ').trim() || null; - const { t } = useLocalizations(); - - return ( - ({ - gap: t.space.$8, - [mqu.xs]: { gap: t.space.$2 }, - })} - > - ({ - padding: `0 ${theme.space.$3}`, - [mqu.sm]: { padding: `0` }, - borderRadius: theme.radii.$md, - })} - > - ({ - '--cl-chassis-bottom': '#444444', - '--cl-chassis-back': '#343434', - '--cl-chassis-screen': '#575757', - '--cl-screen': '#000000', - width: theme.space.$20, - height: theme.space.$20, - [mqu.sm]: { - width: theme.space.$10, - height: theme.space.$10, - }, - })} - /> - - - - {title} - {isCurrent && ( - - )} - {isCurrentlyImpersonating && !isImpersonationSession && ( - - )} - {!isCurrent && isImpersonationSession && ( - - )} - - {browser} - - {ipAddress} ({location}) - - {t(getRelativeToNowDateKey(props.session.lastActiveAt))} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/AddAuthenticatorApp.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/AddAuthenticatorApp.tsx deleted file mode 100644 index e5bfbf9e501..00000000000 --- a/packages/clerk-js/src/ui.retheme/components/UserProfile/AddAuthenticatorApp.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useUser } from '@clerk/shared/react'; -import type { TOTPResource } from '@clerk/types'; -import React from 'react'; - -import { QRCode } from '../../common'; -import type { LocalizationKey } from '../../customizables'; -import { Button, Col, descriptors, localizationKeys, Text } from '../../customizables'; -import { - ClipboardInput, - ContentPage, - FormButtonContainer, - FullHeightLoader, - NavigateToFlowStartButton, - useCardState, -} from '../../elements'; -import { handleError } from '../../utils'; -import { UserProfileBreadcrumbs } from './UserProfileNavbar'; - -type AddAuthenticatorAppProps = { - title: LocalizationKey; - onContinue: () => void; -}; - -type DisplayFormat = 'qr' | 'uri'; - -export const AddAuthenticatorApp = (props: AddAuthenticatorAppProps) => { - const { title, onContinue } = props; - const { user } = useUser(); - const card = useCardState(); - const [totp, setTOTP] = React.useState(undefined); - const [displayFormat, setDisplayFormat] = React.useState('qr'); - - // TODO: React18 - // Non-idempotent useEffect - React.useEffect(() => { - void user - ?.createTOTP() - .then((totp: TOTPResource) => setTOTP(totp)) - .catch(err => handleError(err, [], card.setError)); - }, []); - - if (card.error) { - return ; - } - - return ( - - {!totp && } - - {totp && ( - <> - - {displayFormat == 'qr' && ( - <> - - - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Alert.tsx b/packages/clerk-js/src/ui.retheme/elements/Alert.tsx deleted file mode 100644 index 658f9ae8f9e..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Alert.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { Alert as AlertCust, AlertIcon, Col, descriptors, Text } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; -import { animations } from '../styledSystem'; - -type _AlertProps = { - variant?: 'danger' | 'warning'; - title?: LocalizationKey | string; - subtitle?: LocalizationKey | string; -}; - -type AlertProps = Omit, keyof _AlertProps> & _AlertProps; - -export const Alert = (props: AlertProps): JSX.Element | null => { - const { children, title, subtitle, variant = 'warning', ...rest } = props; - - if (!children && !title && !subtitle) { - return null; - } - - return ( - - - - - {children} - - {subtitle && ( - - )} - - - ); -}; - -export const CardAlert = React.memo((props: AlertProps) => { - return ( - ({ - willChange: 'transform, opacity, height', - animation: `${animations.textInBig} ${theme.transitionDuration.$slow}`, - })} - {...props} - /> - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/ApplicationLogo.tsx b/packages/clerk-js/src/ui.retheme/elements/ApplicationLogo.tsx deleted file mode 100644 index 5106e3b4687..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ApplicationLogo.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; - -import { useEnvironment } from '../contexts'; -import { descriptors, Flex, Image, useAppearance } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; -import { RouterLink } from './RouterLink'; - -type WidthInRem = `${string}rem`; -const getContainerHeightForImageRatio = (imageRef: React.RefObject, remWidth: WidthInRem) => { - const baseFontSize = 16; - const base = Number.parseFloat(remWidth.replace('rem', '')) * baseFontSize; - if (!imageRef.current) { - return base; - } - const ratio = imageRef.current.naturalWidth / imageRef.current.naturalHeight; - let newHeight = `${base}px`; - if (ratio <= 1) { - // logo is taller than it is wide - newHeight = `${2 * base}px`; - } else if (ratio > 1 && ratio <= 2) { - // logo is up to 2x wider than it is tall - newHeight = `${(2 * base) / ratio}px`; - } - return newHeight; -}; - -type ApplicationLogoProps = PropsOfComponent; - -export const ApplicationLogo = (props: ApplicationLogoProps) => { - const imageRef = React.useRef(null); - const [loaded, setLoaded] = React.useState(false); - const { logoImageUrl, applicationName, homeUrl } = useEnvironment().displayConfig; - const { parsedLayout } = useAppearance(); - const imageSrc = parsedLayout.logoImageUrl || logoImageUrl; - const logoUrl = parsedLayout.logoLinkUrl || homeUrl; - - if (!imageSrc) { - return null; - } - - const image = ( - {applicationName} setLoaded(true)} - sx={{ - display: loaded ? 'inline-block' : 'none', - height: '100%', - }} - /> - ); - - return ( - ({ - height: getContainerHeightForImageRatio(imageRef, theme.sizes.$6), - objectFit: 'cover', - }), - props.sx, - ]} - > - {logoUrl ? {image} : image} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/ArrowBlockButton.tsx b/packages/clerk-js/src/ui.retheme/elements/ArrowBlockButton.tsx deleted file mode 100644 index 4f463fe93c5..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ArrowBlockButton.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { isValidElement } from 'react'; - -import type { Button, LocalizationKey } from '../customizables'; -import { Flex, Icon, SimpleButton, Spinner, Text } from '../customizables'; -import type { ElementDescriptor, ElementId } from '../customizables/elementDescriptors'; -import { ArrowRightIcon } from '../icons'; -import type { PropsOfComponent, ThemableCssProp } from '../styledSystem'; - -type ArrowBlockButtonProps = PropsOfComponent & { - rightIcon?: React.ComponentType; - rightIconSx?: ThemableCssProp; - leftIcon?: React.ComponentType | React.ReactElement; - leftIconSx?: ThemableCssProp; - leftIconElementDescriptor?: ElementDescriptor; - leftIconElementId?: ElementId; - badge?: React.ReactElement; - textElementDescriptor?: ElementDescriptor; - textElementId?: ElementId; - arrowElementDescriptor?: ElementDescriptor; - arrowElementId?: ElementId; - spinnerElementDescriptor?: ElementDescriptor; - spinnerElementId?: ElementId; - textLocalizationKey?: LocalizationKey; -}; - -export const ArrowBlockButton = (props: ArrowBlockButtonProps) => { - const { - rightIcon = ArrowRightIcon, - rightIconSx, - leftIcon, - leftIconSx, - leftIconElementId, - leftIconElementDescriptor, - isLoading, - children, - textElementDescriptor, - textElementId, - spinnerElementDescriptor, - spinnerElementId, - arrowElementDescriptor, - arrowElementId, - textLocalizationKey, - badge, - ...rest - } = props; - - const isIconElement = isValidElement(leftIcon); - - return ( - [ - { - gap: theme.space.$4, - position: 'relative', - justifyContent: 'flex-start', - borderColor: theme.colors.$blackAlpha200, - '--arrow-opacity': '0', - '--arrow-transform': `translateX(-${theme.space.$2});`, - '&:hover,&:focus ': { - '--arrow-opacity': '0.5', - '--arrow-transform': 'translateX(0px);', - }, - }, - props.sx, - ]} - > - {(isLoading || leftIcon) && ( - ({ flex: `0 0 ${theme.space.$5}` })} - > - {isLoading ? ( - - ) : !isIconElement && leftIcon ? ( - ({ - color: theme.colors.$blackAlpha600, - width: theme.sizes.$5, - position: 'absolute', - }), - leftIconSx, - ]} - /> - ) : ( - leftIcon - )} - - )} - - - {children} - - {badge} - - ({ - transition: 'all 100ms ease', - minWidth: theme.sizes.$4, - minHeight: theme.sizes.$4, - width: '1em', - height: '1em', - opacity: `var(--arrow-opacity)`, - transform: `var(--arrow-transform)`, - }), - rightIconSx, - ]} - /> - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Avatar.tsx b/packages/clerk-js/src/ui.retheme/elements/Avatar.tsx deleted file mode 100644 index 9a30961169c..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Avatar.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; - -import { Box, descriptors, Flex, Image, Text } from '../customizables'; -import type { ElementDescriptor } from '../customizables/elementDescriptors'; -import type { InternalTheme } from '../foundations'; -import type { PropsOfComponent } from '../styledSystem'; -import { common } from '../styledSystem'; - -type AvatarProps = PropsOfComponent & { - size?: (theme: InternalTheme) => string | number; - title?: string; - initials?: string; - imageUrl?: string | null; - imageFetchSize?: number; - rounded?: boolean; - boxElementDescriptor?: ElementDescriptor; - imageElementDescriptor?: ElementDescriptor; -}; - -export const Avatar = (props: AvatarProps) => { - const { - size = () => 26, - title, - initials, - imageUrl, - rounded = true, - imageFetchSize = 80, - sx, - boxElementDescriptor, - imageElementDescriptor, - } = props; - const [error, setError] = React.useState(false); - - const ImgOrFallback = - initials && (!imageUrl || error) ? ( - - ) : ( - {title} setError(true)} - size={imageFetchSize} - /> - ); - - // TODO: Revise size handling. Do we need to be this dynamic or should we use the theme instead? - return ( - ({ - flexShrink: 0, - borderRadius: rounded ? t.radii.$circle : t.radii.$md, - overflow: 'hidden', - width: size(t), - height: size(t), - backgroundColor: t.colors.$avatarBackground, - backgroundClip: 'padding-box', - position: 'relative', - boxShadow: 'var(--cl-shimmer-hover-shadow)', - transition: `box-shadow ${t.transitionDuration.$slower} ${t.transitionTiming.$easeOut}`, - }), - sx, - ]} - > - {ImgOrFallback} - - {/* /** - * This Box is the "shimmer" effect for the avatar. - * The ":after" selector is responsible for the border shimmer animation. - */} - ({ - overflow: 'hidden', - background: t.colors.$colorShimmer, - position: 'absolute', - width: '25%', - height: '100%', - transition: `all ${t.transitionDuration.$slower} ${t.transitionTiming.$easeOut}`, - transform: 'var(--cl-shimmer-hover-transform, skewX(-45deg) translateX(-300%))', - ':after': { - display: 'block', - boxSizing: 'border-box', - content: "''", - position: 'absolute', - width: '400%', - height: '100%', - transform: 'var(--cl-shimmer-hover-after-transform, skewX(45deg) translateX(75%))', - transition: `all ${t.transitionDuration.$slower} ${t.transitionTiming.$easeOut}`, - border: t.borders.$heavy, - borderColor: t.colors.$colorShimmer, - borderRadius: rounded ? t.radii.$circle : t.radii.$md, - }, - })} - /> - - ); -}; - -const InitialsAvatarFallback = (props: { initials: string }) => { - const initials = props.initials; - - return ( - - {initials} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/AvatarUploader.tsx b/packages/clerk-js/src/ui.retheme/elements/AvatarUploader.tsx deleted file mode 100644 index 4399dbefd6e..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/AvatarUploader.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { Button, Col, descriptors, Flex, localizationKeys, Text } from '../customizables'; -import { handleError } from '../utils'; -import { useCardState } from './contexts'; -import { FileDropArea } from './FileDropArea'; - -export type AvatarUploaderProps = { - title: LocalizationKey; - avatarPreview: React.ReactElement; - onAvatarChange: (file: File) => Promise; - onAvatarRemove?: (() => void) | null; - avatarPreviewPlaceholder?: React.ReactElement | null; -}; - -export const fileToBase64 = (file: File): Promise => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result as string); - reader.onerror = error => reject(error); - }); -}; - -export const AvatarUploader = (props: AvatarUploaderProps) => { - const [showUpload, setShowUpload] = React.useState(false); - const [objectUrl, setObjectUrl] = React.useState(); - const card = useCardState(); - - const { onAvatarChange, onAvatarRemove, title, avatarPreview, avatarPreviewPlaceholder, ...rest } = props; - - const toggle = () => { - setShowUpload(!showUpload); - }; - - const handleFileDrop = (file: File | null) => { - if (file === null) { - return setObjectUrl(''); - } - - void fileToBase64(file).then(setObjectUrl); - card.setLoading(); - return onAvatarChange(file) - .then(() => { - toggle(); - card.setIdle(); - }) - .catch(err => handleError(err, [], card.setError)); - }; - - const handleRemove = () => { - card.setLoading(); - handleFileDrop(null); - return onAvatarRemove?.(); - }; - - const previewElement = objectUrl - ? React.cloneElement(avatarPreview, { imageUrl: objectUrl }) - : avatarPreviewPlaceholder - ? React.cloneElement(avatarPreviewPlaceholder, { onClick: toggle }) - : avatarPreview; - - return ( - - - {previewElement} - - - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/CodeControl.tsx b/packages/clerk-js/src/ui.retheme/elements/CodeControl.tsx deleted file mode 100644 index faf76f48157..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/CodeControl.tsx +++ /dev/null @@ -1,341 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { PropsWithChildren } from 'react'; -import React, { useCallback } from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { descriptors, Flex, Input, Spinner } from '../customizables'; -import { useCardState } from '../elements/contexts'; -import { useLoadingStatus } from '../hooks'; -import type { PropsOfComponent } from '../styledSystem'; -import { common } from '../styledSystem'; -import type { FormControlState } from '../utils'; -import { handleError, sleep, useFormControl } from '../utils'; -import { TimerButton } from './TimerButton'; - -type UseCodeInputOptions = { - length?: number; -}; - -type onCodeEntryFinishedCallback = (code: string) => unknown; -type onCodeEntryFinished = (cb: onCodeEntryFinishedCallback) => void; - -type onCodeEntryFinishedActionCallback = ( - code: string, - resolve: (params?: R) => Promise, - reject: (err: unknown) => Promise, -) => void; - -type UseFieldOTP = (params: { - id?: 'code'; - onCodeEntryFinished: onCodeEntryFinishedActionCallback; - onResendCodeClicked?: React.MouseEventHandler; - onResolve?: (a?: R) => Promise | void; -}) => { - isLoading: boolean; - otpControl: ReturnType; - onResendCode: React.MouseEventHandler | undefined; - onFakeContinue: () => void; -}; - -export const useFieldOTP: UseFieldOTP = params => { - const card = useCardState(); - const { - id = 'code', - onCodeEntryFinished: paramsOnCodeEntryFinished, - onResendCodeClicked: paramsOnResendCodeClicked, - onResolve: paramsOnResolve, - } = params; - const codeControlState = useFormControl(id, ''); - const codeControl = useCodeControl(codeControlState); - const status = useLoadingStatus(); - - const resolve = async (param: any) => { - // TODO: Localize this - codeControlState.setSuccess('success'); - await sleep(750); - await paramsOnResolve?.(param); - }; - - const reject = async (err: any) => { - handleError(err, [codeControlState], card.setError); - status.setIdle(); - await sleep(750); - codeControl.reset(); - }; - - codeControl.onCodeEntryFinished(code => { - status.setLoading(); - codeControlState.setError(undefined); - paramsOnCodeEntryFinished(code, resolve, reject); - }); - - const onFakeContinue = () => { - codeControlState.setError(undefined); - paramsOnCodeEntryFinished('', resolve, reject); - }; - - const onResendCode = useCallback>( - e => { - codeControl.reset(); - paramsOnResendCodeClicked?.(e); - }, - [codeControl, paramsOnResendCodeClicked], - ); - - return { - isLoading: status.isLoading, - otpControl: codeControl, - onResendCode: paramsOnResendCodeClicked ? onResendCode : undefined, - onFakeContinue, - }; -}; - -const useCodeControl = (formControl: FormControlState, options?: UseCodeInputOptions) => { - const otpControlRef = React.useRef(); - const userOnCodeEnteredCallback = React.useRef(); - const defaultValue = formControl.value; - const { feedback, feedbackType, onChange, clearFeedback } = formControl; - const { length = 6 } = options || {}; - const [values, setValues] = React.useState(() => - defaultValue ? defaultValue.split('').slice(0, length) : Array.from({ length }, () => ''), - ); - - const onCodeEntryFinished: onCodeEntryFinished = cb => { - userOnCodeEnteredCallback.current = cb; - }; - - React.useEffect(() => { - const len = values.filter(c => c).length; - if (len === length) { - const code = values.map(c => c || ' ').join(''); - userOnCodeEnteredCallback.current?.(code); - } else { - const code = values.join(''); - onChange?.({ target: { value: code } } as any); - } - }, [values.toString()]); - - const otpInputProps = { length, values, setValues, feedback, feedbackType, clearFeedback, ref: otpControlRef }; - return { otpInputProps, onCodeEntryFinished, reset: () => otpControlRef.current?.reset() }; -}; - -export type OTPInputProps = { - label: string | LocalizationKey; - description: string | LocalizationKey; - resendButton?: LocalizationKey; - isLoading: boolean; - isDisabled?: boolean; - onResendCode?: React.MouseEventHandler; - otpControl: ReturnType['otpControl']; -}; - -const [OTPInputContext, useOTPInputContext] = createContextAndHook('OTPInputContext'); - -export const OTPRoot = ({ children, ...props }: PropsWithChildren) => { - return {children}; -}; - -export const OTPResendButton = () => { - const { resendButton, onResendCode, isLoading, otpControl } = useOTPInputContext(); - - if (!onResendCode) { - return null; - } - - return ( - - ); -}; - -export const OTPCodeControl = React.forwardRef<{ reset: any }>((_, ref) => { - const [disabled, setDisabled] = React.useState(false); - const refs = React.useRef>([]); - const firstClickRef = React.useRef(false); - - const { otpControl, isLoading, isDisabled } = useOTPInputContext(); - const { feedback, values, setValues, feedbackType, length } = otpControl.otpInputProps; - - React.useImperativeHandle(ref, () => ({ - reset: () => { - setValues(values.map(() => '')); - setDisabled(false); - setTimeout(() => focusInputAt(0), 0); - }, - })); - - React.useLayoutEffect(() => { - setTimeout(() => focusInputAt(0), 0); - }, []); - - React.useEffect(() => { - if (feedback) { - setDisabled(true); - } - }, [feedback]); - - const handleMultipleCharValue = ({ eventValue, inputPosition }: { eventValue: string; inputPosition: number }) => { - const eventValues = (eventValue || '').split(''); - - if (eventValues.length === 0 || !eventValues.every(c => isValidInput(c))) { - return; - } - - if (eventValues.length === length) { - setValues([...eventValues]); - focusInputAt(length - 1); - return; - } - - const mergedValues = values.map((value, i) => - i < inputPosition ? value : eventValues[i - inputPosition] || value, - ); - setValues(mergedValues); - focusInputAt(inputPosition + eventValues.length); - }; - - const changeValueAt = (index: number, newValue: string) => { - const newValues = [...values]; - newValues[index] = newValue; - setValues(newValues); - }; - - const focusInputAt = (index: number) => { - const clampedIndex = Math.min(Math.max(0, index), refs.current.length - 1); - const ref = refs.current[clampedIndex]; - if (ref) { - ref.focus(); - values[clampedIndex] && ref.select(); - } - }; - - const handleOnClick = (index: number) => (e: React.MouseEvent) => { - e.preventDefault(); - // Focus on the first digit, when the first click happens. - // This is helpful especially for mobile (iOS) devices that cannot autofocus - // and user needs to manually tap the input area - if (!firstClickRef.current) { - focusInputAt(0); - firstClickRef.current = true; - return; - } - focusInputAt(index); - }; - - const handleOnChange = (index: number) => (e: React.ChangeEvent) => { - e.preventDefault(); - handleMultipleCharValue({ eventValue: e.target.value || '', inputPosition: index }); - }; - - const handleOnInput = (index: number) => (e: React.FormEvent) => { - e.preventDefault(); - if (isValidInput((e.target as any).value)) { - // If a user types on an input that already has a value and the new - // value is the same as the old one, onChange will not fire so we - // manually move focus to the next input - focusInputAt(index + 1); - } - }; - - const handleOnPaste = (index: number) => (e: React.ClipboardEvent) => { - e.preventDefault(); - handleMultipleCharValue({ eventValue: e.clipboardData.getData('text/plain') || '', inputPosition: index }); - }; - - const handleOnKeyDown = (index: number) => (e: React.KeyboardEvent) => { - switch (e.key) { - case 'Backspace': - e.preventDefault(); - changeValueAt(index, ''); - focusInputAt(index - 1); - return; - case 'ArrowLeft': - e.preventDefault(); - focusInputAt(index - 1); - return; - case 'ArrowRight': - e.preventDefault(); - focusInputAt(index + 1); - return; - case ' ': - e.preventDefault(); - return; - } - }; - - return ( - - {values.map((value, index: number) => ( - (refs.current[index] = node)} - autoFocus={index === 0 || undefined} - autoComplete='one-time-code' - aria-label={`${index === 0 ? 'Enter verification code. ' : ''} Digit ${index + 1}`} - isDisabled={isDisabled || isLoading || disabled || feedbackType === 'success'} - hasError={feedbackType === 'error'} - isSuccessfullyFilled={feedbackType === 'success'} - type='text' - inputMode='numeric' - name={`codeInput-${index}`} - /> - ))} - {isLoading && ( - ({ marginLeft: theme.space.$2 })} - elementDescriptor={descriptors.spinner} - /> - )} - - ); -}); - -const SingleCharInput = React.forwardRef< - HTMLInputElement, - PropsOfComponent & { isSuccessfullyFilled?: boolean } ->((props, ref) => { - const { isSuccessfullyFilled, ...rest } = props; - return ( - ({ - textAlign: 'center', - ...common.textVariants(theme).h2, - padding: `${theme.space.$0x5} 0`, - boxSizing: 'content-box', - height: theme.space.$10, - width: theme.space.$10, - borderRadius: theme.radii.$md, - border: 'none', - ...(isSuccessfullyFilled ? { borderColor: theme.colors.$success500 } : common.borderColor(theme, props)), - backgroundColor: 'unset', - '&:focus': {}, - })} - {...rest} - /> - ); -}); - -const isValidInput = (char: string) => char != undefined && Number.isInteger(+char); diff --git a/packages/clerk-js/src/ui.retheme/elements/ContentPage.tsx b/packages/clerk-js/src/ui.retheme/elements/ContentPage.tsx deleted file mode 100644 index b55831f4628..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ContentPage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { Col, descriptors } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; -import { CardAlert, Header, NavbarMenuButtonRow, useCardState } from './index'; - -type PageProps = PropsOfComponent & { - headerTitle: LocalizationKey | string; - headerTitleTextVariant?: PropsOfComponent['textVariant']; - breadcrumbTitle?: LocalizationKey; - Breadcrumbs?: React.ComponentType | null; - headerSubtitle?: LocalizationKey; - headerSubtitleTextVariant?: PropsOfComponent['variant']; -}; - -export const ContentPage = (props: PageProps) => { - const { - headerTitle, - headerTitleTextVariant, - headerSubtitle, - headerSubtitleTextVariant, - breadcrumbTitle, - children, - Breadcrumbs, - sx, - ...rest - } = props; - const card = useCardState(); - - return ( - ({ minHeight: t.sizes.$120 }), sx]} - > - - {card.error} - - {Breadcrumbs && ( - ({ marginBottom: t.space.$5 })} - /> - )} - - {headerSubtitle && ( - - )} - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Divider.tsx b/packages/clerk-js/src/ui.retheme/elements/Divider.tsx deleted file mode 100644 index 876c06d6549..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Divider.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { descriptors, Flex, localizationKeys, Text } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -export const Divider = (props: Omit, 'elementDescriptor'>) => { - return ( - - - ({ margin: `0 ${t.space.$4}` })} - /> - - - ); -}; - -const DividerLine = () => { - return ( - ({ - flex: '1', - height: '1px', - backgroundColor: t.colors.$blackAlpha200, - })} - /> - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/ErrorCard.tsx b/packages/clerk-js/src/ui.retheme/elements/ErrorCard.tsx deleted file mode 100644 index 6e9956239f1..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ErrorCard.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { descriptors, Flex, Flow, Icon, localizationKeys, Text } from '../customizables'; -import { useSupportEmail } from '../hooks/useSupportEmail'; -import { Email } from '../icons'; -import { CardAlert } from './Alert'; -import { ArrowBlockButton } from './ArrowBlockButton'; -import { Card } from './Card'; -import { useCardState } from './contexts'; -import { Footer } from './Footer'; -import { Header } from './Header'; - -type ErrorCardProps = { - cardTitle?: LocalizationKey; - cardSubtitle?: LocalizationKey; - message?: LocalizationKey; - onBackLinkClick?: React.MouseEventHandler | undefined; -}; - -export const ErrorCard = (props: ErrorCardProps) => { - const card = useCardState(); - const supportEmail = useSupportEmail(); - - const handleEmailSupport = () => { - window.location.href = `mailto:${supportEmail}`; - }; - - return ( - - - {card.error} - - - {props.cardSubtitle && } - - {/*TODO: extract main in its own component */} - - {props.message && ( - - )} - {/*TODO: extract */} - - ({ color: theme.colors.$blackAlpha500 })} - /> - } - /> - - - - - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/FieldControl.tsx b/packages/clerk-js/src/ui.retheme/elements/FieldControl.tsx deleted file mode 100644 index aa380cc0bdd..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/FieldControl.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import type { FieldId } from '@clerk/types'; -import type { PropsWithChildren } from 'react'; -import React, { forwardRef } from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { - descriptors, - Flex, - FormLabel, - Icon as IconCustomizable, - Input, - Link, - localizationKeys, - Text, - useLocalizations, -} from '../customizables'; -import { FormFieldContextProvider, sanitizeInputProps, useFormField } from '../primitives/hooks'; -import type { PropsOfComponent } from '../styledSystem'; -import type { useFormControl as useFormControlUtil } from '../utils'; -import { OTPCodeControl, OTPResendButton, OTPRoot } from './CodeControl'; -import { useCardState } from './contexts'; -import type { FormFeedbackProps } from './FormControl'; -import { FormFeedback } from './FormControl'; -import { InputGroup } from './InputGroup'; -import { PasswordInput } from './PasswordInput'; -import { PhoneInput } from './PhoneInput'; -import { RadioItem, RadioLabel } from './RadioGroup'; - -type FormControlProps = Omit, 'label' | 'placeholder' | 'disabled' | 'required'> & - ReturnType>['props']; - -const Root = (props: PropsWithChildren) => { - const card = useCardState(); - const { children, isDisabled: isDisabledProp, ...restProps } = props; - - const isDisabled = isDisabledProp || card.isLoading; - - const ctxProps = { - ...restProps, - isDisabled, - }; - - return {children}; -}; - -const FieldAction = ( - props: PropsWithChildren<{ localizationKey?: LocalizationKey | string; onClick?: React.MouseEventHandler }>, -) => { - const { fieldId, isDisabled } = useFormField(); - - if (!props.localizationKey && !props.children) { - return null; - } - - return ( - { - e.preventDefault(); - props.onClick?.(e); - }} - > - {props.children} - - ); -}; - -const FieldOptionalLabel = () => { - const { fieldId, isDisabled } = useFormField(); - return ( - - ); -}; - -const FieldLabelIcon = (props: { icon?: React.ComponentType }) => { - const { t } = useLocalizations(); - if (!props.icon) { - return null; - } - - return ( - - ({ - marginLeft: theme.space.$0x5, - color: theme.colors.$blackAlpha400, - width: theme.sizes.$4, - height: theme.sizes.$4, - })} - /> - - ); -}; - -const FieldLabel = (props: PropsWithChildren<{ localizationKey?: LocalizationKey | string }>) => { - const { isRequired, id, label, isDisabled, hasError } = useFormField(); - - if (!(props.localizationKey || label) && !props.children) { - return null; - } - - return ( - - {props.children} - - ); -}; - -const FieldLabelRow = (props: PropsWithChildren) => { - const { fieldId } = useFormField(); - return ( - ({ - marginBottom: theme.space.$1, - marginLeft: 0, - })} - > - {props.children} - - ); -}; - -const FieldFeedback = (props: Pick) => { - const { fieldId, debouncedFeedback } = useFormField(); - - return ( - - ); -}; - -const PhoneInputElement = forwardRef((_, ref) => { - const { t } = useLocalizations(); - const formField = useFormField(); - const { placeholder, ...inputProps } = sanitizeInputProps(formField); - - return ( - - ); -}); - -const PasswordInputElement = forwardRef((_, ref) => { - const { t } = useLocalizations(); - const formField = useFormField(); - const { placeholder, ...inputProps } = sanitizeInputProps(formField, [ - 'validatePassword', - 'setError', - 'setWarning', - 'setSuccess', - 'setInfo', - 'setHasPassedComplexity', - ]); - - return ( - // @ts-expect-error Typescript is complaining that `setError`, `setWarning` and the rest of feedback setters are not passed. We are clearly passing them from above. - - ); -}); - -const CheckboxIndicator = forwardRef((_, ref) => { - const formField = useFormField(); - const { placeholder, ...inputProps } = sanitizeInputProps(formField); - - return ( - ({ - width: 'fit-content', - marginTop: t.space.$0x5, - })} - type='checkbox' - /> - ); -}); - -const CheckboxLabel = (props: { description?: string | LocalizationKey }) => { - const { label, id } = useFormField(); - - if (!label) { - return null; - } - - return ( - - ); -}; - -const InputElement = forwardRef((_, ref) => { - const { t } = useLocalizations(); - const formField = useFormField(); - const { placeholder, ...inputProps } = sanitizeInputProps(formField); - return ( - - ); -}); - -const InputGroupElement = forwardRef< - HTMLInputElement, - { - groupPrefix?: string; - groupSuffix?: string; - } ->((props, ref) => { - const { t } = useLocalizations(); - const formField = useFormField(); - const { placeholder, ...inputProps } = sanitizeInputProps(formField); - - return ( - - ); -}); - -export const Field = { - Root: Root, - Label: FieldLabel, - LabelRow: FieldLabelRow, - Input: InputElement, - PasswordInput: PasswordInputElement, - PhoneInput: PhoneInputElement, - InputGroup: InputGroupElement, - RadioItem: RadioItem, - CheckboxIndicator: CheckboxIndicator, - CheckboxLabel: CheckboxLabel, - Action: FieldAction, - AsOptional: FieldOptionalLabel, - LabelIcon: FieldLabelIcon, - Feedback: FieldFeedback, - OTPRoot, - OTPCodeControl, - OTPResendButton, -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/FileDropArea.tsx b/packages/clerk-js/src/ui.retheme/elements/FileDropArea.tsx deleted file mode 100644 index 0056a8cb518..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/FileDropArea.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; - -import { Button, Col, descriptors, localizationKeys, Text } from '../customizables'; -import { Folder } from '../icons'; -import { animations, mqu } from '../styledSystem'; -import { colors } from '../utils'; -import { IconCircle } from './IconCircle'; - -const MAX_SIZE_BYTES = 10 * 1000 * 1000; -const SUPPORTED_MIME_TYPES = Object.freeze(['image/png', 'image/jpeg', 'image/gif', 'image/webp']); - -const validType = (f: File | DataTransferItem) => SUPPORTED_MIME_TYPES.includes(f.type); -const validSize = (f: File) => f.size <= MAX_SIZE_BYTES; -const validFile = (f: File) => validType(f) && validSize(f); - -type FileDropAreaProps = { - onFileDrop: (file: File) => any; -}; - -export const FileDropArea = (props: FileDropAreaProps) => { - const { onFileDrop } = props; - const [status, setStatus] = React.useState<'idle' | 'valid' | 'invalid' | 'loading'>('idle'); - const inputRef = React.useRef(null); - const openDialog = () => inputRef.current?.click(); - - const onDragEnter = (ev: React.DragEvent) => { - const item = ev.dataTransfer.items[0]; - setStatus(item && !validType(item) ? 'invalid' : 'valid'); - }; - - const onDragLeave = (ev: React.DragEvent) => { - if (!ev.currentTarget.contains(ev.relatedTarget as Element)) { - setStatus('idle'); - } - }; - - const onDragOver = (ev: React.DragEvent) => { - ev.preventDefault(); - }; - - const onDrop = (ev: React.DragEvent) => { - ev.preventDefault(); - onDragLeave(ev); - upload(ev.dataTransfer.files[0]); - }; - - const upload = (f: File | undefined) => { - if (f && validFile(f)) { - setStatus('loading'); - onFileDrop(f); - } - }; - - const events = { onDragEnter, onDragLeave, onDragOver, onDrop }; - - return ( - - upload(e.currentTarget.files?.[0])} - /> - ({ - height: t.space.$60, - [mqu.sm]: { height: '10 rem' }, - backgroundColor: { - idle: t.colors.$blackAlpha50, - loading: t.colors.$blackAlpha50, - valid: colors.setAlpha(t.colors.$success500, 0.2), - invalid: colors.setAlpha(t.colors.$danger500, 0.2), - }[status], - borderRadius: t.radii.$xl, - animation: `${animations.expandIn(t.space.$60)} ${t.transitionDuration.$fast} ease`, - })} - > - - {status === 'loading' ? ( - Uploading... - ) : ( - <> - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/IconCircle.tsx b/packages/clerk-js/src/ui.retheme/elements/IconCircle.tsx deleted file mode 100644 index 2a4ad8a91a1..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/IconCircle.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Flex, Icon } from '../customizables'; -import type { ElementDescriptor } from '../customizables/elementDescriptors'; -import type { PropsOfComponent } from '../styledSystem'; - -type IconCircleProps = Pick, 'icon'> & - PropsOfComponent & { - boxElementDescriptor?: ElementDescriptor; - iconElementDescriptor?: ElementDescriptor; - }; - -export const IconCircle = (props: IconCircleProps) => { - const { icon, boxElementDescriptor, iconElementDescriptor, sx, ...rest } = props; - - return ( - ({ - backgroundColor: t.colors.$blackAlpha50, - width: t.sizes.$16, - height: t.sizes.$16, - borderRadius: t.radii.$circle, - }), - sx, - ]} - {...rest} - > - ({ color: theme.colors.$blackAlpha600 })} - /> - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/IdentityPreview.tsx b/packages/clerk-js/src/ui.retheme/elements/IdentityPreview.tsx deleted file mode 100644 index 80d51268599..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/IdentityPreview.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; - -import { Button, descriptors, Flex, Icon, Text } from '../customizables'; -import { AuthApp, PencilEdit } from '../icons'; -import type { PropsOfComponent } from '../styledSystem'; -import { formatSafeIdentifier, getFlagEmojiFromCountryIso, isMaskedIdentifier, parsePhoneString } from '../utils'; - -type IdentityPreviewProps = { - avatarUrl: string | null | undefined; - identifier: string | null | undefined; - onClick?: React.MouseEventHandler; -} & PropsOfComponent; - -export const IdentityPreview = (props: IdentityPreviewProps) => { - const { avatarUrl = 'https://img.clerk.com/static/avatar_placeholder.jpeg', identifier, onClick, ...rest } = props; - const refs = React.useRef({ avatarUrl, identifier: formatSafeIdentifier(identifier) }); - - const edit = onClick && ( - - ); - - if (!refs.current.identifier) { - return ( - - - {edit} - - ); - } - - if (isMaskedIdentifier(refs.current.identifier) || !refs.current.identifier.startsWith('+')) { - return ( - - - {edit} - - ); - } - - const parsedPhone = parsePhoneString(refs.current.identifier || ''); - const flag = getFlagEmojiFromCountryIso(parsedPhone.iso); - return ( - - - {edit} - - ); -}; - -const IdentifierContainer = (props: React.PropsWithChildren) => { - return ( - - ); -}; - -const UsernameOrEmailIdentifier = (props: any) => { - return {props.identifier}; -}; - -const PhoneIdentifier = (props: { identifier: string; flag?: string }) => { - return ( - <> - ({ fontSize: t.fontSizes.$sm })}>{props.flag} - {props.identifier} - - ); -}; - -const Authenticator = () => { - return ( - <> - ({ color: t.colors.$blackAlpha700 })} - /> - Authenticator app - - ); -}; - -const Container = (props: React.PropsWithChildren) => { - return ( - ({ - margin: `${t.space.$none} auto`, - })} - {...props} - /> - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/InformationBox.tsx b/packages/clerk-js/src/ui.retheme/elements/InformationBox.tsx deleted file mode 100644 index 6d3d2fe02c6..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/InformationBox.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { LocalizationKey } from '../customizables'; -import { Flex, Icon, Text } from '../customizables'; -import { InformationCircle } from '../icons'; - -type InformationBoxProps = { - message: LocalizationKey | string; -}; - -export function InformationBox(props: InformationBoxProps) { - return ( - ({ - gap: t.space.$2, - padding: `${t.space.$3} ${t.space.$4}`, - backgroundColor: t.colors.$blackAlpha50, - borderRadius: t.radii.$md, - })} - > - ({ opacity: t.opacity.$disabled })} - /> - ({ color: t.colors.$blackAlpha700 })} - /> - - ); -} diff --git a/packages/clerk-js/src/ui.retheme/elements/InputGroup.tsx b/packages/clerk-js/src/ui.retheme/elements/InputGroup.tsx deleted file mode 100644 index d4645205df5..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/InputGroup.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { forwardRef } from 'react'; - -import { descriptors, Flex, Input, Text } from '../customizables'; -import type { PropsOfComponent, ThemableCssProp } from '../styledSystem'; - -type InputGroupProps = PropsOfComponent; - -export const InputGroup = forwardRef< - HTMLInputElement, - InputGroupProps & { - groupPrefix?: string; - groupSuffix?: string; - } ->((props, ref) => { - const { sx, groupPrefix, groupSuffix, ...rest } = props; - - const inputBorder = groupPrefix - ? { - borderTopLeftRadius: '0', - borderBottomLeftRadius: '0', - } - : { - borderTopRightRadius: '0', - borderBottomRightRadius: '0', - }; - - const textProps: ThemableCssProp = t => ({ - paddingInline: t.space.$2, - backgroundColor: t.colors.$blackAlpha50, - borderTopRightRadius: '0', - borderBottomRightRadius: '0', - width: 'fit-content', - display: 'flex', - alignItems: 'center', - }); - - return ( - ({ - position: 'relative', - borderRadius: theme.radii.$md, - zIndex: 1, - border: theme.borders.$normal, - borderColor: theme.colors.$blackAlpha300, // we use this value in the Input primitive - })} - > - {groupPrefix && {groupPrefix}} - - {groupSuffix && ( - - {groupSuffix} - - )} - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/InputWithIcon.tsx b/packages/clerk-js/src/ui.retheme/elements/InputWithIcon.tsx deleted file mode 100644 index a8f7dea8759..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/InputWithIcon.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import { Flex, Input } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -type InputWithIcon = PropsOfComponent & { leftIcon?: React.ReactElement }; - -export const InputWithIcon = React.forwardRef((props, ref) => { - const { leftIcon, sx, ...rest } = props; - return ( - ({ - width: '100%', - position: 'relative', - '& .cl-internal-icon': { - position: 'absolute', - left: theme.space.$4, - width: theme.sizes.$3x5, - height: theme.sizes.$3x5, - }, - })} - > - {leftIcon && React.cloneElement(leftIcon, { className: 'cl-internal-icon' })} - ({ - width: '100%', - paddingLeft: theme.space.$10, - }), - sx, - ]} - ref={ref} - /> - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/InvisibleRootBox.tsx b/packages/clerk-js/src/ui.retheme/elements/InvisibleRootBox.tsx deleted file mode 100644 index de51ac4b71c..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/InvisibleRootBox.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -import { makeCustomizable } from '../customizables/makeCustomizable'; - -type RootBoxProps = React.PropsWithChildren<{ className: string }>; - -const _InvisibleRootBox = React.memo((props: RootBoxProps) => { - const [showSpan, setShowSpan] = React.useState(true); - const parentRef = React.useRef(null); - - React.useEffect(() => { - const parent = parentRef.current; - if (!parent) { - return; - } - if (showSpan) { - setShowSpan(false); - } - parent.className = props.className; - }, [props.className]); - - return ( - <> - {props.children} - {showSpan && ( - (parentRef.current = el ? el.parentElement : parentRef.current)} - aria-hidden - style={{ display: 'none' }} - /> - )} - - ); -}); - -export const InvisibleRootBox = makeCustomizable(_InvisibleRootBox, { - defaultStyles: t => ({ - boxSizing: 'border-box', - width: 'fit-content', - fontFamily: t.fonts.$main, - fontStyle: t.fontStyles.$normal, - }), -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/LoadingCard.tsx b/packages/clerk-js/src/ui.retheme/elements/LoadingCard.tsx deleted file mode 100644 index 7544baddab1..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/LoadingCard.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -import { descriptors, Flex, Spinner } from '../customizables'; -import { CardAlert } from './Alert'; -import { Card } from './Card'; -import { useCardState, withCardStateProvider } from './contexts'; - -export const LoadingCardContainer = ({ children }: PropsWithChildren) => { - return ( - ({ - marginTop: theme.space.$16, - marginBottom: theme.space.$13, - })} - > - - {children} - - ); -}; - -export const LoadingCard = withCardStateProvider(() => { - const card = useCardState(); - return ( - - {card.error} - - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/Menu.tsx b/packages/clerk-js/src/ui.retheme/elements/Menu.tsx deleted file mode 100644 index d590028433b..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Menu.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { MenuId } from '@clerk/types'; -import type { PropsWithChildren } from 'react'; -import React, { cloneElement, isValidElement, useLayoutEffect, useRef } from 'react'; - -import { Button, Col, descriptors } from '../customizables'; -import type { UsePopoverReturn } from '../hooks'; -import { usePopover } from '../hooks'; -import type { PropsOfComponent } from '../styledSystem'; -import { animations } from '../styledSystem'; -import { colors } from '../utils/colors'; -import { withFloatingTree } from './contexts'; -import { Popover } from './Popover'; - -type MenuState = { - popoverCtx: UsePopoverReturn; - elementId?: MenuId; -}; - -const [MenuStateCtx, useMenuState] = createContextAndHook('MenuState'); - -type MenuProps = PropsWithChildren> & { elementId?: MenuId }; - -export const Menu = withFloatingTree((props: MenuProps) => { - const { elementId } = props; - const popoverCtx = usePopover({ - placement: 'right-start', - offset: 8, - bubbles: false, - }); - - const value = React.useMemo(() => ({ value: { popoverCtx, elementId } }), [{ ...popoverCtx }, elementId]); - - return ( - - ); -}); - -type MenuTriggerProps = React.PropsWithChildren>; - -export const MenuTrigger = (props: MenuTriggerProps) => { - const { children } = props; - const { popoverCtx, elementId } = useMenuState(); - const { reference, toggle } = popoverCtx; - - if (!isValidElement(children)) { - return null; - } - - return cloneElement(children, { - // @ts-expect-error - ref: reference, - elementDescriptor: descriptors.menuButton, - elementId: descriptors.menuButton.setId(elementId), - onClick: (e: React.MouseEvent) => { - children.props?.onClick?.(e); - toggle(); - }, - }); -}; - -const findMenuItem = (el: Element, siblingType: 'prev' | 'next', options?: { countSelf?: boolean }) => { - let tagName = options?.countSelf ? el.tagName : ''; - let sibling: Element | null = el; - while (sibling && tagName.toUpperCase() !== 'BUTTON') { - sibling = sibling[siblingType === 'prev' ? 'previousElementSibling' : 'nextElementSibling']; - tagName = sibling?.tagName ?? ''; - } - return sibling; -}; - -type MenuListProps = PropsOfComponent; - -export const MenuList = (props: MenuListProps) => { - const { sx, ...rest } = props; - const { popoverCtx, elementId } = useMenuState(); - const { floating, styles, isOpen, context, nodeId } = popoverCtx; - const containerRef = useRef(null); - - useLayoutEffect(() => { - const current = containerRef.current; - floating(current); - }, [isOpen]); - - const onKeyDown = (e: React.KeyboardEvent) => { - const current = containerRef.current; - if (!current) { - return; - } - - if (current !== document.activeElement) { - return; - } - - if (e.key === 'ArrowDown') { - e.preventDefault(); - return (findMenuItem(current.children[0], 'next', { countSelf: true }) as HTMLElement)?.focus(); - } - }; - - return ( - - ({ - backgroundColor: colors.makeSolid(theme.colors.$colorBackground), - border: theme.borders.$normal, - outline: 'none', - borderRadius: theme.radii.$lg, - borderColor: theme.colors.$blackAlpha200, - paddingTop: theme.space.$2, - paddingBottom: theme.space.$2, - overflow: 'hidden', - top: `calc(100% + ${theme.space.$2})`, - animation: `${animations.dropdownSlideInScaleAndFade} ${theme.transitionDuration.$slower} ${theme.transitionTiming.$slowBezier}`, - transformOrigin: 'top center', - boxShadow: theme.shadows.$menuShadow, - zIndex: theme.zIndices.$dropdown, - }), - sx, - ]} - style={styles} - {...rest} - /> - - ); -}; - -type MenuItemProps = PropsOfComponent & { - destructive?: boolean; -}; - -export const MenuItem = (props: MenuItemProps) => { - const { sx, onClick, destructive, ...rest } = props; - const { popoverCtx, elementId } = useMenuState(); - const { toggle } = popoverCtx; - const buttonRef = useRef(null); - - const onKeyDown = (e: React.KeyboardEvent) => { - const current = buttonRef.current; - if (!current) { - return; - } - - const key = e.key; - if (key !== 'ArrowUp' && key !== 'ArrowDown') { - return; - } - - e.preventDefault(); - const sibling = findMenuItem(current, key === 'ArrowUp' ? 'prev' : 'next'); - (sibling as HTMLElement)?.focus(); - }; - - return ( - - ); -}; - -export const NavbarMenuButtonRow = (props: PropsOfComponent) => { - const { open } = useUnsafeNavbarContext(); - const { t } = useLocalizations(); - - const navbarContextExistsInTree = !!open; - if (!navbarContextExistsInTree) { - return null; - } - - return ( - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/NavigateToFlowStartButton.tsx b/packages/clerk-js/src/ui.retheme/elements/NavigateToFlowStartButton.tsx deleted file mode 100644 index 3bd0fa85845..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/NavigateToFlowStartButton.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Button } from '../customizables'; -import { useNavigateToFlowStart } from '../hooks'; -import type { PropsOfComponent } from '../styledSystem'; - -type NavigateToFlowStartButtonProps = PropsOfComponent; - -export const NavigateToFlowStartButton = (props: NavigateToFlowStartButtonProps) => { - const { navigateToFlowStart } = useNavigateToFlowStart(); - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/ProfileCardContent.tsx b/packages/clerk-js/src/ui.retheme/elements/ProfileCardContent.tsx deleted file mode 100644 index 899cc058cc1..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ProfileCardContent.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; - -import { Col, descriptors } from '../customizables'; -import { useRouter } from '../router'; -import { common, mqu } from '../styledSystem'; - -type ProfileCardContentProps = React.PropsWithChildren<{ contentRef?: React.RefObject }>; - -export const ProfileCardContent = (props: ProfileCardContentProps) => { - const { contentRef, children } = props; - const router = useRouter(); - const scrollPosRef = React.useRef(0); - - React.useEffect(() => { - const handleScroll = (e: Event) => { - const target = e.target as HTMLDivElement; - if (target.scrollTop) { - scrollPosRef.current = target.scrollTop; - } - }; - contentRef?.current?.addEventListener('scroll', handleScroll); - return () => contentRef?.current?.removeEventListener('scroll', handleScroll); - }, []); - - React.useLayoutEffect(() => { - if (scrollPosRef.current && contentRef?.current) { - contentRef.current.scrollTop = scrollPosRef.current; - } - }, [router.currentPath]); - - return ( - ({ - backgroundColor: t.colors.$colorBackground, - position: 'relative', - borderRadius: t.radii.$lg, - width: '100%', - overflowY: 'auto', - boxShadow: t.shadows.$cardShadow, - })} - > - ({ - flex: `1`, - padding: `${theme.space.$10} ${theme.space.$8}`, - [mqu.xs]: { - padding: `${theme.space.$8} ${theme.space.$5}`, - }, - ...common.maxHeightScroller(theme), - })} - ref={contentRef} - > - {children} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/RadioGroup.tsx b/packages/clerk-js/src/ui.retheme/elements/RadioGroup.tsx deleted file mode 100644 index 5ff73be87e8..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/RadioGroup.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { forwardRef, useId } from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { descriptors, Flex, FormLabel, Input, Text } from '../customizables'; -import { sanitizeInputProps, useFormField } from '../primitives/hooks'; - -const RadioIndicator = forwardRef((props, ref) => { - const formField = useFormField(); - const { value, placeholder, ...inputProps } = sanitizeInputProps(formField); - - return ( - ({ - width: 'fit-content', - marginTop: t.space.$0x5, - })} - type='radio' - value={props.value} - checked={props.value === value} - /> - ); -}); - -export const RadioLabel = (props: { - label: string | LocalizationKey; - description?: string | LocalizationKey; - id?: string; -}) => { - return ( - ({ - padding: `${t.space.$none} ${t.space.$2}`, - display: 'flex', - flexDirection: 'column', - })} - > - - - {props.description && ( - - )} - - ); -}; - -export const RadioItem = forwardRef< - HTMLInputElement, - { - value: string; - label: string | LocalizationKey; - description?: string | LocalizationKey; - } ->((props, ref) => { - const randomId = useId(); - return ( - - - - - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/ReversibleContainer.tsx b/packages/clerk-js/src/ui.retheme/elements/ReversibleContainer.tsx deleted file mode 100644 index 4908ed52575..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ReversibleContainer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; - -import { useAppearance } from '../customizables'; -import { Divider } from './Divider'; - -export const SocialButtonsReversibleContainerWithDivider = (props: React.PropsWithChildren) => { - const appearance = useAppearance(); - const childrenWithDivider = interleaveElementInArray(React.Children.toArray(props.children), i => ( - - )); - - return ( - - {childrenWithDivider} - - ); -}; - -export const ReversibleContainer = (props: React.PropsWithChildren<{ reverse?: boolean }>) => { - const { children, reverse } = props; - return <>{reverse ? React.Children.toArray(children).reverse() : children}; -}; - -const interleaveElementInArray = (arr: A, generator: (i: number) => any): A => { - return arr.reduce((acc, child, i) => { - return i === arr.length - 1 ? [...acc, child] : [...acc, child, generator(i)]; - }, [] as any); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/RootBox.tsx b/packages/clerk-js/src/ui.retheme/elements/RootBox.tsx deleted file mode 100644 index b076028e34b..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/RootBox.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Col } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -export const RootBox = (props: PropsOfComponent) => { - return ( - ({ - boxSizing: 'border-box', - width: 'fit-content', - color: t.colors.$colorText, - fontFamily: t.fonts.$main, - fontStyle: t.fontStyles.$normal, - })} - /> - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/RouterLink.tsx b/packages/clerk-js/src/ui.retheme/elements/RouterLink.tsx deleted file mode 100644 index 8dab8dc2516..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/RouterLink.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Link } from '../customizables'; -import { useRouter } from '../router'; -import type { PropsOfComponent } from '../styledSystem'; - -type RouterLinkProps = PropsOfComponent & { - to?: string; -}; - -export const RouterLink = (props: RouterLinkProps) => { - const { to, onClick: onClickProp, ...rest } = props; - const router = useRouter(); - - const toUrl = router.resolve(to || router.indexPath); - - const onClick: React.MouseEventHandler = e => { - e.preventDefault(); - if (onClickProp && !to) { - return onClickProp(e); - } - return router.navigate(toUrl.href); - }; - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Section.tsx b/packages/clerk-js/src/ui.retheme/elements/Section.tsx deleted file mode 100644 index b5f8a226fad..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Section.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import type { ProfileSectionId } from '@clerk/types'; - -import type { LocalizationKey } from '../customizables'; -import { Col, descriptors, Flex, Text } from '../customizables'; -import type { ElementDescriptor, ElementId } from '../customizables/elementDescriptors'; -import type { PropsOfComponent } from '../styledSystem'; - -type ProfileSectionProps = Omit, 'title'> & { - title: LocalizationKey; - subtitle?: LocalizationKey; - id: ProfileSectionId; -}; - -export const ProfileSection = (props: ProfileSectionProps) => { - const { title, children, id, subtitle, sx, ...rest } = props; - return ( - ({ - borderTop: `${t.borders.$normal} ${t.colors.$blackAlpha100}`, - padding: `${t.space.$4} 0`, - gap: t.space.$4, - }), - sx, - ]} - {...rest} - > - - - {subtitle && ( - - )} - - {children} - - - - ); -}; - -type SectionHeaderProps = PropsOfComponent & { - localizationKey: LocalizationKey; - textElementDescriptor?: ElementDescriptor; - textElementId?: ElementId; -}; - -export const SectionHeader = (props: SectionHeaderProps) => { - const { textElementDescriptor, textElementId, localizationKey, ...rest } = props; - return ( - - - - ); -}; -export const SectionSubHeader = (props: SectionHeaderProps) => { - const { textElementDescriptor, textElementId, localizationKey, ...rest } = props; - return ( - ({ padding: `${t.space.$2} ${t.space.$none}` })} - > - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Select.tsx b/packages/clerk-js/src/ui.retheme/elements/Select.tsx deleted file mode 100644 index a26437746c9..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Select.tsx +++ /dev/null @@ -1,402 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { SelectId } from '@clerk/types'; -import type { PropsWithChildren, ReactElement, ReactNode } from 'react'; -import React, { useState } from 'react'; - -import { Button, descriptors, Flex, Icon, Input, Text } from '../customizables'; -import { usePopover, useSearchInput } from '../hooks'; -import { ArrowUpDown } from '../icons'; -import type { PropsOfComponent, ThemableCssProp } from '../styledSystem'; -import { animations, common } from '../styledSystem'; -import { colors } from '../utils'; -import { withFloatingTree } from './contexts'; -import type { InputWithIcon } from './InputWithIcon'; -import { Popover } from './Popover'; - -type UsePopoverReturn = ReturnType; -type UseSearchInputReturn = ReturnType; - -type Option = { value: string | null; label?: string }; - -type RenderOption = (option: O, index?: number, isSelected?: boolean) => ReactNode; - -type SelectProps = { - options: O[]; - value: string | null; - onChange: (option: O) => void; - searchPlaceholder?: string; - placeholder?: string; - comparator?: (term: string, option: O) => boolean; - noResultsMessage?: string; - renderOption?: RenderOption; - elementId?: SelectId; -}; - -type SelectState = Pick< - SelectProps, - 'placeholder' | 'searchPlaceholder' | 'elementId' | 'value' | 'comparator' | 'noResultsMessage' -> & { - popoverCtx: UsePopoverReturn; - searchInputCtx: UseSearchInputReturn; - renderOption: RenderOption; - buttonRenderOption: RenderOption; - selectedOption: Option | null; - select: (option: O) => void; - focusedItemRef: React.RefObject; - onTriggerClick: () => void; -}; - -const [SelectStateCtx, useSelectState] = createContextAndHook>('SelectState'); - -const defaultRenderOption = (option: O, _index?: number, isFocused?: boolean) => { - return ( - ({ - width: '100%', - padding: `${theme.space.$2} ${theme.space.$4}`, - margin: `0 ${theme.space.$1}`, - borderRadius: theme.radii.$md, - ...(isFocused && { backgroundColor: theme.colors.$blackAlpha200 }), - '&:hover': { - backgroundColor: theme.colors.$blackAlpha200, - }, - })} - > - {option.label || option.value} - - ); -}; - -const defaultButtonRenderOption = (option: O) => { - return option.label || option.value; -}; - -export const Select = withFloatingTree((props: PropsWithChildren>) => { - const { - value, - options, - onChange, - renderOption, - noResultsMessage, - comparator, - placeholder = 'Select an option', - searchPlaceholder, - elementId, - children, - ...rest - } = props; - const popoverCtx = usePopover({ autoUpdate: false, bubbles: false }); - const togglePopover = popoverCtx.toggle; - const focusedItemRef = React.useRef(null); - const searchInputCtx = useSearchInput({ - items: options, - comparator: comparator || (() => true), - }); - - const select = React.useCallback( - (option: O) => { - onChange?.(option); - togglePopover(); - }, - [togglePopover, onChange], - ); - - const defaultChildren = ( - <> - - - - ); - - return ( - o.value === value) || null, - noResultsMessage, - focusedItemRef, - value, - renderOption: renderOption || defaultRenderOption, - buttonRenderOption: renderOption || defaultButtonRenderOption, - placeholder, - searchPlaceholder, - comparator, - select, - onTriggerClick: togglePopover, - elementId, - }, - }} - {...rest} - > - {React.Children.count(children) ? children : defaultChildren} - - ); -}) as (props: PropsWithChildren>) => ReactElement; - -type SelectrenderOptionProps = { - option: Option; - index: number; - renderOption: RenderOption; - handleSelect: (option: Option) => void; - isFocused?: boolean; - isSelected?: boolean; - elementId?: SelectId; -}; - -const SelectRenderOption = React.memo( - React.forwardRef((props: SelectrenderOptionProps, ref?: React.ForwardedRef) => { - const { option, renderOption, isSelected, index, handleSelect, isFocused, elementId } = props; - - return ( - { - handleSelect(option); - }} - > - {React.cloneElement(renderOption(option, index, isSelected) as React.ReactElement, { - //@ts-expect-error - elementDescriptor: descriptors.selectOption, - elementId: descriptors.selectOption.setId(elementId), - 'data-selected': isSelected, - 'data-focused': isFocused, - })} - - ); - }), -); - -const SelectSearchbar = (props: PropsOfComponent) => { - const { sx, ...rest } = props; - React.useEffect(() => { - // @ts-expect-error - return () => props.onChange({ target: { value: '' } }); - }, []); - const { elementId } = useSelectState(); - - return ( - ({ padding: t.space.$1 })}> - ({ - border: 'none', - borderRadius: t.radii.$md, - backgroundColor: t.colors.$blackAlpha200, - padding: t.space.$2, - }), - sx, - ]} - {...rest} - /> - - ); -}; - -export const SelectNoResults = (props: PropsOfComponent) => { - const { sx, ...rest } = props; - return ( - ({ width: '100%', padding: `${theme.space.$2} 0 0 ${theme.space.$4}` }), sx]} - {...rest} - /> - ); -}; - -type SelectOptionListProps = PropsOfComponent & { - containerSx?: ThemableCssProp; -}; - -export const SelectOptionList = (props: SelectOptionListProps) => { - const { containerSx, sx, ...rest } = props; - const { - popoverCtx, - searchInputCtx, - value, - renderOption, - searchPlaceholder, - comparator, - focusedItemRef, - noResultsMessage, - select, - onTriggerClick, - elementId, - } = useSelectState(); - const { filteredItems: options, searchInputProps } = searchInputCtx; - const [focusedIndex, setFocusedIndex] = useState(0); - const { isOpen, floating, styles, nodeId, context } = popoverCtx; - const containerRef = React.useRef(null); - - const scrollToItemOnSelectedIndexChange = () => { - if (!isOpen) { - setFocusedIndex(-1); - return; - } - focusedItemRef.current?.scrollIntoView({ block: 'nearest' }); - }; - - React.useEffect(scrollToItemOnSelectedIndexChange, [focusedIndex, isOpen]); - React.useEffect(() => { - if (!comparator) { - containerRef?.current?.focus(); - } - }, [isOpen]); - - const onKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'ArrowUp') { - e.preventDefault(); - if (isOpen) { - return setFocusedIndex((i = 0) => Math.max(i - 1, 0)); - } - return onTriggerClick(); - } - - if (e.key === 'ArrowDown') { - e.preventDefault(); - if (isOpen) { - return setFocusedIndex((i = 0) => Math.min(i + 1, options.length - 1)); - } - return onTriggerClick(); - } - - if (e.key === 'Enter' && focusedIndex >= 0) { - e.preventDefault(); - return select(options[focusedIndex]); - } - }; - - return ( - - ({ - backgroundColor: colors.makeSolid(theme.colors.$colorBackground), - border: theme.borders.$normal, - borderRadius: theme.radii.$lg, - borderColor: theme.colors.$blackAlpha200, - overflow: 'hidden', - animation: `${animations.dropdownSlideInScaleAndFade} ${theme.transitionDuration.$slower} ${theme.transitionTiming.$slowBezier}`, - transformOrigin: 'top center', - boxShadow: theme.shadows.$cardShadow, - zIndex: theme.zIndices.$dropdown, - }), - sx, - ]} - style={{ ...styles, left: styles.left - 1 }} - > - {comparator && ( - - )} - ({ - gap: theme.space.$1, - outline: 'none', - overflowY: 'auto', - maxHeight: '18vh', - padding: `${theme.space.$2} 0`, - }), - containerSx, - ]} - {...rest} - > - {options.map((option, index) => { - const isFocused = index === focusedIndex; - const isSelected = value === option.value; - - return ( - - ); - })} - {noResultsMessage && options.length === 0 && {noResultsMessage}} - - - - ); -}; - -export const SelectButton = (props: PropsOfComponent) => { - const { sx, children, ...rest } = props; - const { popoverCtx, onTriggerClick, buttonRenderOption, selectedOption, placeholder, elementId } = useSelectState(); - const { reference } = popoverCtx; - - let show: React.ReactNode = children; - if (!children) { - show = selectedOption ? ( - buttonRenderOption(selectedOption) - ) : ( - ({ opacity: t.opacity.$inactive })}>{placeholder} - ); - } - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/SocialButtons.tsx b/packages/clerk-js/src/ui.retheme/elements/SocialButtons.tsx deleted file mode 100644 index a85752e525f..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/SocialButtons.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import type { OAuthProvider, OAuthStrategy, Web3Provider, Web3Strategy } from '@clerk/types'; -import type { Ref } from 'react'; -import React, { forwardRef, isValidElement } from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { - Button, - descriptors, - Flex, - Grid, - Icon, - Image, - localizationKeys, - SimpleButton, - Spinner, - Text, - useAppearance, -} from '../customizables'; -import { useEnabledThirdPartyProviders, useResizeObserver } from '../hooks'; -import type { PropsOfComponent } from '../styledSystem'; -import { sleep } from '../utils'; -import { useCardState } from './contexts'; -import { distributeStrategiesIntoRows } from './utils'; - -const SOCIAL_BUTTON_BLOCK_THRESHOLD = 2; -const SOCIAL_BUTTON_PRE_TEXT_THRESHOLD = 1; -const MAX_STRATEGIES_PER_ROW = 6; - -export type SocialButtonsProps = React.PropsWithChildren<{ - enableOAuthProviders: boolean; - enableWeb3Providers: boolean; -}>; - -type SocialButtonsRootProps = SocialButtonsProps & { - oauthCallback: (strategy: OAuthStrategy) => Promise; - web3Callback: (strategy: Web3Strategy) => Promise; -}; - -const isWeb3Strategy = (val: string): val is Web3Strategy => { - return val.startsWith('web3_'); -}; - -export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { - const { oauthCallback, web3Callback, enableOAuthProviders = true, enableWeb3Providers = true } = props; - const { web3Strategies, authenticatableOauthStrategies, strategyToDisplayData } = useEnabledThirdPartyProviders(); - const card = useCardState(); - const { socialButtonsVariant } = useAppearance().parsedLayout; - const [firstStrategyRef, firstElementRect] = useResizeObserver(); - - const strategies = [ - ...(enableOAuthProviders ? authenticatableOauthStrategies : []), - ...(enableWeb3Providers ? web3Strategies : []), - ]; - - if (!strategies.length) { - return null; - } - - const strategyRows = distributeStrategiesIntoRows([...strategies], MAX_STRATEGIES_PER_ROW); - - const preferBlockButtons = - socialButtonsVariant === 'blockButton' - ? true - : socialButtonsVariant === 'iconButton' - ? false - : strategies.length <= SOCIAL_BUTTON_BLOCK_THRESHOLD; - - const startOauth = (strategy: OAuthStrategy | Web3Strategy) => async () => { - card.setLoading(strategy); - try { - if (isWeb3Strategy(strategy)) { - await web3Callback(strategy); - } else { - await oauthCallback(strategy); - } - } catch { - await sleep(1000); - card.setIdle(); - } - await sleep(5000); - card.setIdle(); - }; - - const ButtonElement = preferBlockButtons ? SocialButtonBlock : SocialButtonIcon; - - return ( - - {strategyRows.map((row, rowIndex) => ( - - {row.map((strategy, strategyIndex) => { - const label = - strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD - ? `Continue with ${strategyToDisplayData[strategy].name}` - : strategyToDisplayData[strategy].name; - - const localizedText = - strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD - ? localizationKeys('socialButtonsBlockButton', { - provider: strategyToDisplayData[strategy].name, - }) - : undefined; - - // When strategies break into 2 rows or more, use the first item of the first - // row as reference for the width of the buttons in the second row and beyond - const ref = - strategies.length > MAX_STRATEGIES_PER_ROW && rowIndex === 0 && strategyIndex === 0 - ? firstStrategyRef - : null; - - return ( - ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} - /> - } - /> - ); - })} - - ))} - - ); -}); - -type SocialButtonProps = PropsOfComponent & { - icon: React.ReactElement; - id: OAuthProvider | Web3Provider; - textLocalizationKey: LocalizationKey | undefined; - label?: string; -}; - -const SocialButtonIcon = forwardRef((props: SocialButtonProps, ref: Ref | null): JSX.Element => { - const { icon, label, id, textLocalizationKey, ...rest } = props; - - return ( - - ); -}); - -const SocialButtonBlock = (props: SocialButtonProps): JSX.Element => { - const { id, icon, isLoading, label, textLocalizationKey, ...rest } = props; - const isIconElement = isValidElement(icon); - - return ( - [ - { - gap: theme.space.$4, - position: 'relative', - justifyContent: 'flex-start', - borderColor: theme.colors.$blackAlpha200, - }, - props.sx, - ]} - > - - {(isLoading || icon) && ( - ({ flex: `0 0 ${theme.space.$4}` })} - > - {isLoading ? ( - - ) : !isIconElement && icon ? ( - ({ - color: theme.colors.$blackAlpha600, - width: theme.sizes.$4, - position: 'absolute', - }), - ]} - /> - ) : ( - icon - )} - - )} - - {label} - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/SuccessPage.tsx b/packages/clerk-js/src/ui.retheme/elements/SuccessPage.tsx deleted file mode 100644 index 53702a29382..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/SuccessPage.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; - -import { Box, descriptors, Text } from '../customizables'; -import { ContentPage, FormButtonContainer, NavigateToFlowStartButton } from '../elements'; -import type { LocalizationKey } from '../localization'; -import { localizationKeys } from '../localization'; -import type { PropsOfComponent } from '../styledSystem'; - -type SuccessPageProps = Omit, 'headerTitle' | 'title'> & { - title: LocalizationKey; - text?: LocalizationKey | LocalizationKey[]; - finishLabel?: LocalizationKey; - contents?: React.ReactNode; - onFinish?: () => void; -}; - -export const SuccessPage = (props: SuccessPageProps) => { - const { text, title, finishLabel, onFinish, contents, ...rest } = props; - - return ( - - - {Array.isArray(text) ? ( - text.map(t => ( - ({ - display: 'inline', - ':not(:last-of-type)': { - marginRight: t.sizes.$1, - }, - })} - /> - )) - ) : ( - - )} - - {contents} - - - - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/Tabs.tsx b/packages/clerk-js/src/ui.retheme/elements/Tabs.tsx deleted file mode 100644 index 4b38e244829..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/Tabs.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { PropsWithChildren } from 'react'; -import React from 'react'; - -import { Button, descriptors, Flex, useLocalizations } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; -import { getValidChildren } from '../utils'; - -type TabsContextValue = { - selectedIndex: number; - setSelectedIndex: (item: number) => void; - focusedIndex: number; - setFocusedIndex: (item: number) => void; -}; - -const [TabsContext, useTabsContext] = createContextAndHook('TabsContext'); -const TabsContextProvider = (props: React.PropsWithChildren<{ value: TabsContextValue }>) => { - const ctxValue = React.useMemo( - () => ({ value: props.value }), - [props.value.selectedIndex, props.value.setFocusedIndex], - ); - return {props.children}; -}; - -type TabsProps = PropsWithChildren<{ - defaultIndex?: number; -}>; - -export const Tabs = (props: TabsProps) => { - const { defaultIndex = 0, children } = props; - const [selectedIndex, setSelectedIndex] = React.useState(defaultIndex); - const [focusedIndex, setFocusedIndex] = React.useState(-1); - - return ( - - {children} - - ); -}; - -type TabsListProps = PropsOfComponent; -export const TabsList = (props: TabsListProps) => { - const { children, sx, ...rest } = props; - const { setSelectedIndex, selectedIndex, setFocusedIndex } = useTabsContext(); - - const childrenWithProps = getValidChildren(children).map((child, index) => - React.cloneElement(child, { - tabIndex: index, - }), - ); - - const onKeyDown = (e: React.KeyboardEvent) => { - const tabs = childrenWithProps.filter(child => !child.props?.isDisabled).map(child => child.props.tabIndex); - const tabsLenth = tabs.length; - const current = tabs.indexOf(selectedIndex); - - if (e.key === 'ArrowLeft') { - const prev = current === 0 ? tabs[tabsLenth - 1] : tabs[current - 1]; - setFocusedIndex(prev); - return setSelectedIndex(prev); - } - if (e.key === 'ArrowRight') { - const next = tabsLenth - 1 === current ? tabs[0] : tabs[current + 1]; - setFocusedIndex(next); - return setSelectedIndex(next); - } - }; - - return ( - ({ - borderBottom: theme.borders.$normal, - borderColor: theme.colors.$blackAlpha300, - }), - sx, - ]} - {...rest} - > - {childrenWithProps} - - ); -}; - -type TabProps = PropsOfComponent; -type TabPropsWithTabIndex = TabProps & { tabIndex?: number }; -export const Tab = (props: TabProps) => { - const { t } = useLocalizations(); - const { children, sx, tabIndex, isDisabled, localizationKey, ...rest } = props as TabPropsWithTabIndex; - - if (tabIndex === undefined) { - throw new Error('Tab component must be a direct child of TabList.'); - } - - const { setSelectedIndex, selectedIndex, focusedIndex, setFocusedIndex } = useTabsContext(); - const buttonRef = React.useRef(null); - const isActive = tabIndex === selectedIndex; - const isFocused = tabIndex === focusedIndex; - - const onClick = () => { - setSelectedIndex(tabIndex); - setFocusedIndex(-1); - }; - - React.useEffect(() => { - if (isDisabled && tabIndex === 0) { - setSelectedIndex((tabIndex as number) + 1); - } - }, []); - - React.useEffect(() => { - if (buttonRef.current && isFocused) { - buttonRef.current.focus(); - } - }, [isFocused]); - - return ( - - ); -}; - -export const TabPanels = (props: PropsWithChildren>) => { - const { children } = props; - - const childrenWithProps = getValidChildren(children).map((child, index) => - React.cloneElement(child, { - tabIndex: index, - }), - ); - - return <>{childrenWithProps}; -}; - -type TabPanelProps = PropsOfComponent; -type TabPanelPropsWithTabIndex = TabPanelProps & { tabIndex?: number }; -export const TabPanel = (props: TabPanelProps) => { - const { tabIndex, sx, children, ...rest } = props as TabPanelPropsWithTabIndex; - - if (tabIndex === undefined) { - throw new Error('TabPanel component must be a direct child of TabPanels.'); - } - - const { selectedIndex } = useTabsContext(); - const isOpen = tabIndex === selectedIndex; - - if (!isOpen) { - return null; - } - - return ( - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/TagInput.tsx b/packages/clerk-js/src/ui.retheme/elements/TagInput.tsx deleted file mode 100644 index 0fa05e0d0bc..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/TagInput.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React from 'react'; - -import type { LocalizationKey } from '../customizables'; -import { descriptors, Flex, Icon, Input, Text, useLocalizations } from '../customizables'; -import { Plus } from '../icons'; -import type { PropsOfComponent } from '../styledSystem'; -import { common } from '../styledSystem'; - -type Tag = string; - -const sanitize = (val: string) => val.trim(); - -type TagInputProps = Pick, 'sx'> & { - value: string; - onChange: React.ChangeEventHandler; - validate?: (tag: Tag) => boolean; - placeholder?: LocalizationKey | string; - autoFocus?: boolean; - validateUnsubmittedEmail?: (value: string) => void; -}; - -export const TagInput = (props: TagInputProps) => { - const { t } = useLocalizations(); - const { - sx, - placeholder, - validate = () => true, - value: valueProp, - onChange: onChangeProp, - autoFocus, - validateUnsubmittedEmail = () => null, - ...rest - } = props; - const tags = valueProp.split(',').map(sanitize).filter(Boolean); - const tagsSet = new Set(tags); - const keyReleasedRef = React.useRef(true); - const inputRef = React.useRef(null); - const [input, setInput] = React.useState(''); - - const emit = (newTags: Tag[]) => { - onChangeProp({ target: { value: newTags.join(',') } } as any); - focusInput(); - validateUnsubmittedEmail(''); - }; - - const remove = (tag: Tag) => { - emit(tags.filter(t => t !== tag)); - }; - - const removeLast = () => { - emit(tags.slice(0, -1)); - }; - - const addTag = (tag: Tag | Tag[]) => { - // asdfa@asd.com - const newTags = (Array.isArray(tag) ? [...tag] : [tag]) - .map(sanitize) - .filter(Boolean) - .filter(validate) - .filter(t => !tagsSet.has(t)); - - if (newTags.length) { - emit([...tags, ...newTags]); - setInput(''); - focusInput(); - } - }; - - const focusInput = () => { - inputRef.current?.focus(); - }; - - const handleKeyDown: React.KeyboardEventHandler = e => { - const { key } = e; - if ((key === ',' || key === ' ' || key === 'Enter') && !!input.length) { - e.preventDefault(); - addTag(input); - } else if (key === 'Backspace' && !input.length && !!tags.length && keyReleasedRef.current) { - e.preventDefault(); - removeLast(); - } - keyReleasedRef.current = false; - }; - - const handleOnBlur: React.FocusEventHandler = e => { - e.preventDefault(); - addTag(input); - }; - - const handleKeyUp: React.KeyboardEventHandler = () => { - // If the user is holding backspace down, we want to clear the input - // but not start deleting previous tags. So we wait for them to release the - // backspace key, before allowing the deletion of the previously set tag - keyReleasedRef.current = true; - }; - - const handleChange: React.ChangeEventHandler = e => { - setInput(e.target.value); - validateUnsubmittedEmail(e.target.value); - }; - - const handlePaste = (e: React.ClipboardEvent) => { - e.preventDefault(); - addTag( - (e.clipboardData.getData('text') || '') - .split(/,| |\n|\t/) - .filter(Boolean) - .map(tag => tag.trim()), - ); - }; - - return ( - ({ - maxWidth: '100%', - padding: `${t.space.$2x5} ${t.space.$4}`, - backgroundColor: t.colors.$colorInputBackground, - color: t.colors.$colorInputText, - minHeight: t.sizes.$20, - maxHeight: t.sizes.$60, - overflowY: 'auto', - ...common.borderVariants(t).normal, - }), - sx, - ]} - {...rest} - > - {tags.map(tag => ( - remove(tag)} - > - {tag} - - ))} - - ({ - flexGrow: 1, - border: 'none', - width: 'initial', - padding: 0, - lineHeight: t.space.$6, - paddingLeft: t.space.$1, - })} - /> - - ); -}; - -type TagPillProps = React.PropsWithChildren<{ onRemoveClick: React.MouseEventHandler }>; -const TagPill = (props: TagPillProps) => { - const { onRemoveClick, children, ...rest } = props; - - return ( - ({ - padding: `${t.space.$1x5} ${t.space.$3}`, - backgroundColor: t.colors.$blackAlpha50, - borderRadius: t.radii.$sm, - cursor: 'pointer', - ':hover svg': { - color: t.colors.$danger500, - }, - overflow: 'hidden', - })} - > - {children} - ({ color: t.colors.$blackAlpha500, transform: 'translateY(1px) rotate(45deg)' })} - /> - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/ThreeDotsMenu.tsx b/packages/clerk-js/src/ui.retheme/elements/ThreeDotsMenu.tsx deleted file mode 100644 index 674091050bf..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/ThreeDotsMenu.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { MenuId } from '@clerk/types'; - -import type { LocalizationKey } from '../customizables'; -import { Button, Icon } from '../customizables'; -import { ThreeDots } from '../icons'; -import { Menu, MenuItem, MenuList, MenuTrigger } from './Menu'; - -type Action = { - label: LocalizationKey; - isDestructive?: boolean; - onClick: () => unknown; - isDisabled?: boolean; -}; - -type ThreeDotsMenuProps = { - actions: Action[]; - elementId?: MenuId; -}; - -export const ThreeDotsMenu = (props: ThreeDotsMenuProps) => { - const { actions, elementId } = props; - return ( - - - - - - {actions.map((a, index) => ( - - ))} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/TileButton.tsx b/packages/clerk-js/src/ui.retheme/elements/TileButton.tsx deleted file mode 100644 index cb53957bdf5..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/TileButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import { Button, Col, Icon, Text } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -export const TileButton = (props: PropsOfComponent & { icon: React.ComponentType }) => { - const { icon, ...rest } = props; - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/TimerButton.tsx b/packages/clerk-js/src/ui.retheme/elements/TimerButton.tsx deleted file mode 100644 index eebcb538cfa..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/TimerButton.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useSafeLayoutEffect } from '@clerk/shared/react'; -import React, { useEffect } from 'react'; - -import { Button, useLocalizations } from '../customizables'; -import type { PropsOfComponent } from '../styledSystem'; - -type TimerButtonProps = PropsOfComponent & { - throttleTimeInSec?: number; - startDisabled?: boolean; - showCounter?: boolean; -}; - -export const TimerButton = (props: TimerButtonProps) => { - const { t } = useLocalizations(); - const { - onClick: onClickProp, - throttleTimeInSec = 30, - startDisabled, - children, - localizationKey, - showCounter = true, - ...rest - } = props; - const [remainingSeconds, setRemainingSeconds] = React.useState(0); - const intervalIdRef = React.useRef(undefined); - - useSafeLayoutEffect(() => { - if (startDisabled) { - disable(); - } - }, []); - - useEffect(() => { - return () => clearInterval(intervalIdRef.current); - }, []); - - const disable = () => { - setRemainingSeconds(throttleTimeInSec); - intervalIdRef.current = window.setInterval(() => { - setRemainingSeconds(seconds => { - if (seconds === 1) { - clearInterval(intervalIdRef.current); - } - return seconds - 1; - }); - }, 1000); - }; - - const handleOnClick: React.MouseEventHandler = e => { - if (remainingSeconds) { - return; - } - onClickProp?.(e); - disable(); - }; - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/UserAvatar.tsx b/packages/clerk-js/src/ui.retheme/elements/UserAvatar.tsx deleted file mode 100644 index 3a7c52d5477..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/UserAvatar.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { UserResource } from '@clerk/types'; - -import { getFullName, getInitials } from '../../utils/user'; -import { Avatar } from '../elements'; -import type { PropsOfComponent } from '../styledSystem'; - -type UserAvatarProps = Omit, 'imageUrl'> & - Partial> & { - name?: string | null; - avatarUrl?: string | null; - }; - -export const UserAvatar = (props: UserAvatarProps) => { - const { name, firstName, lastName, avatarUrl, imageUrl, ...rest } = props; - const generatedName = getFullName({ name, firstName, lastName }); - const initials = getInitials({ name, firstName, lastName }); - - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/UserPreview.tsx b/packages/clerk-js/src/ui.retheme/elements/UserPreview.tsx deleted file mode 100644 index a4b65320f08..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/UserPreview.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import type { ExternalAccountResource, SamlAccountResource, UserPreviewId, UserResource } from '@clerk/types'; -import React from 'react'; - -import { getFullName, getIdentifier } from '../../utils/user'; -import type { LocalizationKey } from '../customizables'; -import { descriptors, Flex, Text, useLocalizations } from '../customizables'; -import type { InternalTheme, PropsOfComponent, ThemableCssProp } from '../styledSystem'; -import { UserAvatar } from './UserAvatar'; - -// TODO Make this accept an interface with the superset of fields in: -// - User -// - ExternalAccountResource -// - SamlAccountResource - -export type UserPreviewProps = Omit, 'title' | 'elementId'> & { - size?: 'lg' | 'md' | 'sm' | 'xs'; - icon?: React.ReactNode; - iconSx?: ThemableCssProp; - badge?: React.ReactNode; - imageUrl?: string | null; - rounded?: boolean; - elementId?: UserPreviewId; - avatarSx?: ThemableCssProp; - mainIdentifierSx?: ThemableCssProp; - mainIdentifierVariant?: PropsOfComponent['variant']; - title?: LocalizationKey | string; - subtitle?: LocalizationKey | string; - showAvatar?: boolean; -} & ( - | { - user?: Partial; - externalAccount?: null | undefined; - samlAccount?: null | undefined; - } - | { - user?: null | undefined; - externalAccount?: Partial; - samlAccount?: null | undefined; - } - | { - user?: null | undefined; - externalAccount?: null | undefined; - samlAccount?: Partial; - } - ); - -export const UserPreview = (props: UserPreviewProps) => { - const { - user, - externalAccount, - samlAccount, - size = 'md', - showAvatar = true, - icon, - iconSx, - rounded = true, - imageUrl: imageUrlProp, - badge, - elementId, - sx, - title, - subtitle, - avatarSx, - mainIdentifierSx, - mainIdentifierVariant, - ...rest - } = props; - const { t } = useLocalizations(); - const name = getFullName({ ...user }) || getFullName({ ...externalAccount }) || getFullName({ ...samlAccount }); - const identifier = getIdentifier({ ...user }) || externalAccount?.accountIdentifier?.() || samlAccount?.emailAddress; - const localizedTitle = t(title); - - const imageUrl = imageUrlProp || user?.imageUrl || externalAccount?.imageUrl; - - const getAvatarSizes = (t: InternalTheme) => - (({ xs: t.sizes.$5, sm: t.sizes.$8, md: t.sizes.$9, lg: t.sizes.$12 } as const)[size]); - - const mainIdentifierSize = - mainIdentifierVariant || ({ xs: 'subtitle', sm: 'caption', md: 'subtitle', lg: 'h1' } as const)[size]; - - return ( - ({ minWidth: '0px', width: 'fit-content', gap: t.space.$4 }), sx]} - {...rest} - > - {/*Do not attempt to render or reserve space based on height if image url is not defined*/} - {imageUrl ? ( - showAvatar ? ( - - - - {icon && ( - - {icon} - - )} - - ) : ( - // Reserve layout space when avatar is not visible - ({ - height: getAvatarSizes(t), - })} - /> - ) - ) : null} - - - ({ display: 'flex', gap: theme.sizes.$1, alignItems: 'center' }), mainIdentifierSx]} - > - - {localizedTitle || name || identifier} - - - {badge} - - - {(subtitle || (name && identifier)) && ( - - )} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/VerificationCodeCard.tsx b/packages/clerk-js/src/ui.retheme/elements/VerificationCodeCard.tsx deleted file mode 100644 index e927c2b3cbe..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/VerificationCodeCard.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import React from 'react'; - -import { Button, Col, descriptors, localizationKeys } from '../customizables'; -import type { LocalizationKey } from '../localization'; -import { CardAlert } from './Alert'; -import { Card } from './Card'; -import { useFieldOTP } from './CodeControl'; -import { useCardState } from './contexts'; -import { Footer } from './Footer'; -import { Form } from './Form'; -import { Header } from './Header'; -import { IdentityPreview } from './IdentityPreview'; - -export type VerificationCodeCardProps = { - cardTitle: LocalizationKey; - cardSubtitle: LocalizationKey; - formTitle: LocalizationKey; - formSubtitle: LocalizationKey; - safeIdentifier?: string | undefined | null; - resendButton?: LocalizationKey; - profileImageUrl?: string; - onCodeEntryFinishedAction: ( - code: string, - resolve: () => Promise, - reject: (err: unknown) => Promise, - ) => void; - onResendCodeClicked?: React.MouseEventHandler; - showAlternativeMethods?: boolean; - onShowAlternativeMethodsClicked?: React.MouseEventHandler; - onIdentityPreviewEditClicked?: React.MouseEventHandler; - onBackLinkClicked?: React.MouseEventHandler; -}; - -export const VerificationCodeCard = (props: PropsWithChildren) => { - const { showAlternativeMethods = true, children } = props; - const card = useCardState(); - - const otp = useFieldOTP({ - onCodeEntryFinished: (code, resolve, reject) => { - props.onCodeEntryFinishedAction(code, resolve, reject); - }, - onResendCodeClicked: props.onResendCodeClicked, - }); - - return ( - - {card.error} - - - - - - {children} - - - - - ); - }); - - return { - Field: MockFieldWrapper, - }; -}; - -describe('PlainInput', () => { - it('renders the component', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByLabelText } = render(, { wrapper }); - expect(getByLabelText('some label')).toHaveValue('init value'); - expect(getByLabelText('some label')).toHaveAttribute('name', 'firstname'); - expect(getByLabelText('some label')).toHaveAttribute('placeholder', 'some placeholder'); - expect(getByLabelText('some label')).toHaveAttribute('type', 'text'); - expect(getByLabelText('some label')).toHaveAttribute('id', 'firstname-field'); - expect(getByLabelText('some label')).not.toHaveAttribute('disabled'); - expect(getByLabelText('some label')).not.toHaveAttribute('required'); - expect(getByLabelText('some label')).toHaveAttribute('aria-invalid', 'false'); - expect(getByLabelText('some label')).toHaveAttribute('aria-describedby', ''); - expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false'); - expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'false'); - }); - - it('disabled', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByLabelText } = render(, { wrapper }); - expect(getByLabelText('some label')).toHaveValue('init value'); - expect(getByLabelText('some label')).toHaveAttribute('disabled'); - expect(getByLabelText('some label')).toHaveAttribute('aria-disabled', 'true'); - }); - - it('required', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByLabelText, queryByText } = render(, { wrapper }); - expect(getByLabelText('some label')).toHaveValue('init value'); - expect(getByLabelText('some label')).toHaveAttribute('required'); - expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'true'); - expect(queryByText(/optional/i)).not.toBeInTheDocument(); - }); - - it('optional', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByLabelText, getByText } = render(, { wrapper }); - expect(getByLabelText('some label')).not.toHaveAttribute('required'); - expect(getByLabelText('some label')).toHaveAttribute('aria-required', 'false'); - expect(getByText(/optional/i)).toBeInTheDocument(); - }); - - it('with icon', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const Icon = () => this is an icon; - - const { getByAltText } = render(, { wrapper }); - expect(getByAltText(/this is an icon/i)).toBeInTheDocument(); - }); - - it('with action label', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByRole } = render(, { wrapper }); - expect(getByRole('link', { name: /take action/i })).toBeInTheDocument(); - expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('rel'); - expect(getByRole('link', { name: /take action/i })).not.toHaveAttribute('target'); - expect(getByRole('link', { name: /take action/i })).toHaveAttribute('href', ''); - }); - - it('with error', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - }); - - const { getByRole, getByLabelText, findByText } = render(, { wrapper }); - - await userEvent.click(getByRole('button', { name: /set error/i })); - - expect(await findByText('some error')).toBeInTheDocument(); - - const label = getByLabelText('some label'); - expect(label).toHaveAttribute('aria-invalid', 'true'); - expect(label).toHaveAttribute('aria-describedby', 'error-firstname'); - }); - - it('with info', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('firstname', 'init value', { - type: 'text', - label: 'some label', - placeholder: 'some placeholder', - infoText: 'some info', - }); - - const { findByLabelText, findByText } = render(, { wrapper }); - - fireEvent.focus(await findByLabelText('some label')); - expect(await findByText('some info')).toBeInTheDocument(); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/__tests__/RadioGroup.test.tsx b/packages/clerk-js/src/ui.retheme/elements/__tests__/RadioGroup.test.tsx deleted file mode 100644 index 5c10111e646..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/__tests__/RadioGroup.test.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, it } from '@jest/globals'; -import { fireEvent, render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { useFormControl } from '../../utils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; -import { withCardStateProvider } from '../contexts'; -import { Form } from '../Form'; - -const { createFixtures } = bindCreateFixtures('UserProfile'); -const createField = (...params: Parameters) => { - const MockFieldWrapper = withCardStateProvider((props: Partial[0]>) => { - const field = useFormControl(...params); - - return ( - <> - {/* @ts-ignore*/} - - - - ); - }); - - return { - Field: MockFieldWrapper, - }; -}; - -describe('RadioGroup', () => { - it('renders the component', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', ''); - - const { getAllByRole } = render( - , - { wrapper }, - ); - - const radios = getAllByRole('radio'); - expect(radios[0]).toHaveAttribute('value', 'one'); - expect(radios[0].nextSibling).toHaveTextContent('One'); - expect(radios[1]).toHaveAttribute('value', 'two'); - expect(radios[1].nextSibling).toHaveTextContent('Two'); - - radios.forEach(radio => { - expect(radio).not.toBeChecked(); - expect(radio).toHaveAttribute('name', 'some-radio'); - expect(radio).not.toHaveAttribute('required'); - expect(radio).not.toHaveAttribute('disabled'); - }); - - expect(radios[1]).not.toBeChecked(); - }); - - it('renders the component with default value', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', 'two'); - - const { getAllByRole } = render( - , - { wrapper }, - ); - - const radios = getAllByRole('radio'); - expect(radios[0]).toHaveAttribute('value', 'one'); - expect(radios[0].nextSibling).toHaveTextContent('One'); - expect(radios[1]).toHaveAttribute('value', 'two'); - expect(radios[1].nextSibling).toHaveTextContent('Two'); - - radios.forEach(radio => { - expect(radio).toHaveAttribute('type', 'radio'); - expect(radio).not.toHaveAttribute('required'); - expect(radio).not.toHaveAttribute('disabled'); - expect(radio).toHaveAttribute('aria-required', 'false'); - expect(radio).toHaveAttribute('aria-disabled', 'false'); - }); - - expect(radios[0]).not.toBeChecked(); - expect(radios[1]).toBeChecked(); - }); - - it('disabled', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', 'two'); - - const { getAllByRole } = render( - , - { wrapper }, - ); - - const radios = getAllByRole('radio'); - radios.forEach(radio => { - expect(radio).not.toHaveAttribute('required'); - expect(radio).toHaveAttribute('disabled'); - expect(radio).toHaveAttribute('aria-disabled', 'true'); - }); - }); - - it('required', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', 'two'); - - const { getAllByRole } = render( - , - { wrapper }, - ); - - const radios = getAllByRole('radio'); - radios.forEach(radio => { - expect(radio).toHaveAttribute('required'); - expect(radio).toHaveAttribute('aria-required', 'true'); - }); - }); - - it('with error', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', 'two'); - - const { getAllByRole, getByRole, findByText } = render( - , - { wrapper }, - ); - - await userEvent.click(getByRole('button', { name: /set error/i })); - expect(await findByText('some error')).toBeInTheDocument(); - - const radios = getAllByRole('radio'); - radios.forEach(radio => { - expect(radio).toHaveAttribute('aria-invalid', 'true'); - expect(radio).toHaveAttribute('aria-describedby', 'error-some-radio'); - }); - }); - - it('with info', async () => { - const { wrapper } = await createFixtures(); - const { Field } = createField('some-radio', '', { - type: 'radio', - radioOptions: [ - { value: 'one', label: 'One' }, - { value: 'two', label: 'Two' }, - ], - infoText: 'some info', - }); - - const { findByLabelText, findByText } = render(, { wrapper }); - - fireEvent.focus(await findByLabelText('One')); - expect(await findByText('some info')).toBeInTheDocument(); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/elements/contexts/CardStateContext.tsx b/packages/clerk-js/src/ui.retheme/elements/contexts/CardStateContext.tsx deleted file mode 100644 index d974c12402c..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/contexts/CardStateContext.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { ClerkAPIError, ClerkRuntimeError } from '@clerk/types'; -import React from 'react'; - -import { useLocalizations } from '../../customizables'; -import { useSafeState } from '../../hooks'; - -type Status = 'idle' | 'loading' | 'error'; -type Metadata = string | undefined; -type State = { status: Status; metadata: Metadata; error: string | undefined }; -type CardStateCtxValue = { - state: State; - setState: React.Dispatch>; -}; - -const [CardStateCtx, _useCardState] = createContextAndHook('CardState'); - -const CardStateProvider = (props: React.PropsWithChildren) => { - const { translateError } = useLocalizations(); - - const [state, setState] = useSafeState({ - status: 'idle', - metadata: undefined, - error: translateError(window?.Clerk?.__internal_last_error || undefined), - }); - - const value = React.useMemo(() => ({ value: { state, setState } }), [state, setState]); - return {props.children}; -}; - -const useCardState = () => { - const { state, setState } = _useCardState(); - const { translateError } = useLocalizations(); - - const setIdle = (metadata?: Metadata) => setState(s => ({ ...s, status: 'idle', metadata })); - const setError = (metadata: ClerkRuntimeError | ClerkAPIError | Metadata | string) => - setState(s => ({ ...s, error: translateError(metadata) })); - const setLoading = (metadata?: Metadata) => setState(s => ({ ...s, status: 'loading', metadata })); - const runAsync = async (cb: Promise | (() => Promise), metadata?: Metadata) => { - setLoading(metadata); - return (typeof cb === 'function' ? cb() : cb) - .then(res => { - return res; - }) - .finally(() => setIdle(metadata)); - }; - - return { - setIdle, - setError, - setLoading, - runAsync, - loadingMetadata: state.status === 'loading' ? state.metadata : undefined, - error: state.error ? state.error : undefined, - isLoading: state.status === 'loading', - isIdle: state.status === 'idle', - }; -}; - -export { useCardState, CardStateProvider }; - -export const withCardStateProvider = (Component: React.ComponentType) => { - return (props: T) => { - return ( - - {/* @ts-expect-error */} - - - ); - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/contexts/FloatingTreeContext.tsx b/packages/clerk-js/src/ui.retheme/elements/contexts/FloatingTreeContext.tsx deleted file mode 100644 index a554f8c6662..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/contexts/FloatingTreeContext.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { FloatingTree, useFloatingParentNodeId } from '@floating-ui/react'; -import React from 'react'; - -export const withFloatingTree = (Component: React.ComponentType): React.ComponentType => { - return (props: T) => { - const parentId = useFloatingParentNodeId(); - if (parentId == null) { - return ( - - {/* @ts-expect-error */} - - - ); - } - - /* @ts-expect-error */ - return ; - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/elements/contexts/FlowMetadataContext.tsx b/packages/clerk-js/src/ui.retheme/elements/contexts/FlowMetadataContext.tsx deleted file mode 100644 index 35bcd088afd..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/contexts/FlowMetadataContext.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import React from 'react'; - -type FlowMetadata = { - flow: - | 'signIn' - | 'signUp' - | 'userButton' - | 'userProfile' - | 'organizationProfile' - | 'createOrganization' - | 'organizationSwitcher' - | 'organizationList'; - part?: - | 'start' - | 'emailCode' - | 'phoneCode' - | 'phoneCode2Fa' - | 'totp2Fa' - | 'backupCode2Fa' - | 'password' - | 'resetPassword' - | 'emailLink' - | 'emailLinkVerify' - | 'emailLinkStatus' - | 'alternativeMethods' - | 'forgotPasswordMethods' - | 'havingTrouble' - | 'ssoCallback' - | 'popover' - | 'complete' - | 'accountSwitcher'; -}; - -const [FlowMetadataCtx, useFlowMetadata] = createContextAndHook('FlowMetadata'); - -const FlowMetadataProvider = (props: React.PropsWithChildren) => { - const { flow, part } = props; - const value = React.useMemo(() => ({ value: props }), [flow, part]); - return {props.children}; -}; - -export { useFlowMetadata, FlowMetadataProvider }; -export type { FlowMetadata }; diff --git a/packages/clerk-js/src/ui.retheme/elements/contexts/index.ts b/packages/clerk-js/src/ui.retheme/elements/contexts/index.ts deleted file mode 100644 index 97db4e0373b..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/contexts/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './CardStateContext'; -export * from './FlowMetadataContext'; -export * from './FloatingTreeContext'; diff --git a/packages/clerk-js/src/ui.retheme/elements/index.ts b/packages/clerk-js/src/ui.retheme/elements/index.ts deleted file mode 100644 index bf16d8e4a0a..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -export * from './contexts'; -export * from './Header'; -export * from './Footer'; -export * from './Alert'; -export * from './Form'; -export * from './BlockWithTrailingComponent'; -export * from './BackLink'; -export * from './IdentityPreview'; -export * from './Avatar'; -export * from './CodeControl'; -export * from './TimerButton'; -export * from './VerificationCodeCard'; -export * from './LoadingCard'; -export * from './ErrorCard'; -export * from './VerificationLinkCard'; -export * from './PoweredByClerk'; -export * from './ApplicationLogo'; -export * from './Card'; -export * from './ArrowBlockButton'; -export * from './ReversibleContainer'; -export * from './Divider'; -export * from './Modal'; -export * from './UserPreview'; -export * from './Accordion'; -export * from './FormattedPhoneNumber'; -export * from './FileDropArea'; -export * from './RootBox'; -export * from './InvisibleRootBox'; -export * from './ClipboardInput'; -export * from './TileButton'; -export * from './Select'; -export * from './Menu'; -export * from './Pagination'; -export * from './FullHeightLoader'; -export * from './Tabs'; -export * from './InputWithIcon'; -export * from './UserAvatar'; -export * from './OrganizationAvatar'; -export * from './OrganizationPreview'; -export * from './PersonalWorkspacePreview'; -export * from './Navbar'; -export * from './Breadcrumbs'; -export * from './ContentPage'; -export * from './ProfileCardContent'; -export * from './IconButton'; -export * from './AvatarUploader'; -export * from './Actions'; -export * from './PopoverCard'; -export * from './TagInput'; -export * from './ThreeDotsMenu'; -export * from './FormButtons'; -export * from './NavigateToFlowStartButton'; -export * from './SuccessPage'; -export * from './IconCircle'; -export * from './Popover'; -export * from './Section'; -export * from './PreviewButton'; -export * from './InformationBox'; -export * from './withAvatarShimmer'; diff --git a/packages/clerk-js/src/ui.retheme/elements/withAvatarShimmer.tsx b/packages/clerk-js/src/ui.retheme/elements/withAvatarShimmer.tsx deleted file mode 100644 index 33816c1abc8..00000000000 --- a/packages/clerk-js/src/ui.retheme/elements/withAvatarShimmer.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { forwardRef } from 'react'; - -import { useAppearance } from '../customizables'; -import type { ThemableCssProp } from '../styledSystem'; - -/** - * This HOC is used to add the hover selector for the avatar shimmer effect to its immediate child. - * It is used since we might want to add the selector to a different element than the avatar itself, - * for example in the - */ -export const withAvatarShimmer = (Component: React.ComponentType) => { - return forwardRef((props, ref) => { - const { parsedLayout } = useAppearance(); - - return ( - ({ - ':hover': { - '--cl-shimmer-hover-shadow': t.shadows.$shadowShimmer, - '--cl-shimmer-hover-transform': 'skew(-45deg) translateX(600%)', - '--cl-shimmer-hover-after-transform': 'skewX(45deg) translateX(-150%)', - }, - }) - : {}, - props.sx, - ]} - /> - ); - }); -}; diff --git a/packages/clerk-js/src/ui.retheme/foundations/__tests__/createInternalTheme.test.ts b/packages/clerk-js/src/ui.retheme/foundations/__tests__/createInternalTheme.test.ts deleted file mode 100644 index 34a8c52b320..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/__tests__/createInternalTheme.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createInternalTheme } from '../createInternalTheme'; - -describe('createInternalTheme', () => { - it('handles empty objects', () => { - const foundations = {}; - const res = createInternalTheme(foundations as any); - expect(res).toEqual({}); - }); - - it('handles empty objects', () => { - const foundations = { - colors: { - primary500: 'primary500', - primary50: 'primary50', - }, - radii: { - 1: '1', - 2: '2', - }, - sizes: {}, - spaces: undefined, - }; - const res = createInternalTheme(foundations as any); - expect(res).toEqual({ - colors: { - $primary500: 'primary500', - $primary50: 'primary50', - }, - radii: { - $1: '1', - $2: '2', - }, - sizes: {}, - spaces: {}, - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/foundations/borders.ts b/packages/clerk-js/src/ui.retheme/foundations/borders.ts deleted file mode 100644 index 58854a02f76..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/borders.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const borders = Object.freeze({ - normal: '1px solid', - heavy: '2px solid', -} as const); diff --git a/packages/clerk-js/src/ui.retheme/foundations/colors.ts b/packages/clerk-js/src/ui.retheme/foundations/colors.ts deleted file mode 100644 index 53a82c4fe4d..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/colors.ts +++ /dev/null @@ -1,98 +0,0 @@ -export const whiteAlpha = Object.freeze({ - whiteAlpha20: 'hsla(0, 0%, 100%, 0.02)', - whiteAlpha50: 'hsla(0, 0%, 100%, 0.04)', - whiteAlpha100: 'hsla(0, 0%, 100%, 0.06)', - whiteAlpha150: 'hsla(0, 0%, 100%, 0.07)', - whiteAlpha200: 'hsla(0, 0%, 100%, 0.08)', - whiteAlpha300: 'hsla(0, 0%, 100%, 0.16)', - whiteAlpha400: 'hsla(0, 0%, 100%, 0.24)', - whiteAlpha500: 'hsla(0, 0%, 100%, 0.36)', - whiteAlpha600: 'hsla(0, 0%, 100%, 0.48)', - whiteAlpha700: 'hsla(0, 0%, 100%, 0.64)', - whiteAlpha750: 'hsla(0, 0%, 100%, 0.72)', - whiteAlpha800: 'hsla(0, 0%, 100%, 0.80)', - whiteAlpha850: 'hsla(0, 0%, 100%, 0.86)', - whiteAlpha900: 'hsla(0, 0%, 100%, 0.92)', - whiteAlpha950: 'hsla(0, 0%, 100%, 0.96)', -} as const); - -export const blackAlpha = Object.freeze({ - blackAlpha25: 'hsla(0, 0%, 0%, 0.02)', - blackAlpha50: 'hsla(0, 0%, 0%, 0.03)', - blackAlpha100: 'hsla(0, 0%, 0%, 0.07)', - blackAlpha150: 'hsla(0, 0%, 0%, 0.11)', - blackAlpha200: 'hsla(0, 0%, 0%, 0.15)', - blackAlpha300: 'hsla(0, 0%, 0%, 0.28)', - blackAlpha400: 'hsla(0, 0%, 0%, 0.41)', - blackAlpha500: 'hsla(0, 0%, 0%, 0.53)', - blackAlpha600: 'hsla(0, 0%, 0%, 0.62)', - blackAlpha700: 'hsla(0, 0%, 0%, 0.73)', - blackAlpha750: 'hsla(0, 0%, 0%, 0.78)', - blackAlpha800: 'hsla(0, 0%, 0%, 0.81)', - blackAlpha850: 'hsla(0, 0%, 0%, 0.84)', - blackAlpha900: 'hsla(0, 0%, 0%, 0.87)', - blackAlpha950: 'hsla(0, 0%, 0%, 0.92)', -} as const); - -export const colors = Object.freeze({ - // Colors that are not affected by `alphaShadesMode` - avatarBorder: blackAlpha.blackAlpha200, - avatarBackground: blackAlpha.blackAlpha400, - modalBackdrop: blackAlpha.blackAlpha700, - activeDeviceBackground: whiteAlpha.whiteAlpha200, - // Themable colors - ...blackAlpha, - ...whiteAlpha, - colorBackground: 'white', - colorInputBackground: 'white', - colorText: 'black', - colorTextOnPrimaryBackground: 'white', - colorTextSecondary: 'rgba(0,0,0,0.65)', - colorTextTertiary: 'rgba(147,148,161,1)', - colorInputText: 'black', - colorShimmer: 'rgba(255, 255, 255, 0.36)', - transparent: 'transparent', - white: 'white', - black: 'black', - primary50: '#B9BDBC', - primary100: '#9EA1A2', - primary200: '#828687', - primary300: '#66696D', - primary400: '#4B4D52', - primary500: '#2F3037', - primary600: '#2A2930', - primary700: '#25232A', - primary800: '#201D23', - primary900: '#1B171C', - danger50: '#FEF2F2', - danger100: '#FEE5E5', - danger200: '#FECACA', - danger300: '#FCA5A5', - danger400: '#F87171', - danger500: '#EF4444', - danger600: '#DC2626', - danger700: '#B91C1C', - danger800: '#991B1B', - danger900: '#7F1D1D', - danger950: '#450A0A', - warning50: '#FFFAEB', - warning100: '#FEF0C7', - warning200: '#FEDF89', - warning300: '#FEC84B', - warning400: '#FDB022', - warning500: '#F79009', - warning600: '#DC6803', - warning700: '#B54708', - warning800: '#93370D', - warning900: '#7A2E0E', - success50: '#ECFDF3', - success100: '#D1FADF', - success200: '#A6F4C5', - success300: '#6CE9A6', - success400: '#32D583', - success500: '#12B76A', - success600: '#039855', - success700: '#027A48', - success800: '#05603A', - success900: '#054F31', -} as const); diff --git a/packages/clerk-js/src/ui.retheme/foundations/createInternalTheme.ts b/packages/clerk-js/src/ui.retheme/foundations/createInternalTheme.ts deleted file mode 100644 index 241ee1129e8..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/createInternalTheme.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { InternalTheme, InternalThemeFoundations } from './defaultFoundations'; - -export const createInternalTheme = (foundations: InternalThemeFoundations): InternalTheme => { - const res = {} as any; - const base = foundations as any; - for (const scale in base) { - res[scale] = {}; - for (const shade in base[scale]) { - res[scale]['$' + shade] = base[scale][shade]; - } - } - return Object.freeze(res); -}; diff --git a/packages/clerk-js/src/ui.retheme/foundations/defaultFoundations.ts b/packages/clerk-js/src/ui.retheme/foundations/defaultFoundations.ts deleted file mode 100644 index 53bdfc24a22..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/defaultFoundations.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { borders } from './borders'; -import { colors } from './colors'; -import { opacity } from './opacity'; -import { shadows } from './shadows'; -import { radii, sizes, space } from './sizes'; -import { transitionDuration, transitionProperty, transitionTiming } from './transitions'; -import { fonts, fontSizes, fontStyles, fontWeights, letterSpacings, lineHeights } from './typography'; -import { zIndices } from './zIndices'; - -const options = { - fontSmoothing: 'auto !important', -} as const; - -const defaultInternalThemeFoundations = Object.freeze({ - colors, - fonts, - fontStyles, - fontSizes, - fontWeights, - letterSpacings, - lineHeights, - radii, - sizes, - space, - shadows, - transitionProperty, - transitionTiming, - transitionDuration, - opacity, - borders, - zIndices, - options, -} as const); - -type InternalThemeFoundations = typeof defaultInternalThemeFoundations; - -type PrefixWith = `${K}${Extract}`; -type InternalTheme = { - [scale in keyof F]: { - [token in keyof F[scale] as PrefixWith<'$', token>]: F[scale][token]; - }; -}; - -export { defaultInternalThemeFoundations }; -export type { InternalTheme, InternalThemeFoundations }; diff --git a/packages/clerk-js/src/ui.retheme/foundations/index.ts b/packages/clerk-js/src/ui.retheme/foundations/index.ts deleted file mode 100644 index 935b65330cb..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createInternalTheme } from './createInternalTheme'; -import type { InternalTheme, InternalThemeFoundations } from './defaultFoundations'; -import { defaultInternalThemeFoundations } from './defaultFoundations'; - -const defaultInternalTheme = createInternalTheme(defaultInternalThemeFoundations); - -export { blackAlpha, whiteAlpha } from './colors'; -export { defaultInternalThemeFoundations, defaultInternalTheme, createInternalTheme }; -export type { InternalTheme, InternalThemeFoundations }; diff --git a/packages/clerk-js/src/ui.retheme/foundations/opacity.ts b/packages/clerk-js/src/ui.retheme/foundations/opacity.ts deleted file mode 100644 index 57597c72055..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/opacity.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const opacity = Object.freeze({ - sm: '24%', - disabled: '50%', - inactive: '62%', -} as const); diff --git a/packages/clerk-js/src/ui.retheme/foundations/shadows.ts b/packages/clerk-js/src/ui.retheme/foundations/shadows.ts deleted file mode 100644 index ccaa2ef3246..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/shadows.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const shadows = Object.freeze({ - menuShadow: - '00px 5px 15px 0px rgba(0, 0, 0, 0.08), 0px 15px 35px -5px rgba(25, 28, 33, 0.20), 0px 0px 0px 1px rgba(25, 28, 33, 0.06)', - fabShadow: '0px 12px 24px rgba(0, 0, 0, 0.32)', - buttonShadow: - '0px 0px 0px 1px {{color}}, 0px 1px 1px 0px rgba(255, 255, 255, 0.07) inset, 0px 2px 3px 0px rgba(34, 42, 53, 0.20), 0px 1px 1px 0px rgba(0, 0, 0, 0.24)', - cardShadow: - '0px 0px 2px 0px rgba(0, 0, 0, 0.08), 0px 1px 2px 0px rgba(25, 28, 33, 0.06), 0px 0px 0px 1px rgba(25, 28, 33, 0.04)', - actionCardShadow: - '1px 4px 0px rgba(0, 0, 0, 0.12), 0px 4px 8px 0px rgba(106, 115, 133, 0.12), 0px 0px 0px 1px rgba(106, 115, 133, 0.12)', - secondaryButtonShadow: - '0px 2px 3px -1px rgba(0, 0, 0, 0.08), 0px 1px 0px 0px rgba(0, 0, 0, 0.02), 0px 0px 0px 1px rgba(0, 0, 0, 0.08)', - shadowShimmer: '1px 1px 2px rgba(0, 0, 0, 0.36)', - sm: '0 1px 1px 0 rgb(0 0 0 / 0.05)', - input: '0px 0px 0px 1px {{color1}}, 0px 1px 1px 0px {{color2}}', - inputHover: '0px 0px 0px 1px {{color1}}, 0px 1px 1px 0px {{color2}}', - focusRing: '0px 0px 0px 4px {{color}}', -} as const); diff --git a/packages/clerk-js/src/ui.retheme/foundations/sizes.ts b/packages/clerk-js/src/ui.retheme/foundations/sizes.ts deleted file mode 100644 index fa3125a4532..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/sizes.ts +++ /dev/null @@ -1,76 +0,0 @@ -const baseSpaceUnits = Object.freeze({ - none: '0', - xxs: '0.5px', - px: '1px', -} as const); - -const dynamicSpaceUnits = Object.freeze({ - '0x5': '0.125rem', - '1': '0.25rem', - '1x5': '0.375rem', - '2': '0.5rem', - '2x5': '0.625rem', - '3': '0.75rem', - '2x25': '0.8125rem', - '3x5': '0.875rem', - '4': '1rem', - '4x25': '1.0625rem', - '5': '1.25rem', - '6': '1.5rem', - '7': '1.75rem', - '8': '2rem', - '9': '2.25rem', - '10': '2.5rem', - '12': '3rem', - '13': '3.5rem', - '16': '4rem', - '20': '5rem', - '24': '6rem', - '28': '7rem', - '32': '8rem', - '36': '9rem', - '40': '10rem', - '44': '11rem', - '48': '12rem', - '52': '13rem', - '56': '14rem', - '60': '15rem', - '94': '23.5rem', - '100': '25rem', - '120': '30rem', - '140': '35rem', - '160': '40rem', - '176': '44rem', - '220': '55rem', -} as const); - -/** - * Instead of generating these values with the helpers of parseVariables, - * we hard code them in order to have better intellisense support while developing - */ -const space = Object.freeze({ - ...baseSpaceUnits, - ...dynamicSpaceUnits, -} as const); - -const sizes = Object.freeze({ ...space } as const); - -const radii = Object.freeze({ - none: '0px', - circle: '50%', - card: '0.75rem', // TODO: rename to 'xl' (?) once we have the design system - sm: '0.25rem', - md: '0.375rem', - lg: '0.5rem', - xl: '1rem', - '2xl': '1.25rem', - halfHeight: '99999px', -} as const); - -/** - * Used by the space scale generation helpers. - * These keys should always match {@link space} - */ -const spaceScaleKeys = Object.keys(dynamicSpaceUnits) as Array; - -export { sizes, space, radii, spaceScaleKeys }; diff --git a/packages/clerk-js/src/ui.retheme/foundations/transitions.ts b/packages/clerk-js/src/ui.retheme/foundations/transitions.ts deleted file mode 100644 index 094bc7b3341..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/transitions.ts +++ /dev/null @@ -1,21 +0,0 @@ -const transitionDuration = Object.freeze({ - slowest: '600ms', - slower: '280ms', - slow: '200ms', - fast: '120ms', - focusRing: '200ms', - controls: '100ms', - textField: '450ms', -} as const); - -const transitionProperty = Object.freeze({ - common: 'background-color,border-color,color,fill,stroke,opacity,box-shadow,transform', -} as const); - -const transitionTiming = Object.freeze({ - common: 'ease', - easeOut: 'ease-out', - slowBezier: 'cubic-bezier(0.16, 1, 0.3, 1)', -} as const); - -export { transitionDuration, transitionTiming, transitionProperty }; diff --git a/packages/clerk-js/src/ui.retheme/foundations/typography.ts b/packages/clerk-js/src/ui.retheme/foundations/typography.ts deleted file mode 100644 index 797f8e0ca0e..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/typography.ts +++ /dev/null @@ -1,36 +0,0 @@ -const fontWeights = Object.freeze({ - normal: 400, - medium: 500, - bold: 700, -} as const); - -const lineHeights = Object.freeze({ - normal: 'normal', - none: '1rem', - small: '1.125rem', - medium: '1.5rem', - large: '2rem', -} as const); - -const letterSpacings = Object.freeze({ - normal: '0', -} as const); - -const fontSizes = Object.freeze({ - xs: '0.6875rem', - sm: '0.75rem', - md: '0.8125rem', - lg: '1.0625rem', - xl: '1.5rem', -} as const); - -const fontStyles = Object.freeze({ - normal: 'normal', -} as const); - -const fonts = Object.freeze({ - main: 'inherit', - buttons: 'inherit', -} as const); - -export { fontSizes, fontWeights, letterSpacings, lineHeights, fonts, fontStyles }; diff --git a/packages/clerk-js/src/ui.retheme/foundations/zIndices.ts b/packages/clerk-js/src/ui.retheme/foundations/zIndices.ts deleted file mode 100644 index 950e76a86dd..00000000000 --- a/packages/clerk-js/src/ui.retheme/foundations/zIndices.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const zIndices = Object.freeze({ - card: '10', - navbar: '100', - fab: '9000', - modal: '10000', - dropdown: '11000', -} as const); diff --git a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganization.test.tsx b/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganization.test.tsx deleted file mode 100644 index d862e650719..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganization.test.tsx +++ /dev/null @@ -1,406 +0,0 @@ -import { useOrganization } from '@clerk/shared/react'; -import { describe } from '@jest/globals'; - -import { act, renderHook, waitFor } from '../../../testUtils'; -import { - createFakeDomain, - createFakeOrganizationMembershipRequest, -} from '../../components/OrganizationProfile/__tests__/utils'; -import { createFakeUserOrganizationMembership } from '../../components/OrganizationSwitcher/__tests__/utlis'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; - -const { createFixtures } = bindCreateFixtures('OrganizationProfile'); - -const defaultRenderer = () => - useOrganization({ - domains: { - pageSize: 2, - }, - membershipRequests: { - pageSize: 2, - }, - memberships: { - pageSize: 2, - }, - }); - -const undefinedPaginatedResource = { - data: [], - count: 0, - isLoading: false, - isFetching: false, - isError: false, - page: 1, - pageCount: 0, - hasNextPage: false, - hasPreviousPage: false, -}; - -describe('useOrganization', () => { - it('returns default values', async () => { - const { wrapper } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - const { result } = renderHook(() => useOrganization(), { wrapper }); - - expect(result.current.isLoaded).toBe(true); - expect(result.current.organization).toBeDefined(); - expect(result.current.organization).not.toBeNull(); - expect(result.current.organization).toEqual( - expect.objectContaining({ - name: 'Org1', - id: 'Org1', - }), - ); - - expect(result.current.memberships).toEqual(expect.objectContaining(undefinedPaginatedResource)); - expect(result.current.domains).toEqual(expect.objectContaining(undefinedPaginatedResource)); - expect(result.current.membershipRequests).toEqual(expect.objectContaining(undefinedPaginatedResource)); - }); - - it('returns null when a organization is not active ', async () => { - const { wrapper } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - }); - }); - - const { result } = renderHook(() => useOrganization(), { wrapper }); - - expect(result.current.isLoaded).toBe(true); - expect(result.current.organization).toBeNull(); - - expect(result.current.memberships).toBeNull(); - expect(result.current.domains).toBeNull(); - expect(result.current.membershipRequests).toBeNull(); - }); - - describe('memberships', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.organization?.getMemberships.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '1', - organization: { - id: '1', - name: 'Org1', - slug: 'org1', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '2', - organization: { - id: '2', - name: 'Org2', - slug: 'org2', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook(defaultRenderer, { wrapper }); - - expect(result.current.memberships).not.toBeNull(); - expect(result.current.memberships?.isLoading).toBe(true); - expect(result.current.memberships?.isFetching).toBe(true); - expect(result.current.memberships?.count).toBe(0); - - await waitFor(() => expect(result.current.memberships?.isLoading).toBe(false)); - - expect(result.current.memberships?.count).toBe(4); - expect(result.current.memberships?.page).toBe(1); - expect(result.current.memberships?.pageCount).toBe(2); - expect(result.current.memberships?.hasNextPage).toBe(true); - expect(result.current.memberships?.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '1', - }), - expect.objectContaining({ - id: '2', - }), - ]), - ); - - fixtures.clerk.organization?.getMemberships.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '3', - organization: { - id: '3', - name: 'Org3', - slug: 'org3', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '4', - organization: { - id: '4', - name: 'Org4', - slug: 'org4', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.memberships?.fetchNext?.()); - - await waitFor(() => expect(result.current.memberships?.isLoading).toBe(true)); - await waitFor(() => expect(result.current.memberships?.isLoading).toBe(false)); - - expect(result.current.memberships?.page).toBe(2); - expect(result.current.memberships?.hasNextPage).toBe(false); - expect(result.current.memberships?.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - }); - - describe('domains', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.organization?.getDomains.mockReturnValue( - Promise.resolve({ - data: [ - createFakeDomain({ - id: '1', - name: 'one.dev', - organizationId: '1', - }), - createFakeDomain({ - id: '2', - name: 'two.dev', - organizationId: '2', - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook(defaultRenderer, { wrapper }); - expect(result.current.domains?.isLoading).toBe(true); - expect(result.current.domains?.isFetching).toBe(true); - expect(result.current.domains?.count).toBe(0); - - await waitFor(() => expect(result.current.domains?.isLoading).toBe(false)); - - expect(result.current.domains?.isLoading).toBe(false); - expect(result.current.domains?.count).toBe(4); - expect(result.current.domains?.page).toBe(1); - expect(result.current.domains?.pageCount).toBe(2); - expect(result.current.domains?.hasNextPage).toBe(true); - expect(result.current.domains?.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '1', - name: 'one.dev', - }), - expect.objectContaining({ - id: '2', - name: 'two.dev', - }), - ]), - ); - - fixtures.clerk.organization?.getDomains.mockReturnValue( - Promise.resolve({ - data: [ - createFakeDomain({ - id: '3', - name: 'three.dev', - organizationId: '3', - }), - createFakeDomain({ - id: '4', - name: 'four.dev', - organizationId: '4', - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.domains?.fetchNext?.()); - - await waitFor(() => expect(result.current.domains?.isLoading).toBe(true)); - await waitFor(() => expect(result.current.domains?.isLoading).toBe(false)); - - expect(result.current.domains?.page).toBe(2); - expect(result.current.domains?.hasNextPage).toBe(false); - expect(result.current.domains?.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - }); - - describe('membershipRequests', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.organization?.getMembershipRequests.mockReturnValue( - Promise.resolve({ - data: [ - createFakeOrganizationMembershipRequest({ - id: '1', - organizationId: '1', - publicUserData: { - userId: 'test_user1', - identifier: 'test1@clerk.com', - }, - }), - createFakeOrganizationMembershipRequest({ - id: '2', - organizationId: '1', - publicUserData: { - userId: 'test_user2', - identifier: 'test2@clerk.com', - }, - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook(defaultRenderer, { wrapper }); - expect(result.current.membershipRequests?.isLoading).toBe(true); - expect(result.current.membershipRequests?.isFetching).toBe(true); - expect(result.current.membershipRequests?.count).toBe(0); - - await waitFor(() => expect(result.current.membershipRequests?.isLoading).toBe(false)); - - expect(result.current.membershipRequests?.isFetching).toBe(false); - expect(result.current.membershipRequests?.count).toBe(4); - expect(result.current.membershipRequests?.page).toBe(1); - expect(result.current.membershipRequests?.pageCount).toBe(2); - expect(result.current.membershipRequests?.hasNextPage).toBe(true); - - fixtures.clerk.organization?.getMembershipRequests.mockReturnValue( - Promise.resolve({ - data: [ - createFakeOrganizationMembershipRequest({ - id: '3', - organizationId: '1', - publicUserData: { - userId: 'test_user3', - identifier: 'test3@clerk.com', - }, - }), - createFakeOrganizationMembershipRequest({ - id: '4', - organizationId: '1', - publicUserData: { - userId: 'test_user4', - identifier: 'test4@clerk.com', - }, - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.membershipRequests?.fetchNext?.()); - - await waitFor(() => expect(result.current.membershipRequests?.isLoading).toBe(true)); - await waitFor(() => expect(result.current.membershipRequests?.isLoading).toBe(false)); - - expect(result.current.membershipRequests?.page).toBe(2); - expect(result.current.membershipRequests?.hasNextPage).toBe(false); - expect(result.current.membershipRequests?.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - organizationId: '1', - id: '3', - publicUserData: expect.objectContaining({ - userId: 'test_user3', - }), - }), - expect.objectContaining({ - organizationId: '1', - id: '4', - publicUserData: expect.objectContaining({ - userId: 'test_user4', - }), - }), - ]), - ); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganizationList.test.tsx b/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganizationList.test.tsx deleted file mode 100644 index 1f7a4a4137a..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useCoreOrganizationList.test.tsx +++ /dev/null @@ -1,664 +0,0 @@ -import { useOrganizationList } from '@clerk/shared/react'; -import { describe } from '@jest/globals'; - -import { act, renderHook, waitFor } from '../../../testUtils'; -import { - createFakeUserOrganizationInvitation, - createFakeUserOrganizationMembership, - createFakeUserOrganizationSuggestion, -} from '../../components/OrganizationSwitcher/__tests__/utlis'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; - -const { createFixtures } = bindCreateFixtures('OrganizationSwitcher'); - -const defaultRenderer = () => - useOrganizationList({ - userMemberships: { - pageSize: 2, - }, - userInvitations: { - pageSize: 2, - }, - userSuggestions: { - pageSize: 2, - }, - }); - -describe('useOrganizationList', () => { - it('opens organization profile when "Manage Organization" is clicked', async () => { - const { wrapper } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - const { result } = renderHook(() => useOrganizationList(), { wrapper }); - expect(result.current.isLoaded).toBe(true); - expect(result.current.setActive).toBeDefined(); - expect(result.current.createOrganization).toBeDefined(); - - expect(result.current.userInvitations).toEqual( - expect.objectContaining({ - data: [], - count: 0, - isLoading: false, - isFetching: false, - isError: false, - page: 1, - pageCount: 0, - hasNextPage: false, - hasPreviousPage: false, - }), - ); - }); - - describe('userMemberships', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationMemberships.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '1', - organization: { - id: '1', - name: 'Org1', - slug: 'org1', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '2', - organization: { - id: '2', - name: 'Org2', - slug: 'org2', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook(defaultRenderer, { wrapper }); - expect(result.current.userMemberships.isLoading).toBe(true); - expect(result.current.userMemberships.isFetching).toBe(true); - expect(result.current.userMemberships.count).toBe(0); - - await waitFor(() => expect(result.current.userMemberships.isLoading).toBe(false)); - - expect(result.current.userMemberships.count).toBe(4); - expect(result.current.userMemberships.page).toBe(1); - expect(result.current.userMemberships.pageCount).toBe(2); - expect(result.current.userMemberships.hasNextPage).toBe(true); - - fixtures.clerk.user?.getOrganizationMemberships.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '3', - organization: { - id: '3', - name: 'Org3', - slug: 'org3', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '4', - organization: { - id: '4', - name: 'Org4', - slug: 'org4', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userMemberships?.fetchNext?.()); - - await waitFor(() => expect(result.current.userMemberships?.isLoading).toBe(true)); - await waitFor(() => expect(result.current.userMemberships?.isLoading).toBe(false)); - - expect(result.current.userMemberships.page).toBe(2); - expect(result.current.userMemberships.hasNextPage).toBe(false); - expect(result.current.userMemberships.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - - it('infinite fetch', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationMemberships.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '1', - organization: { - id: '1', - name: 'Org1', - slug: 'org1', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '2', - organization: { - id: '2', - name: 'Org2', - slug: 'org2', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook( - () => - useOrganizationList({ - userMemberships: { - pageSize: 2, - infinite: true, - }, - }), - { wrapper }, - ); - expect(result.current.userMemberships.isLoading).toBe(true); - expect(result.current.userMemberships.isFetching).toBe(true); - - await waitFor(() => expect(result.current.userMemberships.isLoading).toBe(false)); - expect(result.current.userMemberships.isFetching).toBe(false); - - fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '1', - organization: { - id: '1', - name: 'Org1', - slug: 'org1', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '2', - organization: { - id: '2', - name: 'Org2', - slug: 'org2', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - - fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationMembership({ - id: '3', - organization: { - id: '3', - name: 'Org3', - slug: 'org3', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - createFakeUserOrganizationMembership({ - id: '4', - organization: { - id: '4', - name: 'Org4', - slug: 'org4', - membersCount: 1, - adminDeleteEnabled: false, - maxAllowedMemberships: 0, - pendingInvitationsCount: 1, - }, - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userMemberships?.fetchNext?.()); - - await waitFor(() => expect(result.current.userMemberships?.isFetching).toBe(true)); - expect(result.current.userMemberships?.isLoading).toBe(false); - - await waitFor(() => expect(result.current.userMemberships?.isFetching).toBe(false)); - expect(result.current.userMemberships.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '1', - }), - expect.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - }); - - describe('userInvitations', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationInvitations.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationInvitation({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationInvitation({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - - const { result } = renderHook(defaultRenderer, { wrapper }); - - expect(result.current.userInvitations.isLoading).toBe(true); - expect(result.current.userInvitations.isFetching).toBe(true); - expect(result.current.userInvitations.count).toBe(0); - - await waitFor(() => expect(result.current.userInvitations.isLoading).toBe(false)); - - expect(result.current.userInvitations.count).toBe(4); - expect(result.current.userInvitations.page).toBe(1); - expect(result.current.userInvitations.pageCount).toBe(2); - expect(result.current.userInvitations.hasNextPage).toBe(true); - - fixtures.clerk.user?.getOrganizationInvitations.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationInvitation({ - id: '3', - emailAddress: 'three@clerk.com', - }), - createFakeUserOrganizationInvitation({ - id: '4', - emailAddress: 'four@clerk.com', - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userInvitations?.fetchNext?.()); - - await waitFor(() => expect(result.current.userInvitations?.isLoading).toBe(true)); - await waitFor(() => expect(result.current.userInvitations?.isLoading).toBe(false)); - - expect(result.current.userInvitations.page).toBe(2); - expect(result.current.userInvitations.hasNextPage).toBe(false); - expect(result.current.userInvitations.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - - it('infinite fetch', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationInvitations.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationInvitation({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationInvitation({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook( - () => - useOrganizationList({ - userInvitations: { - pageSize: 2, - infinite: true, - }, - }), - { wrapper }, - ); - - expect(result.current.userInvitations.isLoading).toBe(true); - expect(result.current.userInvitations.isFetching).toBe(true); - - await waitFor(() => expect(result.current.userInvitations.isLoading).toBe(false)); - expect(result.current.userInvitations.isFetching).toBe(false); - - fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationInvitation({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationInvitation({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - - fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationInvitation({ - id: '3', - emailAddress: 'three@clerk.com', - }), - createFakeUserOrganizationInvitation({ - id: '4', - emailAddress: 'four@clerk.com', - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userInvitations.fetchNext?.()); - - await waitFor(() => expect(result.current.userInvitations.isFetching).toBe(true)); - expect(result.current.userInvitations.isLoading).toBe(false); - - await waitFor(() => expect(result.current.userInvitations.isFetching).toBe(false)); - expect(result.current.userInvitations.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '1', - }), - expect.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - }); - - describe('userSuggestions', () => { - it('fetch with pages', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationSuggestion({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationSuggestion({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook(defaultRenderer, { wrapper }); - expect(result.current.userSuggestions.isLoading).toBe(true); - expect(result.current.userSuggestions.isFetching).toBe(true); - expect(result.current.userSuggestions.count).toBe(0); - - await waitFor(() => expect(result.current.userSuggestions.isLoading).toBe(false)); - - expect(result.current.userSuggestions.count).toBe(4); - expect(result.current.userSuggestions.page).toBe(1); - expect(result.current.userSuggestions.pageCount).toBe(2); - expect(result.current.userSuggestions.hasNextPage).toBe(true); - - fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationSuggestion({ - id: '3', - emailAddress: 'three@clerk.com', - }), - createFakeUserOrganizationSuggestion({ - id: '4', - emailAddress: 'four@clerk.com', - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userSuggestions.fetchNext?.()); - - await waitFor(() => expect(result.current.userSuggestions.isLoading).toBe(true)); - await waitFor(() => expect(result.current.userSuggestions.isLoading).toBe(false)); - - expect(result.current.userSuggestions.page).toBe(2); - expect(result.current.userSuggestions.hasNextPage).toBe(false); - expect(result.current.userSuggestions.data).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - id: '1', - }), - expect.not.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - - it('infinite fetch', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withOrganizations(); - f.withUser({ - email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], - }); - }); - - fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValue( - Promise.resolve({ - data: [ - createFakeUserOrganizationSuggestion({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationSuggestion({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - const { result } = renderHook( - () => - useOrganizationList({ - userSuggestions: { - pageSize: 2, - infinite: true, - }, - }), - { wrapper }, - ); - expect(result.current.userSuggestions.isLoading).toBe(true); - expect(result.current.userSuggestions.isFetching).toBe(true); - - await waitFor(() => expect(result.current.userSuggestions.isLoading).toBe(false)); - expect(result.current.userSuggestions.isFetching).toBe(false); - - fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationSuggestion({ - id: '1', - emailAddress: 'one@clerk.com', - }), - createFakeUserOrganizationSuggestion({ - id: '2', - emailAddress: 'two@clerk.com', - }), - ], - total_count: 4, - }), - ); - - fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce( - Promise.resolve({ - data: [ - createFakeUserOrganizationSuggestion({ - id: '3', - emailAddress: 'three@clerk.com', - }), - createFakeUserOrganizationSuggestion({ - id: '4', - emailAddress: 'four@clerk.com', - }), - ], - total_count: 4, - }), - ); - - act(() => result.current.userSuggestions.fetchNext?.()); - - await waitFor(() => expect(result.current.userSuggestions.isFetching).toBe(true)); - expect(result.current.userSuggestions.isLoading).toBe(false); - - await waitFor(() => expect(result.current.userSuggestions.isFetching).toBe(false)); - expect(result.current.userSuggestions.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '1', - }), - expect.objectContaining({ - id: '2', - }), - expect.objectContaining({ - id: '3', - }), - expect.objectContaining({ - id: '4', - }), - ]), - ); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/hooks/__tests__/usePasswordComplexity.test.tsx b/packages/clerk-js/src/ui.retheme/hooks/__tests__/usePasswordComplexity.test.tsx deleted file mode 100644 index bb67dd585ea..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/__tests__/usePasswordComplexity.test.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { act, renderHook } from '../../../testUtils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; -import { usePasswordComplexity } from '../usePasswordComplexity'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -const defaultRenderer = () => - usePasswordComplexity({ - allowed_special_characters: '', - max_length: 999, - min_length: 8, - require_special_char: true, - require_numbers: true, - require_lowercase: true, - require_uppercase: true, - }); - -describe('usePasswordComplexity', () => { - it('internal passwords updates after calling setPassword', async () => { - const { wrapper } = await createFixtures(); - const { result } = renderHook(defaultRenderer, { wrapper }); - - await act(() => { - result.current.getComplexity('password1'); - }); - - expect(result.current.password).toBe('password1'); - }); - - it('password fails and hasFailedComplexity is true', async () => { - const { wrapper } = await createFixtures(); - const { result } = renderHook(defaultRenderer, { wrapper }); - - await act(() => { - result.current.getComplexity('thispasswordfails'); - }); - - expect(result.current.hasFailedComplexity).toBe(true); - expect(result.current.hasPassedComplexity).toBe(false); - }); - - it('password passes and hasPassedComplexity is true', async () => { - const { wrapper } = await createFixtures(); - const { result } = renderHook(defaultRenderer, { wrapper }); - - await act(() => { - result.current.getComplexity('th1sp@sswordPasses'); - }); - - expect(result.current.hasFailedComplexity).toBe(false); - expect(result.current.hasPassedComplexity).toBe(true); - }); - - it('returns object with the missing requirements as properties', async () => { - const { wrapper } = await createFixtures(); - const { result } = renderHook(defaultRenderer, { wrapper }); - - await act(() => { - result.current.getComplexity('thispasswordfails'); - }); - - expect(result.current.failedValidations).toHaveProperty('require_uppercase'); - expect(result.current.failedValidations).toHaveProperty('require_numbers'); - expect(result.current.failedValidations).toHaveProperty('require_special_char'); - expect(result.current.failedValidations).not.toHaveProperty('require_lowercase'); - expect(result.current.failedValidations).not.toHaveProperty('max_length'); - expect(result.current.failedValidations).not.toHaveProperty('min_length'); - - await act(() => { - result.current.getComplexity(`thispasswordfails"`); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - }); - - it('uses allowed_special_character from environment', async () => { - const { wrapper, fixtures } = await createFixtures(f => - f.withPasswordComplexity({ - allowed_special_characters: '@', - max_length: 999, - min_length: 8, - require_special_char: true, - require_numbers: true, - require_lowercase: true, - require_uppercase: true, - }), - ); - const { result } = renderHook(() => usePasswordComplexity(fixtures.environment.userSettings.passwordSettings), { - wrapper, - }); - - await act(() => { - result.current.getComplexity('@'); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - }); - - it('uses allowed_special_character from environment with escaped characters', async () => { - const { wrapper, fixtures } = await createFixtures(f => - f.withPasswordComplexity({ - allowed_special_characters: '[!"#$%&\'()*+,-./:;<=>?@^_`{|}~]', - max_length: 999, - min_length: 8, - require_special_char: true, - require_numbers: true, - require_lowercase: true, - require_uppercase: true, - }), - ); - const { result } = renderHook(() => usePasswordComplexity(fixtures.environment.userSettings.passwordSettings), { - wrapper, - }); - - await act(() => { - result.current.getComplexity('['); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - - await act(() => { - result.current.getComplexity(']'); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - - await act(() => { - result.current.getComplexity('[test]'); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - - await act(() => { - result.current.getComplexity('test[]'); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - - await act(() => { - result.current.getComplexity('[!"#$%&\'()*+,-./:;<=>?@^_`{|}~]'); - }); - - expect(result.current.failedValidations).not.toHaveProperty('require_special_char'); - }); - - it('returns error message with localized conjunction', async () => { - const { wrapper } = await createFixtures(); - const { result } = renderHook(defaultRenderer, { wrapper }); - - await act(() => { - result.current.getComplexity('@apapapap'); - }); - - expect(result.current.failedValidationsText).toBe('Your password must contain a number and an uppercase letter.'); - - await act(() => { - result.current.getComplexity('aPaPaPaPaP'); - }); - - expect(result.current.failedValidationsText).toBe('Your password must contain a special character and a number.'); - - await act(() => { - result.current.getComplexity('aP'); - }); - - expect(result.current.failedValidationsText).toBe('Your password must contain 8 or more characters.'); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useSupportEmail.test.tsx b/packages/clerk-js/src/ui.retheme/hooks/__tests__/useSupportEmail.test.tsx deleted file mode 100644 index 7f31c0dc218..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/__tests__/useSupportEmail.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import { useSupportEmail } from '../useSupportEmail'; - -const mockUseOptions = jest.fn(); -const mockUseEnvironment = jest.fn(); - -jest.mock('@clerk/shared/react', () => ({ - useClerk: () => ({ - publishableKey: 'pk_live_Y2xlcmsuY2xlcmsuY29tJA', - }), -})); -jest.mock('../../contexts', () => { - return { - useEnvironment: () => mockUseEnvironment(), - useOptions: () => mockUseOptions(), - }; -}); - -describe('useSupportEmail', () => { - test('should use custom email when provided from options', () => { - mockUseOptions.mockImplementationOnce(() => ({ supportEmail: 'test@email.com' })); - mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: null } })); - const { result } = renderHook(() => useSupportEmail()); - - expect(result.current).toBe('test@email.com'); - }); - - test('should use custom email when provided from the environment', () => { - mockUseOptions.mockImplementationOnce(() => ({})); - mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: 'test@email.com' } })); - const { result } = renderHook(() => useSupportEmail()); - - expect(result.current).toBe('test@email.com'); - }); - - test('should fallback to default when supportEmail is not provided in options or the environment', () => { - mockUseOptions.mockImplementationOnce(() => ({})); - mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: null } })); - const { result } = renderHook(() => useSupportEmail()); - - expect(result.current).toBe('support@clerk.com'); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/hooks/index.ts b/packages/clerk-js/src/ui.retheme/hooks/index.ts deleted file mode 100644 index 5679be23e38..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export * from './useDelayedVisibility'; -export * from './useSaml'; -export * from './useWindowEventListener'; -export * from './useEmailLink'; -export * from './useClipboard'; -export * from './useEnabledThirdPartyProviders'; -export * from './useFetch'; -export * from './useInView'; -export * from './useLoadingStatus'; -export * from './usePassword'; -export * from './usePasswordComplexity'; -export * from './usePopover'; -export * from './usePrefersReducedMotion'; -export * from './useLocalStorage'; -export * from './useResizeObserver'; -export * from './useSafeState'; -export * from './useSearchInput'; -export * from './useDebounce'; -export * from './useScrollLock'; -export * from './useDeepEqualMemo'; -export * from './useClerkModalStateParams'; -export * from './useNavigateToFlowStart'; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useAlternativeStrategies.ts b/packages/clerk-js/src/ui.retheme/hooks/useAlternativeStrategies.ts deleted file mode 100644 index 32967b1f662..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useAlternativeStrategies.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { SignInFactor } from '@clerk/types'; - -import { factorHasLocalStrategy, isResetPasswordStrategy } from '../components/SignIn/utils'; -import { useCoreSignIn } from '../contexts'; -import { allStrategiesButtonsComparator } from '../utils'; -import { useEnabledThirdPartyProviders } from './useEnabledThirdPartyProviders'; - -export function useAlternativeStrategies({ filterOutFactor }: { filterOutFactor: SignInFactor | null | undefined }) { - const { supportedFirstFactors } = useCoreSignIn(); - - const { strategies: OAuthStrategies } = useEnabledThirdPartyProviders(); - - const firstFactors = supportedFirstFactors.filter( - f => f.strategy !== filterOutFactor?.strategy && !isResetPasswordStrategy(f.strategy), - ); - - const shouldAllowForAlternativeStrategies = firstFactors.length + OAuthStrategies.length > 0; - - const firstPartyFactors = supportedFirstFactors - .filter(f => !f.strategy.startsWith('oauth_') && !(f.strategy === filterOutFactor?.strategy)) - .filter(factor => factorHasLocalStrategy(factor)) - .sort(allStrategiesButtonsComparator); - - return { - hasAnyStrategy: shouldAllowForAlternativeStrategies, - hasFirstParty: !!firstPartyFactors, - firstPartyFactors, - }; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useClerkModalStateParams.tsx b/packages/clerk-js/src/ui.retheme/hooks/useClerkModalStateParams.tsx deleted file mode 100644 index 282559ea50f..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useClerkModalStateParams.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { CLERK_MODAL_STATE } from '../../core/constants'; -import { readStateParam, removeClerkQueryParam } from '../../utils'; - -export const useClerkModalStateParams = () => { - const [state, setState] = React.useState({ startPath: '', path: '', componentName: '', socialProvider: '' }); - const decodedRedirectParams = readStateParam(); - - React.useLayoutEffect(() => { - if (decodedRedirectParams) { - setState(decodedRedirectParams); - } - }, []); - - const clearUrlStateParam = () => { - setState({ startPath: '', path: '', componentName: '', socialProvider: '' }); - }; - - const removeQueryParam = () => removeClerkQueryParam(CLERK_MODAL_STATE); - - return { - urlStateParam: { ...state, clearUrlStateParam }, - decodedRedirectParams, - clearUrlStateParam, - removeQueryParam, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useClipboard.ts b/packages/clerk-js/src/ui.retheme/hooks/useClipboard.ts deleted file mode 100644 index c0ed9fcd766..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useClipboard.ts +++ /dev/null @@ -1,52 +0,0 @@ -import copy from 'copy-to-clipboard'; -import { useCallback, useEffect, useState } from 'react'; - -export interface UseClipboardOptions { - /** - * timeout delay (in ms) to switch back to initial state once copied. - */ - timeout?: number; - /** - * Set the desired MIME type - */ - format?: string; -} - -/** - * React hook to copy content to clipboard - * - * @param text the text or value to copy - * @param {Number} [optionsOrTimeout=1500] optionsOrTimeout - delay (in ms) to switch back to initial state once copied. - * @param {Object} optionsOrTimeout - * @param {string} optionsOrTimeout.format - set the desired MIME type - * @param {number} optionsOrTimeout.timeout - delay (in ms) to switch back to initial state once copied. - */ -export function useClipboard(text: string, optionsOrTimeout: number | UseClipboardOptions = {}) { - const [hasCopied, setHasCopied] = useState(false); - - const { timeout = 1500, ...copyOptions } = - typeof optionsOrTimeout === 'number' ? { timeout: optionsOrTimeout } : optionsOrTimeout; - - const onCopy = useCallback(() => { - const didCopy = copy(text, copyOptions); - setHasCopied(didCopy); - }, [text, copyOptions]); - - useEffect(() => { - let timeoutId: number | null = null; - - if (hasCopied) { - timeoutId = window.setTimeout(() => { - setHasCopied(false); - }, timeout); - } - - return () => { - if (timeoutId) { - window.clearTimeout(timeoutId); - } - }; - }, [timeout, hasCopied]); - - return { value: text, onCopy, hasCopied }; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useDebounce.ts b/packages/clerk-js/src/ui.retheme/hooks/useDebounce.ts deleted file mode 100644 index b9b28fd66cb..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useDebounce.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useState } from 'react'; - -export function useDebounce(value: T, delayInMs?: number): T { - const [debouncedValue, setDebouncedValue] = useState(value); - const [timeoutState, setTimeoutState] = useState | undefined>(undefined); - - useEffect(() => { - const handleDebounce = () => { - if (timeoutState) { - clearTimeout(timeoutState); - setTimeoutState(undefined); - } - - setTimeoutState( - setTimeout(() => { - setDebouncedValue(value); - setTimeoutState(undefined); - }, delayInMs || 500), - ); - }; - - handleDebounce(); - return () => { - if (timeoutState) { - clearTimeout(timeoutState); - setTimeoutState(undefined); - } - }; - }, [JSON.stringify(value), delayInMs]); - - return debouncedValue; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useDeepEqualMemo.ts b/packages/clerk-js/src/ui.retheme/hooks/useDeepEqualMemo.ts deleted file mode 100644 index d86a60666b5..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useDeepEqualMemo.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { dequal as deepEqual } from 'dequal'; -import React from 'react'; - -type UseMemoFactory = () => T; -type UseMemoDependencyArray = Exclude[1], 'undefined'>; -type UseDeepEqualMemo = (factory: UseMemoFactory, dependencyArray: UseMemoDependencyArray) => T; - -const useDeepEqualMemoize = (value: T) => { - const ref = React.useRef(value); - if (!deepEqual(value, ref.current)) { - ref.current = value; - } - return React.useMemo(() => ref.current, [ref.current]); -}; - -export const useDeepEqualMemo: UseDeepEqualMemo = (factory, dependencyArray) => { - return React.useMemo(factory, useDeepEqualMemoize(dependencyArray)); -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useDelayedVisibility.ts b/packages/clerk-js/src/ui.retheme/hooks/useDelayedVisibility.ts deleted file mode 100644 index 62713780bd3..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useDelayedVisibility.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useState } from 'react'; - -/** - * Utility hook for delaying mounting of components for enter and exit animations. - * Delays to update the state when is switched from/to undefined. - * Immediate change for in-between changes - */ -export function useDelayedVisibility(valueToDelay: T, delayInMs: number) { - const [isVisible, setVisible] = useState(undefined); - - useEffect(() => { - let timeoutId: ReturnType; - - if (valueToDelay && !isVisible) { - // First time that valueToDelay has truthy value means we want to display it - timeoutId = setTimeout(() => setVisible(valueToDelay), delayInMs); - } else if (!valueToDelay && isVisible) { - // valueToDelay has already a truthy value and becomes falsy means we want to hide it - timeoutId = setTimeout(() => setVisible(undefined), delayInMs); - } else { - // it is already displayed, and we want immediate updates to that value - setVisible(valueToDelay); - } - return () => clearTimeout(timeoutId); - }, [valueToDelay, delayInMs, isVisible]); - - return isVisible; -} - -export function useFieldMessageVisibility(fieldMessage: T, delayInMs: number) { - return useDelayedVisibility(fieldMessage, delayInMs); -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useEmailLink.ts b/packages/clerk-js/src/ui.retheme/hooks/useEmailLink.ts deleted file mode 100644 index 35dd8f83142..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useEmailLink.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { - CreateEmailLinkFlowReturn, - EmailAddressResource, - SignInResource, - SignInStartEmailLinkFlowParams, - SignUpResource, - StartEmailLinkFlowParams, -} from '@clerk/types'; -import React from 'react'; - -type EmailLinkable = SignUpResource | EmailAddressResource | SignInResource; -type UseEmailLinkSignInReturn = CreateEmailLinkFlowReturn; -type UseEmailLinkSignUpReturn = CreateEmailLinkFlowReturn; -type UseEmailLinkEmailAddressReturn = CreateEmailLinkFlowReturn; - -function useEmailLink(resource: SignInResource): UseEmailLinkSignInReturn; -function useEmailLink(resource: SignUpResource): UseEmailLinkSignUpReturn; -function useEmailLink(resource: EmailAddressResource): UseEmailLinkEmailAddressReturn; -function useEmailLink( - resource: EmailLinkable, -): UseEmailLinkSignInReturn | UseEmailLinkSignUpReturn | UseEmailLinkEmailAddressReturn { - const { startEmailLinkFlow, cancelEmailLinkFlow } = React.useMemo(() => resource.createEmailLinkFlow(), [resource]); - - React.useEffect(() => { - return cancelEmailLinkFlow; - }, []); - - return { - startEmailLinkFlow, - cancelEmailLinkFlow, - } as UseEmailLinkSignInReturn | UseEmailLinkSignUpReturn | UseEmailLinkEmailAddressReturn; -} - -export { useEmailLink }; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useEnabledThirdPartyProviders.tsx b/packages/clerk-js/src/ui.retheme/hooks/useEnabledThirdPartyProviders.tsx deleted file mode 100644 index 1063ba7a23e..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useEnabledThirdPartyProviders.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { OAuthProvider, OAuthStrategy, Web3Provider, Web3Strategy } from '@clerk/types'; -// TODO: This import shouldn't be part of @clerk/types -import { OAUTH_PROVIDERS, WEB3_PROVIDERS } from '@clerk/types'; - -import { iconImageUrl } from '../common/constants'; -import { useEnvironment } from '../contexts/EnvironmentContext'; -import { fromEntries } from '../utils'; - -type ThirdPartyStrategyToDataMap = { - [k in Web3Strategy | OAuthStrategy]: { - id: Web3Provider | OAuthProvider; - iconUrl: string; - name: string; - }; -}; - -type ThirdPartyProviderToDataMap = { - [k in Web3Provider | OAuthProvider]: { - strategy: Web3Strategy | OAuthStrategy; - iconUrl: string; - name: string; - }; -}; - -const oauthStrategies = OAUTH_PROVIDERS.map(p => p.strategy); - -const providerToDisplayData: ThirdPartyProviderToDataMap = fromEntries( - [...OAUTH_PROVIDERS, ...WEB3_PROVIDERS].map(p => { - return [p.provider, { strategy: p.strategy, name: p.name, iconUrl: iconImageUrl(p.provider) }]; - }), -) as ThirdPartyProviderToDataMap; - -const strategyToDisplayData: ThirdPartyStrategyToDataMap = fromEntries( - [...OAUTH_PROVIDERS, ...WEB3_PROVIDERS].map(p => { - return [p.strategy, { id: p.provider, name: p.name, iconUrl: iconImageUrl(p.provider) }]; - }), -) as ThirdPartyStrategyToDataMap; - -export const useEnabledThirdPartyProviders = () => { - const { socialProviderStrategies, web3FirstFactors, authenticatableSocialStrategies } = 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 knownAuthenticatableSocialStrategies = authenticatableSocialStrategies.filter(s => oauthStrategies.includes(s)); - - return { - strategies: [...knownSocialProviderStrategies, ...web3FirstFactors], - web3Strategies: [...web3FirstFactors], - authenticatableOauthStrategies: [...knownAuthenticatableSocialStrategies], - strategyToDisplayData: strategyToDisplayData, - providerToDisplayData: providerToDisplayData, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useFetch.ts b/packages/clerk-js/src/ui.retheme/hooks/useFetch.ts deleted file mode 100644 index 87c81d43477..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useFetch.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect, useRef } from 'react'; - -import { useLoadingStatus } from './useLoadingStatus'; -import { useSafeState } from './useSafeState'; - -export const useFetch = ( - fetcher: ((...args: any) => Promise) | undefined, - params: any, - callbacks?: { - onSuccess?: (data: T) => void; - }, -) => { - const [data, setData] = useSafeState(null); - const requestStatus = useLoadingStatus({ - status: 'loading', - }); - - const fetcherRef = useRef(fetcher); - - useEffect(() => { - if (!fetcherRef.current) { - return; - } - requestStatus.setLoading(); - fetcherRef - .current(params) - .then(result => { - requestStatus.setIdle(); - if (typeof result !== 'undefined') { - setData(typeof result === 'object' ? { ...result } : result); - callbacks?.onSuccess?.(typeof result === 'object' ? { ...result } : result); - } - }) - .catch(() => { - requestStatus.setError(); - setData(null); - }); - }, [JSON.stringify(params)]); - - return { - status: requestStatus, - data, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useFetchRoles.ts b/packages/clerk-js/src/ui.retheme/hooks/useFetchRoles.ts deleted file mode 100644 index b685c4337d8..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useFetchRoles.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useOrganization } from '@clerk/shared/react'; - -import { useLocalizations } from '../localization'; -import { customRoleLocalizationKey, roleLocalizationKey } from '../utils'; -import { useFetch } from './useFetch'; - -const getRolesParams = { - /** - * Fetch at most 20 roles, it is not expected for an app to have more. - * We also prevent the creation of more than 20 roles in dashboard. - */ - pageSize: 20, -}; -export const useFetchRoles = () => { - const { organization } = useOrganization(); - const { data, status } = useFetch(organization?.getRoles, getRolesParams); - - return { - isLoading: status.isLoading, - options: data?.data?.map(role => ({ value: role.key, label: role.name })), - }; -}; - -export const useLocalizeCustomRoles = () => { - const { t } = useLocalizations(); - return { - localizeCustomRole: (param: string | undefined) => - t(customRoleLocalizationKey(param)) || t(roleLocalizationKey(param)), - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useInView.ts b/packages/clerk-js/src/ui.retheme/hooks/useInView.ts deleted file mode 100644 index f9e9258f443..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useInView.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useCallback, useRef, useState } from 'react'; - -interface IntersectionOptions extends IntersectionObserverInit { - /** Only trigger the inView callback once */ - triggerOnce?: boolean; - /** Call this function whenever the in view state changes */ - onChange?: (inView: boolean, entry: IntersectionObserverEntry) => void; -} - -/** - * A custom React hook that provides the ability to track whether an element is in view - * based on the IntersectionObserver API. - * - * @param {IntersectionOptions} params - IntersectionObserver configuration options. - * @returns {{ - * inView: boolean, - * ref: (element: HTMLElement | null) => void - * }} An object containing the current inView status and a ref function to attach to the target element. - */ -export const useInView = (params: IntersectionOptions) => { - const [inView, setInView] = useState(false); - const observerRef = useRef(null); - const thresholds = Array.isArray(params.threshold) ? params.threshold : [params.threshold || 0]; - const internalOnChange = useRef(); - - internalOnChange.current = params.onChange; - - const ref = useCallback((element: HTMLElement | null) => { - // Callback refs are called with null to clear the value, so we rely on that to cleanup the observer. (ref: https://react.dev/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback) - if (!element) { - if (observerRef.current) { - observerRef.current.disconnect(); - } - return; - } - - observerRef.current = new IntersectionObserver( - entries => { - entries.forEach(entry => { - const _inView = entry.isIntersecting && thresholds.some(threshold => entry.intersectionRatio >= threshold); - - setInView(_inView); - - if (internalOnChange.current) { - internalOnChange.current(_inView, entry); - } - }); - }, - { - root: params.root, - rootMargin: params.rootMargin, - threshold: thresholds, - }, - ); - - observerRef.current.observe(element); - }, []); - - return { - inView, - ref, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useLoadingStatus.ts b/packages/clerk-js/src/ui.retheme/hooks/useLoadingStatus.ts deleted file mode 100644 index 95f9aec950d..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useLoadingStatus.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useSafeState } from './useSafeState'; - -type Status = 'idle' | 'loading' | 'error'; - -export const useLoadingStatus = (initialState?: { status: Status; metadata?: Metadata | undefined }) => { - const [state, setState] = useSafeState<{ status: Status; metadata?: Metadata | undefined }>({ - status: 'idle', - metadata: undefined, - ...initialState, - }); - - return { - status: state.status, - setIdle: (metadata?: Metadata) => setState({ status: 'idle', metadata }), - setError: (metadata?: Metadata) => setState({ status: 'error', metadata }), - setLoading: (metadata?: Metadata) => setState({ status: 'loading', metadata }), - loadingMetadata: state.status === 'loading' ? state.metadata : undefined, - isLoading: state.status === 'loading', - isIdle: state.status === 'idle', - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useLocalStorage.ts b/packages/clerk-js/src/ui.retheme/hooks/useLocalStorage.ts deleted file mode 100644 index ee5dfc834c4..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useLocalStorage.ts +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -export function useLocalStorage(key: string, initialValue: T) { - key = 'clerk:' + key; - const [storedValue, setStoredValue] = React.useState(() => { - if (typeof window === 'undefined') { - return initialValue; - } - - try { - const item = window.localStorage.getItem(key); - return item ? JSON.parse(item) : initialValue; - } catch (error) { - return initialValue; - } - }); - - const setValue = React.useCallback((value: ((stored: T) => T) | T) => { - if (typeof window === 'undefined') { - console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`); - } - - try { - const valueToStore = value instanceof Function ? value(storedValue) : value; - setStoredValue(valueToStore); - window.localStorage.setItem(key, JSON.stringify(valueToStore)); - } catch (error) { - console.error(error); - } - }, []); - - return [storedValue, setValue] as const; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useNavigateToFlowStart.ts b/packages/clerk-js/src/ui.retheme/hooks/useNavigateToFlowStart.ts deleted file mode 100644 index 79a1b5275b2..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useNavigateToFlowStart.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useRouter } from '../router'; - -export const useNavigateToFlowStart = () => { - const router = useRouter(); - const navigateToFlowStart = async () => { - const to = '/' + router.basePath + router.flowStartPath; - if (to !== router.currentPath) { - return router.navigate(to); - } - - if (router.urlStateParam?.path) { - return router.navigate('/' + router.basePath + router.urlStateParam?.startPath); - } - }; - return { navigateToFlowStart }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/usePassword.ts b/packages/clerk-js/src/ui.retheme/hooks/usePassword.ts deleted file mode 100644 index 1fcce876077..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/usePassword.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { noop } from '@clerk/shared'; -import type { PasswordValidation } from '@clerk/types'; -import { useCallback, useMemo } from 'react'; - -import type { UsePasswordCbs, UsePasswordConfig } from '../../utils/passwords/password'; -import { createValidatePassword } from '../../utils/passwords/password'; -import { localizationKeys, useLocalizations } from '../localization'; -import type { FormControlState } from '../utils'; -import { generateErrorTextUtil } from './usePasswordComplexity'; - -export const usePassword = (config: UsePasswordConfig, callbacks?: UsePasswordCbs) => { - const { t, locale } = useLocalizations(); - const { - onValidationError = noop, - onValidationSuccess = noop, - onValidationWarning = noop, - onValidationInfo = noop, - onValidationComplexity, - } = callbacks || {}; - - const onValidate = useCallback( - (res: PasswordValidation) => { - /** - * Failed complexity rules always have priority - */ - if (Object.values(res?.complexity || {}).length > 0) { - const message = generateErrorTextUtil({ - config, - t, - failedValidations: res.complexity, - locale, - }); - - if (res.complexity?.min_length) { - return onValidationInfo(message); - } - - return onValidationError(message); - } - - /** - * Failed strength - */ - if (res?.strength?.state === 'fail') { - const error = res.strength.keys.map(localizationKey => t(localizationKeys(localizationKey as any))).join(' '); - return onValidationError(error); - } - - /** - * Password meets all criteria but could be stronger - */ - if (res?.strength?.state === 'pass') { - const error = res.strength.keys.map(localizationKey => t(localizationKeys(localizationKey as any))).join(' '); - return onValidationWarning(error); - } - - /** - * Password meets all criteria and is strong - */ - return onValidationSuccess(); - }, - [callbacks, t, locale], - ); - - const validatePassword = useMemo(() => { - return createValidatePassword(config, { - onValidation: onValidate, - onValidationComplexity, - }); - }, [onValidate]); - - return { - validatePassword, - }; -}; - -export const useConfirmPassword = ({ - passwordField, - confirmPasswordField, -}: { - passwordField: FormControlState; - confirmPasswordField: FormControlState; -}) => { - const { t } = useLocalizations(); - const checkPasswordMatch = useCallback( - (confirmPassword: string) => passwordField.value === confirmPassword, - [passwordField.value], - ); - - const isPasswordMatch = useMemo( - () => checkPasswordMatch(confirmPasswordField.value), - [checkPasswordMatch, confirmPasswordField.value], - ); - - const setConfirmPasswordFeedback = useCallback( - (password: string) => { - if (checkPasswordMatch(password)) { - confirmPasswordField.setSuccess(t(localizationKeys('formFieldError__matchingPasswords'))); - } else { - confirmPasswordField.setError(t(localizationKeys('formFieldError__notMatchingPasswords'))); - } - }, - [confirmPasswordField.setError, confirmPasswordField.setSuccess, t, checkPasswordMatch], - ); - - return { - setConfirmPasswordFeedback, - isPasswordMatch, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/usePasswordComplexity.ts b/packages/clerk-js/src/ui.retheme/hooks/usePasswordComplexity.ts deleted file mode 100644 index 45d4b0f1557..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/usePasswordComplexity.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; - -import type { ComplexityErrors, UsePasswordComplexityConfig } from '../../utils/passwords/complexity'; -import { validate } from '../../utils/passwords/complexity'; -import type { LocalizationKey } from '../localization'; -import { localizationKeys, useLocalizations } from '../localization'; -import { addFullStop, createListFormat } from '../utils'; - -const errorMessages: Record, [string, string] | string> = { - max_length: ['unstable__errors.passwordComplexity.maximumLength', 'length'], - min_length: ['unstable__errors.passwordComplexity.minimumLength', 'length'], - require_numbers: 'unstable__errors.passwordComplexity.requireNumbers', - require_lowercase: 'unstable__errors.passwordComplexity.requireLowercase', - require_uppercase: 'unstable__errors.passwordComplexity.requireUppercase', - require_special_char: 'unstable__errors.passwordComplexity.requireSpecialCharacter', -}; - -export const generateErrorTextUtil = ({ - config, - failedValidations, - locale, - t, -}: { - config: UsePasswordComplexityConfig; - failedValidations: ComplexityErrors | undefined; - locale: string; - t: (localizationKey: LocalizationKey | string | undefined) => string; -}) => { - if (!failedValidations || Object.keys(failedValidations).length === 0) { - return ''; - } - - // show min length error first by itself - const hasMinLengthError = failedValidations?.min_length || false; - - const messages = Object.entries(failedValidations) - .filter(k => (hasMinLengthError ? k[0] === 'min_length' : true)) - .filter(([, v]) => !!v) - .map(([k]) => { - const localizedKey = errorMessages[k as keyof typeof errorMessages]; - if (Array.isArray(localizedKey)) { - const [lk, attr] = localizedKey; - return t(localizationKeys(lk as any, { [attr]: config[k as keyof UsePasswordComplexityConfig] as any })); - } - return t(localizationKeys(localizedKey as any)); - }); - - const messageWithPrefix = createListFormat(messages, locale); - - return addFullStop( - `${t(localizationKeys('unstable__errors.passwordComplexity.sentencePrefix'))} ${messageWithPrefix}`, - ); -}; - -export const usePasswordComplexity = (config: UsePasswordComplexityConfig) => { - const [password, _setPassword] = useState(''); - const [failedValidations, setFailedValidations] = useState(); - const { t, locale } = useLocalizations(); - - // Populates failedValidations state - useEffect(() => { - getComplexity(''); - }, []); - - const hasPassedComplexity = useMemo( - () => !!password && Object.keys(failedValidations || {}).length === 0, - [failedValidations, password], - ); - - const hasFailedComplexity = useMemo( - () => !!password && Object.keys(failedValidations || {}).length > 0, - [failedValidations, password], - ); - - const generateErrorText = useCallback( - (failedValidations: ComplexityErrors | undefined) => { - return generateErrorTextUtil({ - config, - t, - locale, - failedValidations, - }); - }, - [t, locale], - ); - - const failedValidationsText = useMemo(() => generateErrorText(failedValidations), [failedValidations]); - - const getComplexity = useCallback((password: string) => { - _setPassword(password); - const complexity = validate(password, config); - setFailedValidations(complexity); - return { - failedValidationsText: generateErrorText(complexity), - }; - }, []); - - return { - password, - getComplexity, - failedValidations, - failedValidationsText, - hasFailedComplexity, - hasPassedComplexity, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/usePopover.ts b/packages/clerk-js/src/ui.retheme/hooks/usePopover.ts deleted file mode 100644 index c4c7642ab67..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/usePopover.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { UseFloatingOptions } from '@floating-ui/react'; -import { autoUpdate, flip, offset, shift, useDismiss, useFloating, useFloatingNodeId } from '@floating-ui/react'; -import React, { useEffect } from 'react'; - -type UsePopoverProps = { - defaultOpen?: boolean; - placement?: UseFloatingOptions['placement']; - offset?: Parameters[0]; - autoUpdate?: boolean; - outsidePress?: boolean | ((event: MouseEvent) => boolean); - bubbles?: - | boolean - | { - escapeKey?: boolean; - outsidePress?: boolean; - }; -}; - -export type UsePopoverReturn = ReturnType; - -export const usePopover = (props: UsePopoverProps = {}) => { - const { bubbles = true, outsidePress } = props; - const [isOpen, setIsOpen] = React.useState(props.defaultOpen || false); - const nodeId = useFloatingNodeId(); - const { update, refs, strategy, x, y, context } = useFloating({ - open: isOpen, - onOpenChange: setIsOpen, - nodeId, - whileElementsMounted: props.autoUpdate === false ? undefined : autoUpdate, - placement: props.placement || 'bottom-start', - middleware: [offset(props.offset || 6), flip(), shift()], - }); - // Names are aliased because in @floating-ui/react-dom@2.0.0 the top-level elements were removed - // This keeps the API shape for consumers of usePopover - // @see https://github.com/floating-ui/floating-ui/releases/tag/%40floating-ui%2Freact-dom%402.0.0 - const { setReference: reference, setFloating: floating } = refs; - - useDismiss(context, { - bubbles, - outsidePress, - }); - - useEffect(() => { - if (props.defaultOpen) { - update(); - } - }, []); - - const toggle = React.useCallback(() => setIsOpen(o => !o), [setIsOpen]); - const open = React.useCallback(() => setIsOpen(true), [setIsOpen]); - const close = React.useCallback(() => setIsOpen(false), [setIsOpen]); - - return { - reference, - floating, - toggle, - open, - nodeId, - close, - isOpen, - styles: { position: strategy, top: y ?? 0, left: x ?? 0 }, - context, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/usePrefersReducedMotion.ts b/packages/clerk-js/src/ui.retheme/hooks/usePrefersReducedMotion.ts deleted file mode 100644 index 9d39777c583..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/usePrefersReducedMotion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect, useState } from 'react'; - -const mediaQueryNoPreference = '(prefers-reduced-motion: no-preference)'; - -export function usePrefersReducedMotion() { - const [prefersReducedMotion, setPrefersReducedMotion] = useState(true); - - useEffect(() => { - const mediaQueryList = window.matchMedia(mediaQueryNoPreference); - setPrefersReducedMotion(!window.matchMedia(mediaQueryNoPreference).matches); - - const listener = (event: MediaQueryListEvent) => { - setPrefersReducedMotion(!event.matches); - }; - - mediaQueryList.addEventListener('change', listener); - - return () => mediaQueryList.removeEventListener('change', listener); - }, []); - - return prefersReducedMotion; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useSafeState.ts b/packages/clerk-js/src/ui.retheme/hooks/useSafeState.ts deleted file mode 100644 index cba72ee5eec..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useSafeState.ts +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -/** - * Solves/ hides the "setState on unmounted component" warning - * In 99% of cases, there is no memory leak involved, but still an annoying warning - * For more info: - * https://github.com/reactwg/react-18/discussions/82 - */ -export function useSafeState(initialState: S | (() => S)): [S, React.Dispatch>]; -export function useSafeState(): [S | undefined, React.Dispatch>]; -export function useSafeState(initialState?: S | (() => S)) { - const [state, _setState] = React.useState(initialState); - const isMountedRef = React.useRef(true); - - React.useEffect(() => { - return () => { - isMountedRef.current = false; - }; - }, []); - - const setState = React.useCallback((currentState: any) => { - if (!isMountedRef.current) { - return; - } - _setState(currentState); - }, []); - - return [state, setState] as const; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useSaml.ts b/packages/clerk-js/src/ui.retheme/hooks/useSaml.ts deleted file mode 100644 index 5e350b9b233..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useSaml.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { SamlIdpSlug } from '@clerk/types'; -import { SAML_IDPS } from '@clerk/types'; - -import { iconImageUrl } from '../common/constants'; - -function getSamlProviderLogoUrl(provider: SamlIdpSlug = 'saml_custom'): string { - return iconImageUrl(SAML_IDPS[provider]?.logo); -} - -function getSamlProviderName(provider: SamlIdpSlug = 'saml_custom'): string { - return SAML_IDPS[provider]?.name; -} - -export const useSaml = () => { - return { - getSamlProviderLogoUrl, - getSamlProviderName, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useScrollLock.ts b/packages/clerk-js/src/ui.retheme/hooks/useScrollLock.ts deleted file mode 100644 index e41cbba3a1e..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useScrollLock.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Disables scroll for an element. - * Adds extra padding to prevent layout shifting - * caused by hiding the scrollbar. - */ -export const useScrollLock = (el: T) => { - let oldPaddingRightPx: string; - let oldOverflow: string; - - const disableScroll = () => { - oldPaddingRightPx = getComputedStyle(el).paddingRight; - oldOverflow = getComputedStyle(el).overflow; - const oldWidth = el.clientWidth; - el.style.overflow = 'hidden'; - const currentWidth = el.clientWidth; - const oldPaddingRight = Number.parseInt(oldPaddingRightPx.replace('px', '')); - el.style.paddingRight = `${currentWidth - oldWidth + oldPaddingRight}px`; - }; - - const enableScroll = () => { - el.style.overflow = oldOverflow; - if (oldPaddingRightPx) { - el.style.paddingRight = oldPaddingRightPx; - } - }; - - return { disableScroll, enableScroll }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useSearchInput.ts b/packages/clerk-js/src/ui.retheme/hooks/useSearchInput.ts deleted file mode 100644 index 2408585ff43..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useSearchInput.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ChangeEventHandler } from 'react'; -import React from 'react'; - -type Unarray = T extends Array ? U : T; - -type UseSearchInputProps = { - items: Items; - comparator: (term: string, item: Unarray, itemTerm?: string) => boolean; - searchTermForItem?: (item: Unarray) => string; -}; - -type UseSearchInputReturn = { filteredItems: Items; searchInputProps: any }; - -export const useSearchInput = >( - props: UseSearchInputProps, -): UseSearchInputReturn => { - const { items, comparator, searchTermForItem } = props; - const [searchTerm, setSearchTerm] = React.useState(''); - const onChange: ChangeEventHandler = e => setSearchTerm(e.target.value || ''); - - const searchTermMap = React.useMemo(() => { - type TermMap = Map, string | undefined>; - return items.reduce((acc, item) => { - (acc as TermMap).set(item, searchTermForItem?.(item)); - return acc; - }, new Map() as TermMap) as TermMap; - }, [items]); - - const filteredItems = React.useMemo( - () => (searchTerm ? items.filter(i => comparator(searchTerm, i, searchTermMap.get(i))) : items), - [items, searchTerm], - ) as Items; - - return { searchInputProps: { onChange, value: searchTerm }, filteredItems }; -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useSetSessionWithTimeout.ts b/packages/clerk-js/src/ui.retheme/hooks/useSetSessionWithTimeout.ts deleted file mode 100644 index 21eb6fd716e..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useSetSessionWithTimeout.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import { useEffect } from 'react'; - -import { useSignInContext } from '../contexts'; -import { useRouter } from '../router'; - -export const useSetSessionWithTimeout = (delay = 2000) => { - const { queryString } = useRouter(); - const { setActive } = useClerk(); - const { navigateAfterSignIn } = useSignInContext(); - - useEffect(() => { - let timeoutId: ReturnType; - const queryParams = new URLSearchParams(queryString); - const createdSessionId = queryParams.get('createdSessionId'); - if (createdSessionId) { - timeoutId = setTimeout(() => { - void setActive({ session: createdSessionId, beforeEmit: navigateAfterSignIn }); - }, delay); - } - - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, [setActive, navigateAfterSignIn, queryString]); -}; diff --git a/packages/clerk-js/src/ui.retheme/hooks/useSupportEmail.ts b/packages/clerk-js/src/ui.retheme/hooks/useSupportEmail.ts deleted file mode 100644 index 1b4fb27676f..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useSupportEmail.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import React from 'react'; - -import { buildEmailAddress } from '../../utils'; -import { useEnvironment, useOptions } from '../contexts'; - -export function useSupportEmail(): string { - const Clerk = useClerk(); - const { supportEmail: supportEmailFromOptions } = useOptions(); - const { displayConfig } = useEnvironment(); - const { supportEmail: supportEmailFromEnvironment } = displayConfig; - - const supportDomain = React.useMemo( - () => - supportEmailFromOptions || - supportEmailFromEnvironment || - buildEmailAddress({ - localPart: 'support', - frontendApi: Clerk.frontendApi, - }), - [Clerk.frontendApi, supportEmailFromOptions, supportEmailFromEnvironment], - ); - - return supportDomain; -} diff --git a/packages/clerk-js/src/ui.retheme/hooks/useWindowEventListener.ts b/packages/clerk-js/src/ui.retheme/hooks/useWindowEventListener.ts deleted file mode 100644 index 843aba51d7b..00000000000 --- a/packages/clerk-js/src/ui.retheme/hooks/useWindowEventListener.ts +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -type EventType = keyof WindowEventMap; - -export const useWindowEventListener = (eventOrEvents: EventType | EventType[] | undefined, cb: () => void): void => { - React.useEffect(() => { - const events = [eventOrEvents].flat().filter(x => !!x) as EventType[]; - if (!events.length) { - return; - } - events.forEach(e => window.addEventListener(e, cb)); - return () => { - events.forEach(e => window.removeEventListener(e, cb)); - }; - }, [eventOrEvents, cb]); -}; diff --git a/packages/clerk-js/src/ui.retheme/icons/arrow-left.svg b/packages/clerk-js/src/ui.retheme/icons/arrow-left.svg deleted file mode 100644 index 65a98939a7f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/arrow-left.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/arrow-right.svg b/packages/clerk-js/src/ui.retheme/icons/arrow-right.svg deleted file mode 100644 index 3e626bf728b..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/auth-app.svg b/packages/clerk-js/src/ui.retheme/icons/auth-app.svg deleted file mode 100644 index b3c06abfa7f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/auth-app.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/billing.svg b/packages/clerk-js/src/ui.retheme/icons/billing.svg deleted file mode 100644 index 78203d45811..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/billing.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/caret.svg b/packages/clerk-js/src/ui.retheme/icons/caret.svg deleted file mode 100644 index f9f4dccba60..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/caret.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/chat-alt.svg b/packages/clerk-js/src/ui.retheme/icons/chat-alt.svg deleted file mode 100644 index 468207244e2..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/chat-alt.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/check-circle.svg b/packages/clerk-js/src/ui.retheme/icons/check-circle.svg deleted file mode 100644 index d8cc3defc9a..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/check-circle.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/clipboard.svg b/packages/clerk-js/src/ui.retheme/icons/clipboard.svg deleted file mode 100644 index 3b5e478022f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/clipboard.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/close.svg b/packages/clerk-js/src/ui.retheme/icons/close.svg deleted file mode 100644 index 4c18e8a43a5..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/close.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/clerk-js/src/ui.retheme/icons/cog-filled.svg b/packages/clerk-js/src/ui.retheme/icons/cog-filled.svg deleted file mode 100644 index 0e9ff937aec..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/cog-filled.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/cog.svg b/packages/clerk-js/src/ui.retheme/icons/cog.svg deleted file mode 100644 index de7315c02fe..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/cog.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/device-laptop.svg b/packages/clerk-js/src/ui.retheme/icons/device-laptop.svg deleted file mode 100644 index c24e4088d7a..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/device-laptop.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/device-mobile.svg b/packages/clerk-js/src/ui.retheme/icons/device-mobile.svg deleted file mode 100644 index e824ebfeb4f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/device-mobile.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/dot-circle-horizontal.svg b/packages/clerk-js/src/ui.retheme/icons/dot-circle-horizontal.svg deleted file mode 100644 index 1f5b269d130..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/dot-circle-horizontal.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/email.svg b/packages/clerk-js/src/ui.retheme/icons/email.svg deleted file mode 100644 index b4f033d986b..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/email.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/exclamation-circle.svg b/packages/clerk-js/src/ui.retheme/icons/exclamation-circle.svg deleted file mode 100644 index 62a3f245943..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/exclamation-circle.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/exclamation-triangle.svg b/packages/clerk-js/src/ui.retheme/icons/exclamation-triangle.svg deleted file mode 100644 index 946e6bcec5d..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/exclamation-triangle.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/eye-slash.svg b/packages/clerk-js/src/ui.retheme/icons/eye-slash.svg deleted file mode 100644 index 514a1e7b049..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/eye-slash.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/eye.svg b/packages/clerk-js/src/ui.retheme/icons/eye.svg deleted file mode 100644 index d2ba4e089a0..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/eye.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/folder.svg b/packages/clerk-js/src/ui.retheme/icons/folder.svg deleted file mode 100644 index 11eddc522bc..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/folder.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/index.ts b/packages/clerk-js/src/ui.retheme/icons/index.ts deleted file mode 100644 index a8c06b2a554..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-nocheck -/** - * TypeScript configuration (typings) is not correctly configured for all projects. - * Consequently, the files are correctly imported but the TS checker emits errors. - * The above no-check is safe, as webpack will not allow compilation if for example a file is not resolved. - */ -export { default as Add } from './add.svg'; -export { default as ArrowLeftIcon } from './arrow-left.svg'; -export { default as ArrowRightIcon } from './arrow-right.svg'; -export { default as ArrowRightButtonIcon } from './arrow-right-button.svg'; -export { default as AuthApp } from './auth-app.svg'; -export { default as Billing } from './billing.svg'; -export { default as Caret } from './caret.svg'; -export { default as ChatAltIcon } from './chat-alt.svg'; -export { default as CheckCircle } from './check-circle.svg'; -export { default as ChevronDown } from './chevron-down.svg'; -export { default as Clipboard } from './clipboard.svg'; -export { default as Close } from './close.svg'; -export { default as CogFilled } from './cog-filled.svg'; -export { default as DeviceLaptop } from './device-laptop.svg'; -export { default as DeviceMobile } from './device-mobile.svg'; -export { default as DotCircle } from './dot-circle-horizontal.svg'; -export { default as Email } from './email.svg'; -export { default as ExclamationCircle } from './exclamation-circle.svg'; -export { default as ExclamationTriangle } from './exclamation-triangle.svg'; -export { default as EyeSlash } from './eye-slash.svg'; -export { default as Eye } from './eye.svg'; -export { default as Folder } from './folder.svg'; -export { default as InformationCircle } from './information-circle.svg'; -export { default as LinkIcon } from './link.svg'; -export { default as LockClosedIcon } from './lock-closed.svg'; -export { default as LogoMark } from './logo-mark-new.svg'; -export { default as MagnifyingGlass } from './magnifying-glass.svg'; -export { default as Menu } from './menu.svg'; -export { default as Mobile } from './mobile-small.svg'; -export { default as PencilEdit } from './pencil-edit.svg'; -export { default as Pencil } from './pencil.svg'; -export { default as Plus } from './plus.svg'; -export { default as QuestionMark } from './question-mark.svg'; -export { default as RequestAuthIcon } from './request-auth.svg'; -export { default as Selector } from './selector.svg'; -export { default as SignOutDouble } from './signout-double.svg'; -export { default as SignOut } from './signout.svg'; -export { default as SwitchArrows } from './switch-arrows.svg'; -export { default as SwitchArrowRight } from './switch-arrow-right.svg'; -export { default as ThreeDots } from './threeDots.svg'; -export { default as TickShield } from './tick-shield.svg'; -export { default as Times } from './times.svg'; -export { default as Trash } from './trash.svg'; -export { default as Upload } from './upload.svg'; -export { default as User } from './user.svg'; -export { default as UserAdd } from './userAdd.svg'; -export { default as Check } from './check.svg'; -export { default as ArrowUpDown } from './arrow-up-down.svg'; -export { default as CheckmarkFilled } from './checkmark-filled.svg'; diff --git a/packages/clerk-js/src/ui.retheme/icons/information-circle.svg b/packages/clerk-js/src/ui.retheme/icons/information-circle.svg deleted file mode 100644 index 9ab89bd3d21..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/information-circle.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/packages/clerk-js/src/ui.retheme/icons/link.svg b/packages/clerk-js/src/ui.retheme/icons/link.svg deleted file mode 100644 index d33f12d24c7..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/link.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/lock-closed.svg b/packages/clerk-js/src/ui.retheme/icons/lock-closed.svg deleted file mode 100644 index 87c5d0551b0..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/lock-closed.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/logo-mark-new.svg b/packages/clerk-js/src/ui.retheme/icons/logo-mark-new.svg deleted file mode 100644 index d797bc3a4fe..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/logo-mark-new.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/clerk-js/src/ui.retheme/icons/logo-mark.svg b/packages/clerk-js/src/ui.retheme/icons/logo-mark.svg deleted file mode 100644 index 9144580157a..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/logo-mark.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/magnifying-glass.svg b/packages/clerk-js/src/ui.retheme/icons/magnifying-glass.svg deleted file mode 100644 index 324be823f55..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/magnifying-glass.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/menu.svg b/packages/clerk-js/src/ui.retheme/icons/menu.svg deleted file mode 100644 index f4a81c36c4b..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/menu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/mobile-small.svg b/packages/clerk-js/src/ui.retheme/icons/mobile-small.svg deleted file mode 100644 index a0f8dea5ff3..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/mobile-small.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/mobile.svg b/packages/clerk-js/src/ui.retheme/icons/mobile.svg deleted file mode 100644 index 1dab9108294..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/mobile.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/pencil-edit.svg b/packages/clerk-js/src/ui.retheme/icons/pencil-edit.svg deleted file mode 100644 index 95ff9c51189..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/pencil-edit.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/clerk-js/src/ui.retheme/icons/pencil.svg b/packages/clerk-js/src/ui.retheme/icons/pencil.svg deleted file mode 100644 index 6060104b722..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/pencil.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/clerk-js/src/ui.retheme/icons/plus.svg b/packages/clerk-js/src/ui.retheme/icons/plus.svg deleted file mode 100644 index 16d6d1e8e3c..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/plus.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/question-mark.svg b/packages/clerk-js/src/ui.retheme/icons/question-mark.svg deleted file mode 100644 index d34c38a6033..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/question-mark.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/request-auth.svg b/packages/clerk-js/src/ui.retheme/icons/request-auth.svg deleted file mode 100644 index 8ac7ea863e9..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/request-auth.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/selector.svg b/packages/clerk-js/src/ui.retheme/icons/selector.svg deleted file mode 100644 index f445d1f9d3b..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/selector.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/signout-double.svg b/packages/clerk-js/src/ui.retheme/icons/signout-double.svg deleted file mode 100644 index 52a5d03f00f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/signout-double.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/signout.svg b/packages/clerk-js/src/ui.retheme/icons/signout.svg deleted file mode 100644 index 73478e021c0..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/signout.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/switch-arrows.svg b/packages/clerk-js/src/ui.retheme/icons/switch-arrows.svg deleted file mode 100644 index bc00400b7e2..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/switch-arrows.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/threeDots.svg b/packages/clerk-js/src/ui.retheme/icons/threeDots.svg deleted file mode 100644 index b399e31a9cc..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/threeDots.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/tick-shield.svg b/packages/clerk-js/src/ui.retheme/icons/tick-shield.svg deleted file mode 100644 index 498672049bf..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/tick-shield.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/times.svg b/packages/clerk-js/src/ui.retheme/icons/times.svg deleted file mode 100644 index 722fa62240f..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/times.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/trash.svg b/packages/clerk-js/src/ui.retheme/icons/trash.svg deleted file mode 100644 index 8c41a793fd6..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/trash.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/upload.svg b/packages/clerk-js/src/ui.retheme/icons/upload.svg deleted file mode 100644 index 794924db521..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/clerk-js/src/ui.retheme/icons/user.svg b/packages/clerk-js/src/ui.retheme/icons/user.svg deleted file mode 100644 index d762fd8778b..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/user.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/icons/userAdd.svg b/packages/clerk-js/src/ui.retheme/icons/userAdd.svg deleted file mode 100644 index 50a226e6276..00000000000 --- a/packages/clerk-js/src/ui.retheme/icons/userAdd.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/clerk-js/src/ui.retheme/lazyModules/common.ts b/packages/clerk-js/src/ui.retheme/lazyModules/common.ts deleted file mode 100644 index 81f7d71faf0..00000000000 --- a/packages/clerk-js/src/ui.retheme/lazyModules/common.ts +++ /dev/null @@ -1,3 +0,0 @@ -import '../contexts/index'; - -export { createRoot } from 'react-dom/client'; diff --git a/packages/clerk-js/src/ui.retheme/lazyModules/components.ts b/packages/clerk-js/src/ui.retheme/lazyModules/components.ts deleted file mode 100644 index f4fe93f9a47..00000000000 --- a/packages/clerk-js/src/ui.retheme/lazyModules/components.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { lazy } from 'react'; - -const componentImportPaths = { - SignIn: () => import(/* webpackChunkName: "signin" */ './../components/SignIn'), - SignUp: () => import(/* webpackChunkName: "signup" */ './../components/SignUp'), - UserButton: () => import(/* webpackChunkName: "userbutton" */ './../components/UserButton'), - UserProfile: () => import(/* webpackChunkName: "userprofile" */ './../components/UserProfile'), - CreateOrganization: () => import(/* webpackChunkName: "createorganization" */ './../components/CreateOrganization'), - OrganizationProfile: () => - import(/* webpackChunkName: "organizationprofile" */ './../components/OrganizationProfile'), - OrganizationSwitcher: () => - import(/* webpackChunkName: "organizationswitcher" */ './../components/OrganizationSwitcher'), - OrganizationList: () => import(/* webpackChunkName: "organizationlist" */ './../components/OrganizationList'), - ImpersonationFab: () => import(/* webpackChunkName: "impersonationfab" */ './../components/ImpersonationFab'), -} as const; - -export const SignIn = lazy(() => componentImportPaths.SignIn().then(module => ({ default: module.SignIn }))); - -export const SignInModal = lazy(() => componentImportPaths.SignIn().then(module => ({ default: module.SignInModal }))); - -export const SignUp = lazy(() => componentImportPaths.SignUp().then(module => ({ default: module.SignUp }))); - -export const SignUpModal = lazy(() => componentImportPaths.SignUp().then(module => ({ default: module.SignUpModal }))); - -export const UserButton = lazy(() => - componentImportPaths.UserButton().then(module => ({ default: module.UserButton })), -); -export const UserProfile = lazy(() => - componentImportPaths.UserProfile().then(module => ({ default: module.UserProfile })), -); -export const UserProfileModal = lazy(() => - componentImportPaths.UserProfile().then(module => ({ default: module.UserProfileModal })), -); -export const CreateOrganization = lazy(() => - componentImportPaths.CreateOrganization().then(module => ({ default: module.CreateOrganization })), -); - -export const CreateOrganizationModal = lazy(() => - componentImportPaths.CreateOrganization().then(module => ({ default: module.CreateOrganizationModal })), -); - -export const OrganizationProfile = lazy(() => - componentImportPaths.OrganizationProfile().then(module => ({ default: module.OrganizationProfile })), -); - -export const OrganizationProfileModal = lazy(() => - componentImportPaths.OrganizationProfile().then(module => ({ default: module.OrganizationProfileModal })), -); - -export const OrganizationSwitcher = lazy(() => - componentImportPaths.OrganizationSwitcher().then(module => ({ default: module.OrganizationSwitcher })), -); - -export const OrganizationList = lazy(() => - componentImportPaths.OrganizationList().then(module => ({ default: module.OrganizationList })), -); - -export const ImpersonationFab = lazy(() => - componentImportPaths.ImpersonationFab().then(module => ({ default: module.ImpersonationFab })), -); - -export const preloadComponent = async (component: unknown) => { - return componentImportPaths[component as keyof typeof componentImportPaths]?.(); -}; - -export const ClerkComponents = { - SignIn, - SignUp, - UserButton, - UserProfile, - OrganizationSwitcher, - OrganizationList, - OrganizationProfile, - CreateOrganization, - SignInModal, - SignUpModal, - UserProfileModal, - OrganizationProfileModal, - CreateOrganizationModal, -}; - -export type ClerkComponentName = keyof typeof ClerkComponents; diff --git a/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx b/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx deleted file mode 100644 index 730cd62654e..00000000000 --- a/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import type { Appearance } from '@clerk/types'; -import React, { lazy, Suspense } from 'react'; - -import type { FlowMetadata } from '../elements'; -import type { ThemableCssProp } from '../styledSystem'; -import type { ClerkComponentName } from './components'; -import { ClerkComponents } from './components'; - -const CoreClerkContextWrapper = lazy(() => import('../contexts').then(m => ({ default: m.CoreClerkContextWrapper }))); -const EnvironmentProvider = lazy(() => import('../contexts').then(m => ({ default: m.EnvironmentProvider }))); -const OptionsProvider = lazy(() => import('../contexts').then(m => ({ default: m.OptionsProvider }))); -const AppearanceProvider = lazy(() => import('../customizables').then(m => ({ default: m.AppearanceProvider }))); -const VirtualRouter = lazy(() => import('../router').then(m => ({ default: m.VirtualRouter }))); -const InternalThemeProvider = lazy(() => import('../styledSystem').then(m => ({ default: m.InternalThemeProvider }))); -const Portal = lazy(() => import('./../portal').then(m => ({ default: m.Portal }))); -const FlowMetadataProvider = lazy(() => import('./../elements').then(m => ({ default: m.FlowMetadataProvider }))); -const Modal = lazy(() => import('./../elements').then(m => ({ default: m.Modal }))); - -type LazyProvidersProps = React.PropsWithChildren<{ clerk: any; environment: any; options: any; children: any }>; - -export const LazyProviders = (props: LazyProvidersProps) => { - return ( - - - {props.children} - - - ); -}; - -type _AppearanceProviderProps = Parameters[0]; -type AppearanceProviderProps = { - globalAppearance?: _AppearanceProviderProps['globalAppearance']; - appearanceKey: _AppearanceProviderProps['appearanceKey']; - componentAppearance?: _AppearanceProviderProps['appearance']; -}; -type LazyComponentRendererProps = React.PropsWithChildren< - { - node: PortalProps['node']; - componentName: any; - componentProps: any; - } & AppearanceProviderProps ->; - -type PortalProps = Parameters[0]; - -export const LazyComponentRenderer = (props: LazyComponentRendererProps) => { - return ( - - - - ); -}; - -type ModalProps = Parameters[0]; - -type LazyModalRendererProps = React.PropsWithChildren< - { - componentName: ClerkComponentName; - flowName?: FlowMetadata['flow']; - startPath?: string; - onClose?: ModalProps['handleClose']; - onExternalNavigate?: () => any; - modalContainerSx?: ThemableCssProp; - modalContentSx?: ThemableCssProp; - } & AppearanceProviderProps ->; - -export const LazyModalRenderer = (props: LazyModalRendererProps) => { - return ( - - - - - - {props.startPath ? ( - - - {props.children} - - - ) : ( - props.children - )} - - - - - - ); -}; - -/** - * This component automatically mounts when impersonating, without a user action. - * We want to hotload the /ui dependencies only if we're actually impersonating. - */ -export const LazyImpersonationFabProvider = ( - props: React.PropsWithChildren<{ globalAppearance: Appearance | undefined }>, -) => { - return ( - - - {props.children} - - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/localization/__tests__/applyTokensToString.test.ts b/packages/clerk-js/src/ui.retheme/localization/__tests__/applyTokensToString.test.ts deleted file mode 100644 index ed9ff255d7e..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/__tests__/applyTokensToString.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { applyTokensToString } from '../applyTokensToString'; - -describe('applyTokensToString', function () { - const tokens = { - applicationName: 'myApp', - 'user.firstName': 'nikos', - identifier: 'nikos@hello.dev', - provider: 'google', - date: new Date('2021-12-31T22:00:00.000Z'), - dateString: '2021-12-31T22:00:00.000Z', - dateNumber: 1640988000000, - }; - - const cases = [ - ['Continue with {{provider|titleize}}', 'Continue with Google'], - ['Continue with {{ provider | titleize }}', 'Continue with Google'], - ['Welcome to {{applicationName}}, have fun', 'Welcome to myApp, have fun'], - ['Welcome to {{applicationName|titleize}}, have fun', 'Welcome to MyApp, have fun'], - ['Welcome to {{ applicationName| titleize}}, have fun', 'Welcome to MyApp, have fun'], - ['This is an {{unknown}} token', 'This is an {{unknown}} token'], - ['This is an {{applicationName|unknown}} modifier', 'This is an myApp modifier'], - ['This includes no token', 'This includes no token'], - [ - 'This supports multiple tokens {{user.firstName }} - {{ identifier |titleize}}', - 'This supports multiple tokens nikos - Nikos@hello.dev', - ], - ['', ''], - [undefined, ''], - ]; - - it.each(cases)('.applyTokensToString(%s, tokens) => %s', (input, expected) => { - expect(applyTokensToString(input, tokens as any)).toEqual(expected); - }); - - describe('Date related tokens and modifiers', () => { - beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); - }); - - const tokens = { - date: new Date('2021-12-31T22:00:00.000Z'), - dateString: '2021-12-31T22:00:00.000Z', - dateNumber: 1640988000000, - errorString: 'test_error_case', - }; - - const cases = [ - ["Last {{ date | weekday('en-US','long') }} at {{ date | timeString('en-US') }}", 'Last Friday at 10:00 PM'], - ["Last {{ date | weekday('fr-FR','long') }} at {{ date | timeString('fr-FR') }}", 'Last vendredi at 22:00'], - [ - "Last {{ date | weekday('fr-FR','long') | titleize }} at {{ date | timeString('fr-FR') }}", - 'Last Vendredi at 22:00', - ], - [ - "Προηγούμενη {{ date | weekday('el-GR','long') }} στις {{ date | timeString('el-GR') }}", - 'Προηγούμενη Παρασκευή στις 10:00 μ.μ.', - ], - [ - "Προηγούμενη {{ date | weekday('el-GR') }} στις {{ date | timeString('el-GR') }}", - 'Προηγούμενη Παρασκευή στις 10:00 μ.μ.', - ], - [ - "Last {{ dateString | weekday('en-US','long') }} at {{ dateString | timeString('en-US') }}", - 'Last Friday at 10:00 PM', - ], - [ - "Last {{ dateNumber | weekday('en-US','long') }} at {{ dateNumber | timeString('en-US') }}", - 'Last Friday at 10:00 PM', - ], - ['Last {{ date | weekday }} at {{ date | timeString }}', 'Last Friday at 10:00 PM'], - ]; - - it.each(cases)('.applyTokensToString(%s, tokens) => %s', (input, expected) => { - expect(applyTokensToString(input, tokens as any)).toEqual(expected); - }); - - describe('Modifiers', () => { - describe('weekday', () => { - const cases = [ - ['{{ date | weekday }}', 'Friday'], - ['{{ dateString | weekday }}', 'Friday'], - ['{{ dateNumber | weekday }}', 'Friday'], - ['{{ date | weekday("en-US") }}', 'Friday'], - ['{{ date | weekday("fr-FR") }}', 'vendredi'], - ['{{ date | weekday("fr-FR") | titleize }}', 'Vendredi'], - ['{{ date | weekday("en-US", "long") }}', 'Friday'], - ['{{ date | weekday("en-US", "short") }}', 'Fri'], - ['{{ date | weekday("en-US", "narrow") }}', 'F'], - ['{{ date | weekday("fr-FR", "narrow") }}', 'V'], - ['{{ errorString | weekday("en-US") }}', ''], - ]; - - it.each(cases)('.applyTokensToString(%s, tokens) => %s', (input, expected) => { - expect(applyTokensToString(input, tokens as any)).toEqual(expected); - }); - }); - - describe('timeString', () => { - const cases = [ - ['{{ date | timeString }}', '10:00 PM'], - ['{{ dateString | timeString }}', '10:00 PM'], - ['{{ dateNumber | timeString }}', '10:00 PM'], - ['{{ date | timeString("en-US") }}', '10:00 PM'], - ['{{ date | timeString("el-GR") }}', '10:00 μ.μ.'], - ['{{ date | timeString("el-GR") | titleize }}', '10:00 μ.μ.'], - ['{{ date | timeString("fr-FR") }}', '22:00'], - ['{{ errorString | timeString("en-US") }}', ''], - ]; - - it.each(cases)('.applyTokensToString(%s, tokens) => %s', (input, expected) => { - expect(applyTokensToString(input, tokens as any)).toEqual(expected); - }); - }); - - describe('numeric', () => { - const cases = [ - ['{{ date | numeric }}', '12/31/2021'], - ['{{ dateString | numeric }}', '12/31/2021'], - ['{{ dateNumber | numeric }}', '12/31/2021'], - ['{{ date | numeric("en-US") }}', '12/31/2021'], - ['{{ date | numeric("fr-FR") }}', '31/12/2021'], - ['{{ date | numeric("fr-FR") | titleize }}', '31/12/2021'], - ['{{ errorString | numeric("en-US") }}', ''], - ]; - - it.each(cases)('.applyTokensToString(%s, tokens) => %s', (input, expected) => { - expect(applyTokensToString(input, tokens as any)).toEqual(expected); - }); - }); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/localization/__tests__/makeLocalizable.test.tsx b/packages/clerk-js/src/ui.retheme/localization/__tests__/makeLocalizable.test.tsx deleted file mode 100644 index f633d33a067..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/__tests__/makeLocalizable.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { render, renderHook, screen } from '../../../testUtils'; -import { - Badge, - Button, - FormErrorText, - FormLabel, - Heading, - Link, - localizationKeys, - SimpleButton, - Text, - useLocalizations, -} from '../../customizables'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -const localizableElements = [ - { name: 'Badge', el: Badge }, - { name: 'Button', el: Button }, - { name: 'FormErrorText', el: FormErrorText }, - { name: 'FormLabel', el: FormLabel }, - { name: 'Heading', el: Heading }, - { name: 'Link', el: Link }, - { name: 'SimpleButton', el: SimpleButton }, - { name: 'Text', el: Text }, -]; - -describe('Test localizable components', () => { - it.each(localizableElements)( - '$name renders the localization value based on the localization key', - async ({ el: El }) => { - const { wrapper } = await createFixtures(); - - render(, { wrapper }); - - const { result } = renderHook(() => useLocalizations(), { wrapper }); - - const localizedValue = result.current.t(localizationKeys('backButton')); - - screen.getByText(localizedValue); - }, - ); - - it.each(localizableElements)('$name renders the children if no localization key is provided', async ({ el: El }) => { - const { wrapper } = await createFixtures(); - - render(test, { wrapper }); - - screen.getByText('test'); - }); - - it.each(localizableElements)( - '$name only renders the localization value if both children and key are provided', - async ({ el: El }) => { - const { wrapper } = await createFixtures(); - - render(test, { wrapper }); - - const { result } = renderHook(() => useLocalizations(), { wrapper }); - - const localizedValue = result.current.t(localizationKeys('backButton')); - - screen.getByText(localizedValue); - }, - ); - - it.each(localizableElements)('$name renders the global token if provided with one', async ({ el: El }) => { - const { wrapper } = await createFixtures(); - - render(, { - wrapper, - }); - - screen.getByText(`Continue with Test_provider`); // this key makes use of titleize - }); - - it.each(localizableElements)('$name renders the global date token if provided with one', async ({ el: El }) => { - const { wrapper } = await createFixtures(); - - const date = new Date('11/12/1999'); - - render(, { - wrapper, - }); - - screen.getByText('11/12/1999'); // this key makes use of numeric('en-US') - }); - - it('translates errors using the values provided in unstable_errors', async () => { - const { wrapper, fixtures } = await createFixtures(); - fixtures.options.localization = { - unstable__errors: { - form_identifier_not_found: 'form_identifier_not_found', - form_password_pwned: 'form_password_pwned', - form_username_invalid_length: 'form_username_invalid_length', - form_username_invalid_character: 'form_username_invalid_character', - form_param_format_invalid: 'form_param_format_invalid', - form_password_length_too_short: 'form_password_length_too_short', - form_param_nil: 'form_param_nil', - form_code_incorrect: 'form_code_incorrect', - form_password_incorrect: 'form_password_incorrect', - not_allowed_access: 'not_allowed_access', - form_identifier_exists: 'form_identifier_exists', - form_identifier_exists__username: 'form_identifier_exists__username', - form_identifier_exists__email_address: 'form_identifier_exists__email_address', - }, - }; - const { result } = renderHook(() => useLocalizations(), { wrapper }); - const { translateError } = result.current; - expect(translateError({ code: 'code-does-not-exist', message: 'message' })).toBe('message'); - expect(translateError({ code: 'form_identifier_not_found', message: 'message' } as any)).toBe( - 'form_identifier_not_found', - ); - expect(translateError({ code: 'form_password_pwned', message: 'message' })).toBe('form_password_pwned'); - expect(translateError({ code: 'form_username_invalid_length', message: 'message' })).toBe( - 'form_username_invalid_length', - ); - expect(translateError({ code: 'form_username_invalid_character', message: 'message' })).toBe( - 'form_username_invalid_character', - ); - expect(translateError({ code: 'form_param_format_invalid', message: 'message' })).toBe('form_param_format_invalid'); - expect(translateError({ code: 'form_password_length_too_short', message: 'message' })).toBe( - 'form_password_length_too_short', - ); - expect(translateError({ code: 'form_param_nil', message: 'message' })).toBe('form_param_nil'); - expect(translateError({ code: 'form_code_incorrect', message: 'message' })).toBe('form_code_incorrect'); - expect(translateError({ code: 'form_password_incorrect', message: 'message' })).toBe('form_password_incorrect'); - expect(translateError({ code: 'not_allowed_access', message: 'message' })).toBe('not_allowed_access'); - expect(translateError({ code: 'form_identifier_exists', message: 'message' })).toBe('form_identifier_exists'); - expect( - translateError({ code: 'form_identifier_exists', message: 'message', meta: { paramName: 'username' } }), - ).toBe('form_identifier_exists__username'); - expect( - translateError({ code: 'form_identifier_exists', message: 'message', meta: { paramName: 'email_address' } }), - ).toBe('form_identifier_exists__email_address'); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/localization/__tests__/parseLocalization.test.tsx b/packages/clerk-js/src/ui.retheme/localization/__tests__/parseLocalization.test.tsx deleted file mode 100644 index 6fb7385fd7c..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/__tests__/parseLocalization.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import { renderHook } from '../../../testUtils'; -import { OptionsProvider } from '../../contexts'; -import { localizationKeys, useLocalizations } from '../../customizables'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; -import { defaultResource } from '../defaultEnglishResource'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -describe('Localization parsing and replacing', () => { - it('Localization value returned from hook is equal to the value declared in the defaultResource when no localization options are passed', async () => { - const { wrapper: Wrapper } = await createFixtures(); - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const localizedValue = result.current.t(localizationKeys('backButton')); - expect(localizedValue).toBe(defaultResource.backButton); - }); - - it('Localization value returned from hook is equal to the value declared in the defaultResource', async () => { - const { wrapper: Wrapper } = await createFixtures(); - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const localizedValue = result.current.t(localizationKeys('backButton')); - expect(localizedValue).toBe('test'); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/localization/applyTokensToString.ts b/packages/clerk-js/src/ui.retheme/localization/applyTokensToString.ts deleted file mode 100644 index 051cd505958..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/applyTokensToString.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; - -import { useEnvironment } from '../contexts'; -import { MODIFIERS } from './localizationModifiers'; - -export type GlobalTokens = { - applicationName: string; - 'signIn.identifier': string; - 'user.firstName': string; - 'user.lastName': string; - 'user.username': string; - 'user.primaryEmailAddress': string; - 'user.primaryPhoneNumber': string; -}; - -// TODO: This type can be narrowed down when we know all -// global and local tokens used throughout the codebase -export type Tokens = GlobalTokens & Record; - -type Token = keyof Tokens | string; -type Modifier = { modifierName: keyof typeof MODIFIERS; params: string[] }; -type TokenExpression = { token: Token; modifiers: Modifier[] }; - -export const applyTokensToString = (s: string | undefined, tokens: Tokens): string => { - if (!s) { - return ''; - } - const { normalisedString, expressions } = parseTokensFromLocalizedString(s, tokens); - return applyTokenExpressions(normalisedString, expressions, tokens); -}; - -export const useGlobalTokens = (): GlobalTokens => { - const { applicationName } = useEnvironment().displayConfig; - const { client, user } = useClerk(); - const { signIn } = client; - - return { - applicationName, - 'signIn.identifier': signIn.identifier || '', - 'user.username': user?.username || '', - 'user.firstName': user?.firstName || '', - 'user.lastName': user?.lastName || '', - 'user.primaryEmailAddress': user?.primaryEmailAddress?.emailAddress || '', - 'user.primaryPhoneNumber': user?.primaryPhoneNumber?.phoneNumber || '', - }; -}; - -const parseTokensFromLocalizedString = ( - s: string, - tokens: Tokens, -): { normalisedString: string; expressions: TokenExpression[] } => { - const matches = (s.match(/{{.+?}}/g) || []).map(m => m.replace(/[{}]/g, '')); - const parsedMatches = matches.map(m => m.split('|').map(m => m.trim())); - const expressions = parsedMatches - .filter(match => match[0] in tokens) - .map(([token, ...modifiers]) => ({ - token, - modifiers: modifiers.map(m => getModifierWithParams(m)).filter(m => assertKnownModifier(m.modifierName)), - })); - - let normalisedString = s; - expressions.forEach(({ token }) => { - // Marking the position of each token with _++token++_ so we can easily - // replace it with its localized value in the next step - normalisedString = normalisedString.replace(/{{.+?}}/, `_++${token}++_`); - }); - return { expressions: expressions as TokenExpression[], normalisedString }; -}; - -const applyTokenExpressions = (s: string, expressions: TokenExpression[], tokens: Tokens) => { - expressions.forEach(({ token, modifiers }) => { - const value = modifiers.reduce((acc, mod) => { - try { - return MODIFIERS[mod.modifierName](acc, ...mod.params); - } catch (e) { - console.warn(e); - return ''; - } - }, tokens[token]); - s = s.replace(`_++${token}++_`, value); - }); - return s; -}; - -const assertKnownModifier = (s: any): s is Modifier => Object.prototype.hasOwnProperty.call(MODIFIERS, s); - -const getModifierWithParams = (modifierExpression: string) => { - const parts = modifierExpression - .split(/[(,)]/g) - .map(m => m.trim()) - .filter(m => !!m); - if (parts.length === 1) { - const [modifierName] = parts; - return { modifierName, params: [] }; - } else { - const [modifierName, ...params] = parts; - return { modifierName, params: params.map(p => p.replace(/['"]+/g, '')) }; - } -}; diff --git a/packages/clerk-js/src/ui.retheme/localization/defaultEnglishResource.ts b/packages/clerk-js/src/ui.retheme/localization/defaultEnglishResource.ts deleted file mode 100644 index b59b108a9d1..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/defaultEnglishResource.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { enUS } from '@clerk/localizations'; -import type { DeepRequired } from '@clerk/types'; - -export const defaultResource = enUS as DeepRequired; diff --git a/packages/clerk-js/src/ui.retheme/localization/index.ts b/packages/clerk-js/src/ui.retheme/localization/index.ts deleted file mode 100644 index 7c3c6b8995e..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './makeLocalizable'; -export * from './localizationKeys'; -export * from './defaultEnglishResource'; -export * from './parseLocalization'; diff --git a/packages/clerk-js/src/ui.retheme/localization/localizationKeys.ts b/packages/clerk-js/src/ui.retheme/localization/localizationKeys.ts deleted file mode 100644 index 016eea05e95..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/localizationKeys.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { PathValue, RecordToPath } from '@clerk/types'; - -import type { defaultResource } from './defaultEnglishResource'; - -type Value = string | number | boolean | Date; -type Whitespace = ' ' | '\t' | '\n' | '\r'; - -type Trim = T extends `${Whitespace}${infer Rest}` - ? Trim - : T extends `${infer Rest}${Whitespace}` - ? Trim - : T extends string - ? T - : never; - -type RemovePipeUtils = Text extends `${infer Left}|titleize${infer Right}` - ? `${Left}${Right}` - : Text; - -type FindBlocks = Text extends `${string}{{${infer Right}` - ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] - ? [Block, ...FindBlocks] - : never - : []; - -type TupleFindBlocks = T extends readonly [infer First, ...infer Rest] - ? [...FindBlocks, ...TupleFindBlocks] - : []; - -type ReadBlock< - Block extends string, - Tail extends string, - Depth extends string, -> = Tail extends `${infer L1}}}${infer R1}` - ? L1 extends `${infer L2}{{${infer R2}` - ? ReadBlock<`${Block}${L2}{{`, `${R2}}}${R1}`, `${Depth}+`> - : Depth extends `+${infer Rest}` - ? ReadBlock<`${Block}${L1}}}`, R1, Rest> - : [`${Block}${L1}`, R1] - : []; - -/** Parse block, return variables with types and recursively find nested blocks within */ -type ParseBlock = Block extends `${infer Name},${infer Format},${infer Rest}` - ? { [K in Trim]: VariableType> } & TupleParseBlock>> - : Block extends `${infer Name},${infer Format}` - ? { [K in Trim]: VariableType> } - : { [K in Trim]: Value }; - -/** Parse block for each tuple entry */ -type TupleParseBlock = T extends readonly [infer First, ...infer Rest] - ? ParseBlock & TupleParseBlock - : unknown; - -type VariableType = T extends 'number' | 'plural' | 'selectordinal' - ? number - : T extends 'date' | 'time' - ? Date - : Value; - -export type GetICUArgs> = TupleParseBlock< - T extends readonly string[] ? TupleFindBlocks : FindBlocks ->; - -type DefaultLocalizationKey = RecordToPath; -type LocalizationKeyToValue

= PathValue; - -// @ts-ignore -type LocalizationKeyToParams

= GetICUArgs>; - -export type LocalizationKey = { - key: string; - params: Record | undefined; -}; - -export const localizationKeys = >( - key: Key, - params?: keyof Params extends never ? never : Params, -): LocalizationKey => { - return { key, params } as LocalizationKey; -}; diff --git a/packages/clerk-js/src/ui.retheme/localization/localizationModifiers.ts b/packages/clerk-js/src/ui.retheme/localization/localizationModifiers.ts deleted file mode 100644 index 1e84382923c..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/localizationModifiers.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { normalizeDate, titleize } from '@clerk/shared'; - -const timeString = (val: Date | string | number, locale?: string) => { - try { - return new Intl.DateTimeFormat(locale || 'en-US', { timeStyle: 'short' }).format(normalizeDate(val)); - } catch (e) { - console.warn(e); - return ''; - } -}; - -const weekday = (val: Date | string | number, locale?: string, weekday?: 'long' | 'short' | 'narrow' | undefined) => { - try { - return new Intl.DateTimeFormat(locale || 'en-US', { weekday: weekday || 'long' }).format(normalizeDate(val)); - } catch (e) { - console.warn(e); - return ''; - } -}; - -const numeric = (val: Date | number | string, locale?: string) => { - try { - return new Intl.DateTimeFormat(locale || 'en-US').format(normalizeDate(val)); - } catch (e) { - console.warn(e); - return ''; - } -}; - -export const MODIFIERS = { - titleize, - timeString, - weekday, - numeric, -} as const; diff --git a/packages/clerk-js/src/ui.retheme/localization/makeLocalizable.tsx b/packages/clerk-js/src/ui.retheme/localization/makeLocalizable.tsx deleted file mode 100644 index 96f4604ed94..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/makeLocalizable.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { isClerkRuntimeError } from '@clerk/shared/error'; -import type { ClerkAPIError, ClerkRuntimeError, LocalizationResource } from '@clerk/types'; -import React from 'react'; - -import { useOptions } from '../contexts'; -import { readObjectPath } from '../utils'; -import type { GlobalTokens } from './applyTokensToString'; -import { applyTokensToString, useGlobalTokens } from './applyTokensToString'; -import { defaultResource } from './defaultEnglishResource'; -import type { LocalizationKey } from './localizationKeys'; -import { localizationKeys } from './localizationKeys'; -import { useParsedLocalizationResource } from './parseLocalization'; - -type Localizable = T & { - localizationKey?: LocalizationKey | string; -}; - -type LocalizablePrimitive = React.FunctionComponent>; - -export const makeLocalizable = (Component: React.FunctionComponent

): LocalizablePrimitive

=> { - const localizableComponent = React.forwardRef((props: Localizable, ref) => { - const parsedResource = useParsedLocalizationResource(); - const { localizationKey, ...restProps } = props; - const globalTokens = useGlobalTokens(); - - if (!localizationKey) { - return ( - - ); - } - - if (typeof localizationKey === 'string') { - return ( - - {localizationKey} - - ); - } - - return ( - - {localizedStringFromKey(localizationKey, parsedResource, globalTokens) || restProps.children} - - ); - }); - - const displayName = Component.displayName || Component.name || 'Component'; - localizableComponent.displayName = `Localizable${displayName}`.replace('_', ''); - return localizableComponent as LocalizablePrimitive

; -}; - -export const useLocalizations = () => { - const { localization } = useOptions(); - const parsedResource = useParsedLocalizationResource(); - const globalTokens = useGlobalTokens(); - - const t = (localizationKey: LocalizationKey | string | undefined) => { - if (!localizationKey || typeof localizationKey === 'string') { - return localizationKey || ''; - } - return localizedStringFromKey(localizationKey, parsedResource, globalTokens); - }; - - const translateError = (error: ClerkRuntimeError | ClerkAPIError | string | undefined) => { - if (!error || typeof error === 'string') { - return t(error); - } - - if (isClerkRuntimeError(error)) { - return t(localizationKeys(`unstable__errors.${error.code}` as any)) || error.message; - } - - const { code, message, longMessage, meta } = (error || {}) as ClerkAPIError; - const { paramName = '' } = meta || {}; - - if (!code) { - return ''; - } - - return ( - t(localizationKeys(`unstable__errors.${code}__${paramName}` as any)) || - t(localizationKeys(`unstable__errors.${code}` as any)) || - longMessage || - message - ); - }; - - return { t, translateError, locale: localization?.locale || defaultResource?.locale }; -}; - -const localizationKeyAttribute = (localizationKey: LocalizationKey) => { - return localizationKey.key; -}; - -const localizedStringFromKey = ( - localizationKey: LocalizationKey, - resource: LocalizationResource, - globalTokens: GlobalTokens, -): string => { - const key = localizationKey.key; - const base = readObjectPath(resource, key) as string; - const params = localizationKey.params; - const tokens = { ...globalTokens, ...params }; - return applyTokensToString(base || '', tokens); -}; diff --git a/packages/clerk-js/src/ui.retheme/localization/parseLocalization.ts b/packages/clerk-js/src/ui.retheme/localization/parseLocalization.ts deleted file mode 100644 index 63ecb5605cd..00000000000 --- a/packages/clerk-js/src/ui.retheme/localization/parseLocalization.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { DeepPartial, LocalizationResource } from '@clerk/types'; -import { dequal as deepEqual } from 'dequal'; - -import { useOptions } from '../contexts'; -import { fastDeepMergeAndReplace } from '../utils'; -import { defaultResource } from './defaultEnglishResource'; - -let cache: LocalizationResource | undefined; -let prev: DeepPartial | undefined; - -const parseLocalizationResource = ( - userDefined: DeepPartial, - base: LocalizationResource, -): LocalizationResource => { - if (!cache || (!!prev && prev !== userDefined && !deepEqual(userDefined, prev))) { - prev = userDefined; - const res = {} as LocalizationResource; - fastDeepMergeAndReplace(base, res); - fastDeepMergeAndReplace(userDefined, res); - cache = res; - return cache; - } - return cache; -}; - -export const useParsedLocalizationResource = () => { - const { localization } = useOptions(); - return parseLocalizationResource(localization || {}, defaultResource as any as LocalizationResource); -}; diff --git a/packages/clerk-js/src/ui.retheme/portal/index.tsx b/packages/clerk-js/src/ui.retheme/portal/index.tsx deleted file mode 100644 index 7ecac34394e..00000000000 --- a/packages/clerk-js/src/ui.retheme/portal/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { RoutingOptions } from '@clerk/types'; -import React, { Suspense } from 'react'; -import ReactDOM from 'react-dom'; - -import { PRESERVED_QUERYSTRING_PARAMS } from '../../core/constants'; -import { clerkErrorPathRouterMissingPath } from '../../core/errors'; -import { normalizeRoutingOptions } from '../../utils/authPropHelpers'; -import { ComponentContext } from '../contexts'; -import { HashRouter, PathRouter } from '../router'; -import type { AvailableComponentCtx } from '../types'; - -type PortalProps> = { - node: HTMLDivElement; - component: React.FunctionComponent | React.ComponentClass; - // Aligning this with props attributes of ComponentControls - props: PropsType & RoutingOptions; -} & Pick; - -export class Portal extends React.PureComponent> { - render(): React.ReactPortal { - const { props, component, componentName, node } = this.props; - const normalizedProps = { ...props, ...normalizeRoutingOptions({ routing: props.routing, path: props.path }) }; - - const el = ( - - {React.createElement(component, normalizedProps)} - - ); - - if (normalizedProps?.routing === 'path') { - if (!normalizedProps?.path) { - clerkErrorPathRouterMissingPath(componentName); - } - - return ReactDOM.createPortal( - - {el} - , - node, - ); - } - - return ReactDOM.createPortal({el}, node); - } -} diff --git a/packages/clerk-js/src/ui.retheme/primitives/Alert.tsx b/packages/clerk-js/src/ui.retheme/primitives/Alert.tsx deleted file mode 100644 index 17023e4f918..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Alert.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; -import type { FlexProps } from './Flex'; -import { Flex } from './Flex'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - padding: `${theme.space.$3} ${theme.space.$4}`, - backgroundColor: theme.colors.$blackAlpha50, - ...common.borderVariants(theme).normal, - }, - variants: {}, -})); - -export type AlertProps = FlexProps & StyleVariants; - -export const Alert = (props: AlertProps): JSX.Element => { - return ( - // @ts-ignore - - {props.children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/AlertIcon.tsx b/packages/clerk-js/src/ui.retheme/primitives/AlertIcon.tsx deleted file mode 100644 index 2450a9ee3ef..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/AlertIcon.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ExclamationCircle, ExclamationTriangle } from '../icons'; -import type { StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - marginRight: theme.space.$2x5, - width: theme.sizes.$4, - height: theme.sizes.$4, - }, - variants: { - colorScheme: { - danger: { color: theme.colors.$danger500 }, - warning: { color: theme.colors.$warning500 }, - success: { color: theme.colors.$success500 }, - primary: { color: theme.colors.$primary500 }, - }, - }, -})); - -type OwnProps = { variant: 'danger' | 'warning' }; - -export type AlertIconProps = OwnProps & StyleVariants; - -export const AlertIcon = (props: AlertIconProps): JSX.Element => { - const { variant, ...rest } = props; - const Icon = variant === 'warning' ? ExclamationCircle : ExclamationTriangle; - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Badge.tsx b/packages/clerk-js/src/ui.retheme/primitives/Badge.tsx deleted file mode 100644 index 9ac7351d498..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Badge.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { PropsOfComponent, StyleVariants } from '../styledSystem'; -import { common, createCssVariables, createVariants } from '../styledSystem'; -import { colors } from '../utils'; -import { Flex } from './Flex'; - -const vars = createCssVariables('accent', 'bg'); - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - color: vars.accent, - backgroundColor: vars.bg, - borderRadius: theme.radii.$sm, - padding: `${theme.space.$0x5} ${theme.space.$1x5}`, - display: 'inline-flex', - }, - variants: { - textVariant: { ...common.textVariants(theme) }, - colorScheme: { - primary: { - [vars.accent]: theme.colors.$primary500, - [vars.bg]: colors.setAlpha(theme.colors.$primary400, 0.2), - }, - danger: { - [vars.accent]: theme.colors.$danger500, - [vars.bg]: theme.colors.$danger100, - }, - neutral: { - [vars.accent]: theme.colors.$blackAlpha600, - [vars.bg]: theme.colors.$blackAlpha200, - }, - success: { - [vars.accent]: theme.colors.$success500, - [vars.bg]: colors.setAlpha(theme.colors.$success50, 0.2), - }, - warning: { - [vars.accent]: theme.colors.$warning600, - [vars.bg]: theme.colors.$warning100, - }, - }, - }, - defaultVariants: { - colorScheme: 'primary', - textVariant: 'caption', - }, -})); - -// @ts-ignore -export type BadgeProps = PropsOfComponent & StyleVariants; - -export const Badge = (props: BadgeProps) => { - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Box.tsx b/packages/clerk-js/src/ui.retheme/primitives/Box.tsx deleted file mode 100644 index 5b0d941b6d2..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Box.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import type { AsProp, PrimitiveProps, StateProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; - -const { applyVariants } = createVariants(() => ({ - base: { - boxSizing: 'inherit', - }, - variants: {}, -})); - -export type BoxProps = StateProps & PrimitiveProps<'div'> & AsProp & StyleVariants; - -export const Box = React.forwardRef((props, ref) => { - // Simply ignore non-native props if they reach here - const { as: As = 'div', ...rest } = props; - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Button.tsx b/packages/clerk-js/src/ui.retheme/primitives/Button.tsx deleted file mode 100644 index 444b67a9115..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Button.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import React from 'react'; - -import { descriptors, Icon, Spinner } from '../customizables'; -import { ArrowRightButtonIcon } from '../icons'; -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; -import { Flex } from './Flex'; - -const { applyVariants, filterProps } = createVariants((theme, props: OwnProps) => { - return { - base: { - margin: 0, - padding: 0, - border: 0, - outline: 0, - userSelect: 'none', - cursor: 'pointer', - backgroundColor: 'unset', - color: 'currentColor', - borderRadius: theme.radii.$md, - position: 'relative', - isolation: 'isolate', - ...common.centeredFlex('inline-flex'), - ...common.disabled(theme), - transitionProperty: theme.transitionProperty.$common, - transitionDuration: theme.transitionDuration.$controls, - }, - variants: { - textVariant: common.textVariants(theme), - size: { - iconLg: { minHeight: theme.sizes.$13, width: theme.sizes.$13 }, - xs: { minHeight: theme.sizes.$1x5, padding: `${theme.space.$1x5} ${theme.space.$1x5}` }, - sm: { - minHeight: theme.sizes.$7, - padding: `${theme.space.$1x5} ${theme.space.$3x5}`, - }, - md: { - minHeight: theme.sizes.$9, - padding: `${theme.space.$2x5} ${theme.space.$5}`, - letterSpacing: theme.sizes.$xxs, - }, - }, - variant: { - primary: { - backgroundColor: theme.colors.$primary500, - color: theme.colors.$colorTextOnPrimaryBackground, - ...common.buttonShadow, - ':before': { - position: 'absolute', - content: '""', - borderRadius: 'inherit', - zIndex: -1, - inset: 0, - background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha300} 0%, ${theme.colors.$transparent} 100%)`, - }, - ':after': { - position: 'absolute', - content: '""', - borderRadius: 'inherit', - zIndex: -1, - inset: 0, - opacity: 0, - transitionProperty: theme.transitionProperty.$common, - transitionDuration: theme.transitionDuration.$controls, - background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha200} 0%, ${theme.colors.$transparent} 100%)`, - }, - ':hover::after': { - opacity: 1, - }, - ':active::after': { - opacity: 0, - }, - }, - primaryDanger: { - backgroundColor: theme.colors.$danger500, - color: theme.colors.$colorTextOnPrimaryBackground, - ...common.buttonShadow, - ':before': { - position: 'absolute', - content: '""', - borderRadius: 'inherit', - zIndex: -1, - inset: 0, - background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha300} 0%, ${theme.colors.$transparent} 100%)`, - }, - ':after': { - position: 'absolute', - content: '""', - borderRadius: 'inherit', - zIndex: -1, - inset: 0, - opacity: 0, - transitionProperty: theme.transitionProperty.$common, - transitionDuration: theme.transitionDuration.$controls, - background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha200} 0%, ${theme.colors.$transparent} 100%)`, - }, - ':hover::after': { - opacity: 1, - }, - ':active::after': { - opacity: 0, - }, - }, - secondary: { - backgroundColor: theme.colors.$colorBackground, - color: theme.colors.$blackAlpha700, - '&:hover': { - backgroundColor: theme.colors.$blackAlpha50, - color: theme.colors.$blackAlpha950, - }, - '&:focus': props.hoverAsFocus - ? { backgroundColor: theme.colors.$blackAlpha50, color: theme.colors.$blackAlpha950 } - : undefined, - '&:active': { backgroundColor: theme.colors.$colorBackground }, - boxShadow: theme.shadows.$secondaryButtonShadow, - }, - secondaryDanger: { - backgroundColor: theme.colors.$colorBackground, - color: theme.colors.$danger500, - '&:hover': { - backgroundColor: theme.colors.$danger50, - color: theme.colors.$danger500, - }, - '&:focus': props.hoverAsFocus - ? { backgroundColor: theme.colors.$danger50, color: theme.colors.$danger500 } - : undefined, - '&:active': { backgroundColor: theme.colors.$colorBackground }, - boxShadow: theme.shadows.$secondaryButtonShadow, - }, - ghost: { - color: theme.colors.$blackAlpha700, - '&:hover': { backgroundColor: theme.colors.$blackAlpha50, color: theme.colors.$blackAlpha950 }, - '&:focus': props.hoverAsFocus - ? { backgroundColor: theme.colors.$blackAlpha50, color: theme.colors.$blackAlpha950 } - : undefined, - '&:active': { backgroundColor: theme.colors.$transparent }, - }, - ghostPrimary: { - color: theme.colors.$primary500, - '&:hover': { backgroundColor: theme.colors.$blackAlpha50, color: theme.colors.$primary500 }, - '&:focus': props.hoverAsFocus ? { backgroundColor: theme.colors.$blackAlpha50 } : undefined, - '&:active': { backgroundColor: theme.colors.$transparent }, - }, - ghostDanger: { - color: theme.colors.$danger500, - '&:hover': { backgroundColor: theme.colors.$danger50, color: theme.colors.$danger500 }, - '&:focus': props.hoverAsFocus ? { backgroundColor: theme.colors.$danger50 } : undefined, - '&:active': { backgroundColor: theme.colors.$transparent }, - }, - link: { - ...common.textVariants(theme).buttonSmall, - minHeight: 'fit-content', - height: 'fit-content', - width: 'fit-content', - textTransform: 'none', - padding: 0, - color: theme.colors.$primary500, - '&:hover': { textDecoration: 'underline' }, - '&:focus': props.hoverAsFocus ? { textDecoration: 'underline' } : undefined, - }, - linkDanger: { - ...common.textVariants(theme).buttonSmall, - minHeight: 'fit-content', - height: 'fit-content', - width: 'fit-content', - textTransform: 'none', - padding: 0, - color: theme.colors.$danger500, - '&:hover': { textDecoration: 'underline' }, - '&:focus': props.hoverAsFocus ? { textDecoration: 'underline' } : undefined, - }, - roundWrapper: { padding: 0, margin: 0, height: 'unset', width: 'unset', minHeight: 'unset' }, - }, - block: { - true: { width: '100%' }, - }, - focusRing: { - true: { ...common.focusRing(theme) }, - }, - }, - defaultVariants: { - textVariant: 'buttonLarge', - variant: 'primary', - size: 'sm', - focusRing: true, - }, - }; -}); -type OwnProps = PrimitiveProps<'button'> & { - isLoading?: boolean; - loadingText?: string; - isDisabled?: boolean; - isActive?: boolean; - hoverAsFocus?: boolean; - hasArrow?: boolean; -}; - -type ButtonProps = OwnProps & StyleVariants; - -const ButtonChildrenWithArrow = ({ children }: PropsWithChildren) => { - return ( - - {children} - ({ - width: t.sizes.$2x5, - height: t.sizes.$2x5, - opacity: t.opacity.$inactive, - })} - /> - - ); -}; - -const Button = React.forwardRef((props, ref) => { - const parsedProps: ButtonProps = { ...props, isDisabled: props.isDisabled || props.isLoading }; - const { - isLoading, - isDisabled, - hoverAsFocus, - loadingText, - children, - hasArrow, - onClick: onClickProp, - ...rest - } = filterProps(parsedProps); - - const onClick: React.MouseEventHandler = e => { - if (rest.type !== 'submit') { - e.preventDefault(); - } - return onClickProp?.(e); - }; - - return ( - - ); -}); - -const SimpleButton = React.forwardRef((props, ref) => { - const parsedProps: ButtonProps = { ...props, isDisabled: props.isDisabled || props.isLoading }; - - const { loadingText, isDisabled, hoverAsFocus, children, onClick: onClickProp, ...rest } = filterProps(parsedProps); - - const onClick: React.MouseEventHandler = e => { - if (rest.type !== 'submit') { - e.preventDefault(); - } - return onClickProp?.(e); - }; - - return ( - - ); -}); - -export { Button, SimpleButton }; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Flex.tsx b/packages/clerk-js/src/ui.retheme/primitives/Flex.tsx deleted file mode 100644 index 40f4c08fe50..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Flex.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; - -import type { StateProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - display: 'flex', - }, - variants: { - direction: { - row: { flexDirection: 'row' }, - col: { flexDirection: 'column' }, - rowReverse: { flexDirection: 'row-reverse' }, - columnReverse: { flexDirection: 'column-reverse' }, - }, - align: { - start: { alignItems: 'flex-start' }, - center: { alignItems: 'center' }, - end: { alignItems: 'flex-end' }, - stretch: { alignItems: 'stretch' }, - baseline: { alignItems: 'baseline' }, - }, - justify: { - start: { justifyContent: 'flex-start' }, - center: { justifyContent: 'center' }, - end: { justifyContent: 'flex-end' }, - between: { justifyContent: 'space-between' }, - }, - wrap: { - noWrap: { flexWrap: 'nowrap' }, - wrap: { flexWrap: 'wrap' }, - wrapReverse: { flexWrap: 'wrap-reverse' }, - }, - gap: { - 1: { gap: theme.space.$1 }, - 2: { gap: theme.space.$2 }, - 3: { gap: theme.space.$3 }, - 4: { gap: theme.space.$4 }, - 5: { gap: theme.space.$5 }, - 6: { gap: theme.space.$6 }, - 7: { gap: theme.space.$7 }, - 8: { gap: theme.space.$8 }, - 9: { gap: theme.space.$9 }, - }, - center: { - true: { justifyContent: 'center', alignItems: 'center' }, - }, - }, - defaultVariants: { - direction: 'row', - align: 'stretch', - justify: 'start', - wrap: 'noWrap', - }, -})); - -// @ts-ignore -export type FlexProps = StateProps & BoxProps & StyleVariants; - -export const Flex = React.forwardRef((props, ref) => { - return ( - - ); -}); - -export const Col = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Form.tsx b/packages/clerk-js/src/ui.retheme/primitives/Form.tsx deleted file mode 100644 index c7cbf7471c4..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Form.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps } from '../styledSystem'; -import type { FlexProps } from './Flex'; -import { Flex } from './Flex'; - -export type FormProps = PrimitiveProps<'form'> & Omit; - -export const Form = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/FormErrorText.tsx b/packages/clerk-js/src/ui.retheme/primitives/FormErrorText.tsx deleted file mode 100644 index 595827c1e2c..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/FormErrorText.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { forwardRef } from 'react'; - -import { Icon } from '../customizables'; -import { ExclamationCircle } from '../icons'; -import type { StyleVariants } from '../styledSystem'; -import { animations, createVariants } from '../styledSystem'; -import { useFormField } from './hooks'; -import { Text } from './Text'; - -const { applyVariants } = createVariants(theme => ({ - base: { - willChange: 'transform, opacity, height', - marginTop: theme.sizes.$2, - animation: `${animations.textInSmall} ${theme.transitionDuration.$fast}`, - display: 'flex', - gap: theme.sizes.$1, - position: 'absolute', - top: '0', - }, - variants: {}, -})); - -type FormErrorTextProps = React.PropsWithChildren>; - -export const FormErrorText = forwardRef((props, ref) => { - const { hasError, errorMessageId } = useFormField() || {}; - - if (!hasError && !props.children) { - return null; - } - - const { children, ...rest } = props; - - return ( - - - {children} - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/FormInfoText.tsx b/packages/clerk-js/src/ui.retheme/primitives/FormInfoText.tsx deleted file mode 100644 index 6483895ed62..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/FormInfoText.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { forwardRef } from 'react'; - -import type { FormTextProps } from './FormSuccessText'; -import { applyVariants } from './FormSuccessText'; -import { useFormField } from './hooks'; -import { Text } from './Text'; - -export const FormInfoText = forwardRef((props, ref) => { - const { hasError, errorMessageId } = useFormField() || {}; - - if (!hasError && !props.children) { - return null; - } - - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/FormLabel.tsx b/packages/clerk-js/src/ui.retheme/primitives/FormLabel.tsx deleted file mode 100644 index 4f6300fd6df..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/FormLabel.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, RequiredProp, StateProps, StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; -import { useFormField } from './hooks'; - -const { applyVariants } = createVariants(theme => ({ - base: { - color: theme.colors.$colorText, - ...common.textVariants(theme).subtitle, - ...common.disabled(theme), - }, - variants: {}, -})); - -type OwnProps = React.PropsWithChildren; - -type FormLabelProps = PrimitiveProps<'label'> & StyleVariants & OwnProps & RequiredProp; - -export const FormLabel = (props: FormLabelProps) => { - const { id: fieldHtmlId } = useFormField(); - const { isRequired, htmlFor: htmlForProp, ...rest } = props; - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/FormSuccessText.tsx b/packages/clerk-js/src/ui.retheme/primitives/FormSuccessText.tsx deleted file mode 100644 index b879339bde9..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/FormSuccessText.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { forwardRef } from 'react'; - -import { Icon } from '../customizables'; -import { CheckCircle } from '../icons'; -import type { StyleVariants } from '../styledSystem'; -import { animations, createVariants } from '../styledSystem'; -import { Text } from './Text'; - -export const { applyVariants } = createVariants(theme => ({ - base: { - willChange: 'transform, opacity, height', - marginTop: theme.sizes.$2, - animation: `${animations.textInSmall} ${theme.transitionDuration.$fast}`, - display: 'flex', - gap: theme.sizes.$1, - position: 'absolute', - top: '0', - }, - variants: {}, -})); - -export type FormTextProps = React.PropsWithChildren>; - -export const FormSuccessText = forwardRef((props, ref) => { - const { children, ...rest } = props; - - return ( - - - {children} - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/FormWarningText.tsx b/packages/clerk-js/src/ui.retheme/primitives/FormWarningText.tsx deleted file mode 100644 index b88a3bdbb0d..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/FormWarningText.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { forwardRef } from 'react'; - -import { Icon } from '../customizables'; -import { ExclamationCircle } from '../icons'; -import type { FormTextProps } from './FormSuccessText'; -import { applyVariants } from './FormSuccessText'; -import { Text } from './Text'; - -export const FormWarningText = forwardRef((props, ref) => { - const { children, ...rest } = props; - - return ( - - - {children} - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Grid.tsx b/packages/clerk-js/src/ui.retheme/primitives/Grid.tsx deleted file mode 100644 index 80a8d2c61a9..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Grid.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; - -import type { StateProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - display: 'grid', - }, - variants: { - align: { - start: { alignItems: 'flex-start' }, - center: { alignItems: 'center' }, - end: { alignItems: 'flex-end' }, - stretch: { alignItems: 'stretch' }, - baseline: { alignItems: 'baseline' }, - }, - justify: { - start: { justifyContent: 'flex-start' }, - center: { justifyContent: 'center' }, - end: { justifyContent: 'flex-end' }, - between: { justifyContent: 'space-between' }, - around: { justifyContent: 'space-around' }, - stretch: { justifyContent: 'stretch' }, - }, - columns: { - 1: { gridTemplateColumns: '1fr' }, - 2: { gridTemplateColumns: 'repeat(2, 1fr)' }, - 3: { gridTemplateColumns: 'repeat(3, 1fr)' }, - 4: { gridTemplateColumns: 'repeat(4, 1fr)' }, - 6: { gridTemplateColumns: 'repeat(6, 1fr)' }, - }, - gap: { - 1: { gap: theme.space.$1 }, - 2: { gap: theme.space.$2 }, - 3: { gap: theme.space.$3 }, - 4: { gap: theme.space.$4 }, - 5: { gap: theme.space.$5 }, - 6: { gap: theme.space.$6 }, - 7: { gap: theme.space.$7 }, - 8: { gap: theme.space.$8 }, - 9: { gap: theme.space.$9 }, - }, - }, - defaultVariants: { - align: 'stretch', - justify: 'stretch', - wrap: 'noWrap', - }, -})); - -export type GridProps = StateProps & BoxProps & StyleVariants; - -export const Grid = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Heading.tsx b/packages/clerk-js/src/ui.retheme/primitives/Heading.tsx deleted file mode 100644 index b8c8fa6a4dc..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Heading.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - boxSizing: 'border-box', - color: `${theme.colors.$colorText}`, - margin: 0, - }, - variants: { - textVariant: { ...common.textVariants(theme) }, - }, - defaultVariants: { - as: 'h1', - textVariant: 'h1', - }, -})); - -// @ts-ignore -export type HeadingProps = PrimitiveProps<'div'> & StyleVariants & { as?: 'h1' }; - -export const Heading = (props: HeadingProps) => { - const { as: As = 'h1', ...rest } = props; - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Icon.tsx b/packages/clerk-js/src/ui.retheme/primitives/Icon.tsx deleted file mode 100644 index 38c8e1af6c3..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Icon.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; - -import type { StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - flexShrink: 0, - }, - variants: { - size: { - sm: { width: theme.sizes.$3, height: theme.sizes.$3 }, - md: { width: theme.sizes.$4, height: theme.sizes.$4 }, - lg: { width: theme.sizes.$5, height: theme.sizes.$5 }, - }, - colorScheme: { - success: { color: theme.colors.$success500 }, - danger: { color: theme.colors.$danger500 }, - warning: { color: theme.colors.$warning500 }, - neutral: { color: theme.colors.$blackAlpha400 }, - }, - }, - defaultVariants: { - size: 'md', - }, -})); - -// @ts-ignore -export type IconProps = StyleVariants & { - icon: React.ComponentType; -}; - -export const Icon = (props: IconProps): JSX.Element => { - const { icon: Icon, ...rest } = props; - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Image.tsx b/packages/clerk-js/src/ui.retheme/primitives/Image.tsx deleted file mode 100644 index 85a71e3ce22..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Image.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StateProps } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; - -export type ImageProps = PrimitiveProps<'img'> & StateProps; - -export const Image = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Input.tsx b/packages/clerk-js/src/ui.retheme/primitives/Input.tsx deleted file mode 100644 index 465b3915b03..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Input.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, RequiredProp, StyleVariants } from '../styledSystem'; -import { common, createVariants, mqu } from '../styledSystem'; -import { sanitizeInputProps, useFormField } from './hooks'; -import { useInput } from './hooks/useInput'; - -const { applyVariants, filterProps } = createVariants((theme, props) => ({ - base: { - boxSizing: 'inherit', - margin: 0, - padding: `${theme.space.$2x5} ${theme.space.$4}`, - backgroundColor: theme.colors.$colorInputBackground, - color: theme.colors.$colorInputText, - // outline support for Windows contrast themes - outline: 'transparent solid 2px', - outlineOffset: '2px', - width: props.type === 'checkbox' ? theme.sizes.$4 : '100%', - aspectRatio: props.type === 'checkbox' ? '1/1' : 'unset', - accentColor: theme.colors.$primary500, - ...common.textVariants(theme).body, - ...common.borderVariants(theme, props).normal, - ...common.disabled(theme), - [mqu.ios]: { - fontSize: theme.fontSizes.$md, - }, - ':autofill': { - animationName: 'onAutoFillStart', - }, - }, - variants: {}, -})); - -type OwnProps = { - isDisabled?: boolean; - hasError?: boolean; - focusRing?: boolean; - isSuccessful?: boolean; -}; - -export type InputProps = PrimitiveProps<'input'> & StyleVariants & OwnProps & RequiredProp; - -export const Input = React.forwardRef((props, ref) => { - const fieldControl = useFormField() || {}; - // @ts-expect-error Typescript is complaining that `errorMessageId` does not exist. We are clearly passing them from above. - const { errorMessageId, ...fieldControlProps } = sanitizeInputProps(fieldControl, ['errorMessageId']); - const propsWithoutVariants = filterProps({ - ...props, - hasError: props.hasError || fieldControlProps.hasError, - }); - const { onChange } = useInput(propsWithoutVariants.onChange); - const { isDisabled, hasError, focusRing, isRequired, ...rest } = propsWithoutVariants; - const _disabled = isDisabled || fieldControlProps.isDisabled; - const _required = isRequired || fieldControlProps.isRequired; - const _hasError = hasError || fieldControlProps.hasError; - - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Link.tsx b/packages/clerk-js/src/ui.retheme/primitives/Link.tsx deleted file mode 100644 index b03f706603c..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Link.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - boxSizing: 'border-box', - display: 'inline-flex', - alignItems: 'center', - margin: 0, - cursor: 'pointer', - ...common.focusRing(theme), - ...common.disabled(theme), - textDecoration: 'none', - '&:hover': { textDecoration: 'underline' }, - }, - variants: { - variant: common.textVariants(theme), - colorScheme: { - primary: { - color: theme.colors.$primary500, - '&:hover': { color: theme.colors.$primary400 }, - '&:active': { color: theme.colors.$primary600 }, - }, - danger: { - color: theme.colors.$danger500, - '&:hover': { color: theme.colors.$danger400 }, - '&:active': { color: theme.colors.$danger600 }, - }, - neutral: { - color: theme.colors.$colorTextSecondary, - }, - inherit: { color: 'inherit' }, - }, - }, - defaultVariants: { - colorScheme: 'primary', - variant: 'body', - }, -})); - -type OwnProps = { isExternal?: boolean; isDisabled?: boolean }; - -// @ts-ignore -export type LinkProps = PrimitiveProps<'a'> & OwnProps & StyleVariants; - -export const Link = (props: LinkProps): JSX.Element => { - const { isExternal, children, href, onClick, ...rest } = props; - - const onClickHandler = onClick - ? (e: React.MouseEvent) => { - if (!href) { - e.preventDefault(); - } - onClick(e); - } - : undefined; - - return ( - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/NotificationBadge.tsx b/packages/clerk-js/src/ui.retheme/primitives/NotificationBadge.tsx deleted file mode 100644 index d777081debd..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/NotificationBadge.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { PropsOfComponent, StyleVariants } from '../styledSystem'; -import { common, createCssVariables, createVariants } from '../styledSystem'; -import { Flex } from './Flex'; - -const vars = createCssVariables('accent', 'bg'); - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - color: vars.accent, - backgroundColor: vars.bg, - borderRadius: theme.radii.$sm, - height: theme.space.$4, - minWidth: theme.space.$4, - padding: `${theme.space.$0x5}`, - display: 'inline-flex', - }, - variants: { - textVariant: { ...common.textVariants(theme) }, - colorScheme: { - primary: { - [vars.accent]: theme.colors.$colorTextOnPrimaryBackground, - [vars.bg]: theme.colors.$primary500, - }, - }, - }, - defaultVariants: { - colorScheme: 'primary', - textVariant: 'caption', - }, -})); - -// @ts-ignore -export type NotificationBadgeProps = PropsOfComponent & StyleVariants; - -export const NotificationBadge = (props: NotificationBadgeProps) => { - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Spinner.tsx b/packages/clerk-js/src/ui.retheme/primitives/Spinner.tsx deleted file mode 100644 index ca3988e9c64..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Spinner.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { animations, createCssVariables, createVariants } from '../styledSystem'; - -const { size, thickness, speed } = createCssVariables('speed', 'size', 'thickness'); - -const { applyVariants, filterProps } = createVariants(theme => { - return { - base: { - display: 'inline-block', - borderRadius: '99999px', - borderTop: `${thickness} solid currentColor`, - borderRight: `${thickness} solid currentColor`, - borderBottomWidth: thickness, - borderLeftWidth: thickness, - borderBottomStyle: 'solid', - borderLeftStyle: 'solid', - borderBottomColor: theme.colors.$transparent, - borderLeftColor: theme.colors.$transparent, - opacity: 1, - animation: `${animations.spinning} ${speed} linear 0s infinite normal none running`, - width: [size], - height: [size], - minWidth: [size], - minHeight: [size], - }, - variants: { - colorScheme: { - primary: { borderTopColor: theme.colors.$primary500, borderRightColor: theme.colors.$primary500, opacity: 1 }, - neutral: { - borderTopColor: theme.colors.$blackAlpha700, - borderRightColor: theme.colors.$blackAlpha700, - opacity: 1, - }, - }, - thickness: { - sm: { [thickness]: theme.sizes.$0x5 }, - md: { [thickness]: theme.sizes.$1 }, - }, - size: { - xs: { [size]: theme.sizes.$3 }, - sm: { [size]: theme.sizes.$4 }, - md: { [size]: theme.sizes.$5 }, - lg: { [size]: theme.sizes.$6 }, - xl: { [size]: theme.sizes.$8 }, - }, - speed: { - slow: { [speed]: '600ms' }, - normal: { [speed]: '400ms' }, - }, - }, - defaultVariants: { - speed: 'normal', - thickness: 'sm', - size: 'sm', - }, - }; -}); - -type SpinnerProps = PrimitiveProps<'div'> & StyleVariants; -export const Spinner = (props: SpinnerProps) => { - return ( - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/Table.tsx b/packages/clerk-js/src/ui.retheme/primitives/Table.tsx deleted file mode 100644 index cb333fd1dc3..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Table.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -const { applyVariants, filterProps } = createVariants(theme => { - return { - base: { - borderBottom: theme.borders.$normal, - borderColor: theme.colors.$blackAlpha300, - borderCollapse: 'separate', - borderSpacing: '0', - 'td:not(:first-of-type)': { - paddingLeft: theme.space.$2, - }, - 'th:not(:first-of-type)': { - paddingLeft: theme.space.$2, - }, - 'tr > td': { - paddingBottom: theme.space.$2, - paddingTop: theme.space.$2, - paddingLeft: theme.space.$4, - paddingRight: theme.space.$4, - }, - 'tr > th:first-of-type': { - paddingLeft: theme.space.$5, - }, - 'thead::after': { - content: '""', - display: 'block', - paddingBottom: theme.space.$2, - }, - 'tbody::after': { - content: '""', - display: 'block', - paddingBottom: theme.space.$2, - }, - // border-radius for hover - 'tr > td:first-of-type': { - borderTopLeftRadius: theme.radii.$md, - borderBottomLeftRadius: theme.radii.$md, - }, - 'tr > td:last-of-type': { - borderTopRightRadius: theme.radii.$md, - borderBottomRightRadius: theme.radii.$md, - }, - width: '100%', - }, - variants: {}, - }; -}); - -export type TableProps = PrimitiveProps<'table'> & Omit & StyleVariants; - -export const Table = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Tbody.tsx b/packages/clerk-js/src/ui.retheme/primitives/Tbody.tsx deleted file mode 100644 index d3503ca0c8d..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Tbody.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -export type TbodyProps = PrimitiveProps<'tbody'> & Omit; - -export const Tbody = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Td.tsx b/packages/clerk-js/src/ui.retheme/primitives/Td.tsx deleted file mode 100644 index 51357ef6d36..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Td.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - fontSize: theme.fontSizes.$xs, - fontWeight: theme.fontWeights.$normal, - color: theme.colors.$colorText, - }, - variants: {}, -})); - -export type TdProps = PrimitiveProps<'td'> & Omit & StyleVariants; - -export const Td = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Text.tsx b/packages/clerk-js/src/ui.retheme/primitives/Text.tsx deleted file mode 100644 index a098b6470e6..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Text.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { common, createVariants } from '../styledSystem'; -import { applyDataStateProps } from './applyDataStateProps'; - -const { applyVariants, filterProps } = createVariants(theme => { - return { - base: { - boxSizing: 'border-box', - // TODO: this should probably be inherited - // and handled through cards - color: theme.colors.$colorText, - margin: 0, - fontSize: 'inherit', - ...common.disabled(theme), - }, - variants: { - variant: common.textVariants(theme), - colorScheme: { - primary: { color: theme.colors.$colorText }, - onPrimaryBg: { color: theme.colors.$colorTextOnPrimaryBackground }, - danger: { color: theme.colors.$danger500 }, - success: { color: theme.colors.$success500 }, - neutral: { color: theme.colors.$colorTextSecondary }, - inherit: { color: 'inherit' }, - }, - truncate: { - true: { - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - }, - }, - defaultVariants: { - variant: 'body', - colorScheme: 'inherit', - }, - }; -}); - -// @ts-ignore -export type TextProps = PrimitiveProps<'p'> & { isDisabled?: boolean } & StyleVariants & { - as?: 'p' | 'div' | 'label' | 'code' | 'span' | 'li' | 'a'; - }; - -export const Text = React.forwardRef((props, ref) => { - const { as: As = 'p', ...rest } = props; - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Th.tsx b/packages/clerk-js/src/ui.retheme/primitives/Th.tsx deleted file mode 100644 index bb912f7a348..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Th.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps, StyleVariants } from '../styledSystem'; -import { createVariants } from '../styledSystem'; -import { colors } from '../utils'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -const { applyVariants, filterProps } = createVariants(theme => ({ - base: { - textAlign: 'left', - fontSize: theme.fontSizes.$xs, - fontWeight: theme.fontWeights.$normal, - color: colors.setAlpha(theme.colors.$colorText, 0.62), - borderBottom: theme.borders.$normal, - borderColor: theme.colors.$blackAlpha300, - paddingBottom: theme.space.$2, - }, - variants: {}, -})); - -export type ThProps = PrimitiveProps<'th'> & Omit & StyleVariants; - -export const Th = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Thead.tsx b/packages/clerk-js/src/ui.retheme/primitives/Thead.tsx deleted file mode 100644 index 988c7d5253a..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Thead.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -export type TheadProps = PrimitiveProps<'thead'> & Omit; - -export const Thead = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/Tr.tsx b/packages/clerk-js/src/ui.retheme/primitives/Tr.tsx deleted file mode 100644 index ad0fe3be679..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/Tr.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import type { PrimitiveProps } from '../styledSystem'; -import type { BoxProps } from './Box'; -import { Box } from './Box'; - -export type TrProps = PrimitiveProps<'tr'> & Omit; - -export const Tr = React.forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/packages/clerk-js/src/ui.retheme/primitives/applyDataStateProps.ts b/packages/clerk-js/src/ui.retheme/primitives/applyDataStateProps.ts deleted file mode 100644 index 251e9260278..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/applyDataStateProps.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { StateProps } from '../styledSystem'; - -export const applyDataStateProps = (props: any) => { - const { hasError, isDisabled, isLoading, isOpen, isActive, ...rest } = props as StateProps; - return { - 'data-error': hasError || undefined, - 'data-disabled': isDisabled || undefined, - 'data-loading': isLoading || undefined, - 'data-open': isOpen || undefined, - 'data-active': isActive || undefined, - ...rest, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/hooks/index.ts b/packages/clerk-js/src/ui.retheme/primitives/hooks/index.ts deleted file mode 100644 index f0f19d12103..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './useFormControl'; -export * from './useInput'; diff --git a/packages/clerk-js/src/ui.retheme/primitives/hooks/useFormControl.tsx b/packages/clerk-js/src/ui.retheme/primitives/hooks/useFormControl.tsx deleted file mode 100644 index 31f949ecdde..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/hooks/useFormControl.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { createContextAndHook } from '@clerk/shared/react'; -import type { FieldId } from '@clerk/types'; -import React from 'react'; - -import type { useFormControl as useFormControlUtil } from '../../utils/useFormControl'; -import { useFormControlFeedback } from '../../utils/useFormControl'; - -type FormFieldProviderProps = ReturnType>['props'] & { - isDisabled: boolean; -}; - -type FormFieldContextValue = Omit & { - errorMessageId?: string; - id?: string; - fieldId?: FieldId; - hasError: boolean; - debouncedFeedback: ReturnType['debounced']; -}; - -/** - * Extract the context hook without the guarantee in order to avoid throwing errors if our field/form primitives are not wrapped inside a Field.Root component. - * In case our primitives need to always be wrapped with Field.Root, consider updating the following line to [FormFieldContext, useFormField] - */ -export const [FormFieldContext, , useFormField] = createContextAndHook('FormFieldContext'); - -export const FormFieldContextProvider = (props: React.PropsWithChildren) => { - const { - id: propsId, - isRequired = false, - isDisabled = false, - setError, - setSuccess, - setWarning, - setHasPassedComplexity, - setInfo, - clearFeedback, - children, - feedbackType, - feedback, - isFocused, - ...rest - } = props; - // TODO: This shouldnt be targettable - const id = `${propsId}-field`; - - /** - * Create a debounced version of the feedback. - */ - const { debounced } = useFormControlFeedback({ feedback, feedbackType, isFocused }); - - /** - * Both html attributes (e.g. data-invalid) and css styles depend on hasError being debounced - */ - const hasError = debounced.feedbackType === 'error'; - - /** - * Track whether the `FormErrorText` has been rendered. - * We use this to append its id the `aria-describedby` of the `input`. - */ - const errorMessageId = hasError ? `error-${propsId}` : ''; - const value = React.useMemo( - () => ({ - isRequired, - isDisabled, - hasError, - id, - fieldId: propsId, - errorMessageId, - setError, - setSuccess, - setWarning, - setInfo, - clearFeedback, - setHasPassedComplexity, - feedbackType, - feedback, - isFocused, - }), - [ - isRequired, - hasError, - id, - propsId, - errorMessageId, - isDisabled, - setError, - setSuccess, - setWarning, - setInfo, - clearFeedback, - setHasPassedComplexity, - feedbackType, - feedback, - isFocused, - ], - ); - - return ( - - {props.children} - - ); -}; - -/** - * Each of our Form primitives depend on different custom props - * This utility filters out any props that will litter the DOM, but allows for exceptions when the `keep` param is used. - * This allows for maintainers to opt-in and only allow for specific props to be passed for each primitive. - */ -export const sanitizeInputProps = ( - obj: ReturnType, - keep?: (keyof ReturnType)[], -) => { - const { - radioOptions, - validatePassword, - hasPassedComplexity, - isFocused, - feedback, - feedbackType, - setHasPassedComplexity, - setWarning, - setSuccess, - setError, - setInfo, - errorMessageId, - fieldId, - label, - clearFeedback, - infoText, - debouncedFeedback, - ...inputProps - } = obj; - /* eslint-enable */ - - keep?.forEach(key => { - /** - * Ignore error for the index type as we have defined it explicitly above - */ - // @ts-ignore - inputProps[key] = obj[key]; - }); - - return inputProps; -}; diff --git a/packages/clerk-js/src/ui.retheme/primitives/hooks/useInput.ts b/packages/clerk-js/src/ui.retheme/primitives/hooks/useInput.ts deleted file mode 100644 index b6d2f2eed1d..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/hooks/useInput.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { FormEvent } from 'react'; -import React from 'react'; - -export function useInput(callback: React.FormEventHandler | null | undefined): { - onChange: React.FormEventHandler; - ref: React.MutableRefObject; -} { - const ref = React.useRef(null); - - function onChange(e: FormEvent) { - e.persist(); - if (typeof callback === 'function') { - callback(e); - } - } - - return { - onChange, - ref, - }; -} diff --git a/packages/clerk-js/src/ui.retheme/primitives/index.ts b/packages/clerk-js/src/ui.retheme/primitives/index.ts deleted file mode 100644 index 445a2872f7a..00000000000 --- a/packages/clerk-js/src/ui.retheme/primitives/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -export * from './Box'; -export * from './Button'; -export * from './Flex'; -export * from './Grid'; -export * from './Heading'; -export * from './Text'; -export * from './Spinner'; -export * from './Input'; -export * from './Link'; -export * from './Image'; -export * from './Alert'; -export * from './AlertIcon'; -export * from './Input'; -export * from './FormErrorText'; -export * from './FormInfoText'; -export * from './FormSuccessText'; -export * from './FormWarningText'; -export * from './FormLabel'; -export * from './Form'; -export * from './Icon'; -export * from './Badge'; -export * from './Table'; -export * from './Thead'; -export * from './Tbody'; -export * from './Tr'; -export * from './Th'; -export * from './Td'; -export * from './NotificationBadge'; diff --git a/packages/clerk-js/src/ui.retheme/router/BaseRouter.tsx b/packages/clerk-js/src/ui.retheme/router/BaseRouter.tsx deleted file mode 100644 index 2a76d9e6072..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/BaseRouter.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import qs from 'qs'; -import React from 'react'; - -import { getQueryParams, trimTrailingSlash } from '../../utils'; -import { useWindowEventListener } from '../hooks'; -import { newPaths } from './newPaths'; -import { match } from './pathToRegexp'; -import { Route } from './Route'; -import { RouteContext } from './RouteContext'; - -interface BaseRouterProps { - basePath: string; - startPath: string; - getPath: () => string; - getQueryString: () => string; - internalNavigate: (toURL: URL) => Promise | any; - onExternalNavigate?: () => any; - refreshEvents?: Array; - preservedParams?: string[]; - urlStateParam?: { - startPath: string; - path: string; - componentName: string; - clearUrlStateParam: () => void; - socialProvider: string; - }; - children: React.ReactNode; -} - -export const BaseRouter = ({ - basePath, - startPath, - getPath, - getQueryString, - internalNavigate, - onExternalNavigate, - refreshEvents, - preservedParams, - urlStateParam, - children, -}: BaseRouterProps): JSX.Element => { - const { navigate: externalNavigate } = useClerk(); - - const [routeParts, setRouteParts] = React.useState({ - path: getPath(), - queryString: getQueryString(), - }); - const currentPath = routeParts.path; - const currentQueryString = routeParts.queryString; - const currentQueryParams = getQueryParams(routeParts.queryString); - - const resolve = (to: string): URL => { - return new URL(to, window.location.origin); - }; - - const getMatchData = (path?: string, index?: boolean) => { - const [newIndexPath, newFullPath] = newPaths('', '', path, index); - const currentPathWithoutSlash = trimTrailingSlash(currentPath); - - const matchResult = - (path && match(newFullPath + '/:foo*')(currentPathWithoutSlash)) || - (index && match(newIndexPath)(currentPathWithoutSlash)) || - (index && match(newFullPath)(currentPathWithoutSlash)) || - false; - if (matchResult !== false) { - return matchResult.params; - } else { - return false; - } - }; - - const matches = (path?: string, index?: boolean): boolean => { - return !!getMatchData(path, index); - }; - - const refresh = React.useCallback((): void => { - const newPath = getPath(); - const newQueryString = getQueryString(); - - if (newPath !== currentPath || newQueryString !== currentQueryString) { - setRouteParts({ - path: newPath, - queryString: newQueryString, - }); - } - }, [currentPath, currentQueryString, getPath, getQueryString]); - - useWindowEventListener(refreshEvents, refresh); - - // TODO: Look into the real possible types of globalNavigate - const baseNavigate = async (toURL: URL | undefined): Promise => { - if (!toURL) { - return; - } - - if (toURL.origin !== window.location.origin || !toURL.pathname.startsWith('/' + basePath)) { - if (onExternalNavigate) { - onExternalNavigate(); - } - const res = await externalNavigate(toURL.href); - refresh(); - return res; - } - - // For internal navigation, preserve any query params - // that are marked to be preserved - if (preservedParams) { - const toQueryParams = getQueryParams(toURL.search); - preservedParams.forEach(param => { - if (!toQueryParams[param] && currentQueryParams[param]) { - toQueryParams[param] = currentQueryParams[param]; - } - }); - toURL.search = qs.stringify(toQueryParams); - } - const internalNavRes = await internalNavigate(toURL); - setRouteParts({ path: toURL.pathname, queryString: toURL.search }); - return internalNavRes; - }; - - return ( - { - // - }, - resolve: resolve.bind(this), - refresh: refresh.bind(this), - params: {}, - urlStateParam: urlStateParam, - }} - > - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/router/HashRouter.tsx b/packages/clerk-js/src/ui.retheme/router/HashRouter.tsx deleted file mode 100644 index 0e44a73f39e..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/HashRouter.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; - -import { hasUrlInFragment, stripOrigin } from '../../utils'; -import { BaseRouter } from './BaseRouter'; - -export const hashRouterBase = 'CLERK-ROUTER/HASH'; - -interface HashRouterProps { - preservedParams?: string[]; - children: React.ReactNode; -} - -export const HashRouter = ({ preservedParams, children }: HashRouterProps): JSX.Element => { - const internalNavigate = async (toURL: URL): Promise => { - if (!toURL) { - return; - } - window.location.hash = stripOrigin(toURL).substring(1 + hashRouterBase.length); - return Promise.resolve(); - }; - - const fakeUrl = (): URL => { - // Create a URL object with the contents of the hash - // Use the origin because you can't create a url object without protocol and host - if (hasUrlInFragment(window.location.hash)) { - return new URL(window.location.origin + window.location.hash.substring(1)); - } else { - return new URL(window.location.origin); - } - }; - - const getPath = (): string => { - return fakeUrl().pathname === '/' ? '/' + hashRouterBase : '/' + hashRouterBase + fakeUrl().pathname; - }; - - const getQueryString = (): string => { - return fakeUrl().search; - }; - - return ( - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/router/PathRouter.tsx b/packages/clerk-js/src/ui.retheme/router/PathRouter.tsx deleted file mode 100644 index ac4c81109b3..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/PathRouter.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import type { NavigateOptions } from '@clerk/types'; -import React from 'react'; - -import { hasUrlInFragment, mergeFragmentIntoUrl, stripOrigin } from '../../utils'; -import { BaseRouter } from './BaseRouter'; - -interface PathRouterProps { - basePath: string; - preservedParams?: string[]; - children: React.ReactNode; -} - -export const PathRouter = ({ basePath, preservedParams, children }: PathRouterProps): JSX.Element | null => { - const { navigate } = useClerk(); - const [stripped, setStripped] = React.useState(false); - - if (!navigate) { - throw new Error('Clerk: Missing navigate option.'); - } - - const internalNavigate = (toURL: URL | string | undefined, options?: NavigateOptions) => { - if (!toURL) { - return; - } - // Only send the path - return navigate(stripOrigin(toURL), options); - }; - - const getPath = () => { - return window.location.pathname; - }; - - const getQueryString = () => { - return window.location.search; - }; - - React.useEffect(() => { - const convertHashToPath = async () => { - if (hasUrlInFragment(window.location.hash)) { - const url = mergeFragmentIntoUrl(new URL(window.location.href)); - await internalNavigate(url.href, { replace: true }); - setStripped(true); - } - }; - void convertHashToPath(); - }, [setStripped, navigate, window.location.hash]); - - if (hasUrlInFragment(window.location.hash) && !stripped) { - return null; - } - - return ( - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/router/Route.tsx b/packages/clerk-js/src/ui.retheme/router/Route.tsx deleted file mode 100644 index c68ab28c4e4..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/Route.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import type { LoadedClerk } from '@clerk/types'; -import React from 'react'; - -import { pathFromFullPath, trimTrailingSlash } from '../../utils'; -import { useNavigateToFlowStart } from '../hooks'; -import { newPaths } from './newPaths'; -import { match } from './pathToRegexp'; -import { RouteContext, useRouter } from './RouteContext'; - -interface RouteGuardProps { - canActivate: (clerk: LoadedClerk) => boolean; -} - -interface UnguardedRouteProps { - path?: string; - index?: boolean; - flowStart?: boolean; - canActivate?: never; -} -type GuardedRouteProps = { - path?: string; - index?: boolean; - flowStart?: boolean; -} & RouteGuardProps; - -export type RouteProps = React.PropsWithChildren; - -const RouteGuard = ({ canActivate, children }: React.PropsWithChildren): JSX.Element | null => { - const { navigateToFlowStart } = useNavigateToFlowStart(); - const clerk = useClerk(); - - React.useEffect(() => { - if (!canActivate(clerk)) { - void navigateToFlowStart(); - } - }); - if (canActivate(clerk)) { - return <>{children}; - } - return null; -}; - -export function Route(props: RouteProps): JSX.Element | null { - const router = useRouter(); - - if (!props.children) { - return null; - } - - if (!props.index && !props.path) { - return <>{props.children}; - } - - if (!router.matches(props.path, props.index)) { - return null; - } - - const [indexPath, fullPath] = newPaths(router.indexPath, router.fullPath, props.path, props.index); - - const resolve = (to: string) => { - const url = new URL(to, window.location.origin + fullPath + '/'); - url.pathname = trimTrailingSlash(url.pathname); - return url; - }; - - const newGetMatchData = (path?: string, index?: boolean) => { - const [newIndexPath, newFullPath] = newPaths(indexPath, fullPath, path, index); - const currentPath = trimTrailingSlash(router.currentPath); - const matchResult = - (path && match(newFullPath + '/:foo*')(currentPath)) || - (index && match(newIndexPath)(currentPath)) || - (index && match(newFullPath)(currentPath)) || - false; - if (matchResult !== false) { - return matchResult.params; - } else { - return false; - } - }; - - const rawParams = router.getMatchData(props.path, props.index) || {}; - const paramsDict: Record = {}; - for (const [key, value] of Object.entries(rawParams)) { - paramsDict[key] = value; - } - - const flowStartPath = - (props.flowStart - ? //set it as the old full path (the previous step), - //replacing the base path for navigateToFlowStart() to work as expected - pathFromFullPath(router.fullPath).replace('/' + router.basePath, '') - : router.flowStartPath) || router.startPath; - - return ( - { - return newGetMatchData(path, index) ? true : false; - }, - resolve: resolve, - navigate: (to: string) => { - const toURL = resolve(to); - return router.baseNavigate(toURL); - }, - refresh: router.refresh, - params: paramsDict, - urlStateParam: router.urlStateParam, - }} - > - {props.canActivate ? {props.children} : props.children} - - ); -} diff --git a/packages/clerk-js/src/ui.retheme/router/RouteContext.tsx b/packages/clerk-js/src/ui.retheme/router/RouteContext.tsx deleted file mode 100644 index 370e776ac96..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/RouteContext.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { ParsedQs } from 'qs'; -import React from 'react'; - -export interface RouteContextValue { - basePath: string; - startPath: string; - flowStartPath: string; - fullPath: string; - indexPath: string; - currentPath: string; - matches: (path?: string, index?: boolean) => boolean; - baseNavigate: (toURL: URL) => Promise; - navigate: (to: string) => Promise; - resolve: (to: string) => URL; - refresh: () => void; - params: { [key: string]: string }; - queryString: string; - queryParams: ParsedQs; - preservedParams?: string[]; - getMatchData: (path?: string, index?: boolean) => false | object; - urlStateParam?: { - startPath: string; - path: string; - componentName: string; - clearUrlStateParam: () => void; - socialProvider: string; - }; -} - -export const RouteContext = React.createContext(null); - -RouteContext.displayName = 'RouteContext'; - -export const useRouter = (): RouteContextValue => { - const ctx = React.useContext(RouteContext); - if (!ctx) { - throw new Error('useRouter called while Router is null'); - } - return ctx; -}; diff --git a/packages/clerk-js/src/ui.retheme/router/Switch.tsx b/packages/clerk-js/src/ui.retheme/router/Switch.tsx deleted file mode 100644 index 04a7b57839b..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/Switch.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import type { RouteProps } from './Route'; -import { Route } from './Route'; -import { useRouter } from './RouteContext'; - -function assertRoute(v: any): v is React.ReactElement { - return !!v && React.isValidElement(v) && typeof v === 'object' && (v as React.ReactElement).type === Route; -} - -export function Switch({ children }: { children: React.ReactNode }): JSX.Element { - const router = useRouter(); - - let node: React.ReactNode = null; - React.Children.forEach(children, child => { - if (node || !assertRoute(child)) { - return; - } - - const { index, path } = child.props; - if ((!index && !path) || router.matches(path, index)) { - node = child; - } - }); - - return <>{node}; -} diff --git a/packages/clerk-js/src/ui.retheme/router/VirtualRouter.tsx b/packages/clerk-js/src/ui.retheme/router/VirtualRouter.tsx deleted file mode 100644 index a589a33c3cf..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/VirtualRouter.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; - -import { useClerkModalStateParams } from '../hooks'; -import { BaseRouter } from './BaseRouter'; -export const VIRTUAL_ROUTER_BASE_PATH = 'CLERK-ROUTER/VIRTUAL'; - -interface VirtualRouterProps { - startPath: string; - preservedParams?: string[]; - onExternalNavigate?: () => any; - children: React.ReactNode; -} - -export const VirtualRouter = ({ - startPath, - preservedParams, - onExternalNavigate, - children, -}: VirtualRouterProps): JSX.Element => { - const [currentURL, setCurrentURL] = React.useState( - new URL('/' + VIRTUAL_ROUTER_BASE_PATH + startPath, window.location.origin), - ); - const { urlStateParam, removeQueryParam } = useClerkModalStateParams(); - - if (urlStateParam.componentName) { - removeQueryParam(); - } - - const internalNavigate = (toURL: URL | undefined) => { - if (!toURL) { - return; - } - setCurrentURL(toURL); - }; - - const getPath = () => currentURL.pathname; - - const getQueryString = () => currentURL.search; - - return ( - - {children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/router/__mocks__/RouteContext.tsx b/packages/clerk-js/src/ui.retheme/router/__mocks__/RouteContext.tsx deleted file mode 100644 index cabc7dfec95..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/__mocks__/RouteContext.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { noop } from '@clerk/shared'; - -export const useRouter = () => ({ - resolve: jest.fn(() => ({ - toURL: { - href: 'http://test.host/test-href', - }, - })), - matches: jest.fn(noop), - navigate: jest.fn(noop), -}); diff --git a/packages/clerk-js/src/ui.retheme/router/__tests__/HashRouter.test.tsx b/packages/clerk-js/src/ui.retheme/router/__tests__/HashRouter.test.tsx deleted file mode 100644 index 9eec0bd7b99..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/__tests__/HashRouter.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import type Clerk from '../../../core/clerk'; -import { HashRouter, Route, useRouter } from '..'; - -const mockNavigate = jest.fn(); - -jest.mock('@clerk/shared/react', () => { - return { - useClerk: () => { - return { - navigate: (to: string) => { - mockNavigate(to); - if (to) { - // @ts-ignore - window.location = new URL(to, window.location.origin); - } - return Promise.resolve(); - }, - } as Clerk; - }, - }; -}); - -const Button = ({ to, children }: React.PropsWithChildren<{ to: string }>) => { - const router = useRouter(); - return ( - - ); -}; - -const Tester = () => ( - - -

Index
- - - - -
Bar
-
- -); - -describe('HashRouter', () => { - const oldWindowLocation = window.location; - - beforeAll(() => { - // @ts-ignore - delete window.location; - }); - - afterAll(() => { - window.location = oldWindowLocation; - }); - - beforeEach(() => { - mockNavigate.mockReset(); - }); - - describe('when hash has a path included in it', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/hash#/foo'); - }); - - it('loads that path', () => { - // Wrap this is an await because we need a state update with the path change - render(); - - expect(mockNavigate).not.toHaveBeenCalled(); - expect(screen.queryByText('Bar')).toBeInTheDocument(); - }); - }); - - describe('when query has a preservedParam', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/hash#/?preserved=1'); - }); - - it('preserves the param for internal navigation', async () => { - render(); - - const button = screen.getByRole('button', { name: /Internal/i }); - await userEvent.click(button); - - expect(window.location.hash).toBe('#/foo?preserved=1'); - expect(screen.queryByText('Bar')).toBeInTheDocument(); - }); - - it('removes the param for external navigation', async () => { - render(); - - const button = screen.getByRole('button', { name: /External/i }); - await userEvent.click(button); - - expect(mockNavigate).toHaveBeenNthCalledWith(1, 'https://www.example.com/external'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/router/__tests__/PathRouter.test.tsx b/packages/clerk-js/src/ui.retheme/router/__tests__/PathRouter.test.tsx deleted file mode 100644 index 1b7750c1516..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/__tests__/PathRouter.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import type Clerk from '../../../core/clerk'; -import { PathRouter, Route, useRouter } from '..'; - -const mockNavigate = jest.fn(); - -jest.mock('@clerk/shared/react', () => { - return { - useClerk: () => { - return { - navigate: (to: string) => { - mockNavigate(to); - if (to) { - // @ts-ignore - window.location = new URL(to, window.location.origin); - } - return Promise.resolve(); - }, - } as Clerk; - }, - }; -}); - -const Button = ({ to, children }: React.PropsWithChildren<{ to: string }>) => { - const router = useRouter(); - return ( - - ); -}; - -const Tester = () => ( - - - - - - -); - -describe('PathRouter', () => { - const oldWindowLocation = window.location; - - beforeAll(() => { - // @ts-ignore - delete window.location; - }); - - afterAll(() => { - window.location = oldWindowLocation; - }); - - beforeEach(() => { - mockNavigate.mockReset(); - }); - - describe('when hash has a path included in it', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/foo#/bar'); - }); - - it('adds the hash path to the primary path', async () => { - render(); - - await waitFor(() => { - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/foo/bar'); - }); - }); - }); - - describe('when query has a preservedParam', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/foo/bar?preserved=1'); - }); - - it('preserves the param for internal navigation', async () => { - render(); - - const button = screen.getByRole('button', { name: /Internal/i }); - await userEvent.click(button); - - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/foo/baz?preserved=1'); - }); - - it('removes the param for external navigation', async () => { - render(); - - const button = screen.getByRole('button', { name: /External/i }); - await userEvent.click(button); - - expect(mockNavigate).toHaveBeenNthCalledWith(1, 'https://www.example.com/'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/router/__tests__/Switch.test.tsx b/packages/clerk-js/src/ui.retheme/router/__tests__/Switch.test.tsx deleted file mode 100644 index c27bd019140..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/__tests__/Switch.test.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { HashRouter, Route, Switch } from '../../router'; - -const mockNavigate = jest.fn(); - -jest.mock('@clerk/shared/react', () => ({ - useClerk: () => ({ - navigate: jest.fn(to => { - mockNavigate(to); - if (to) { - // @ts-ignore - window.location = new URL(to, window.location.origin); - } - return Promise.resolve(); - }), - }), -})); - -const oldWindowLocation = window.location; -const setWindowOrigin = (origin: string) => { - // @ts-ignore - delete window.location; - // the URL interface is very similar to window.location - // we use it to easily mock the location methods in tests - (window.location as any) = new URL(origin); -}; - -describe('', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - afterAll(() => { - window.location = oldWindowLocation; - }); - - it('ignores nodes that are not of type Route', () => { - setWindowOrigin('http://dashboard.example.com/#/first'); - render( - - - hey - first - - , - ); - - expect(screen.queryByText('hey')).toBeNull(); - expect(screen.queryByText('first 1')).toBeDefined(); - }); - - it('renders only the first Route that matches', () => { - setWindowOrigin('http://dashboard.example.com/#/first'); - render( - - - first 1 - first 2 - catchall - - , - ); - - expect(screen.queryByText('first 1')).toBeDefined(); - expect(screen.queryByText('first 2')).toBeNull(); - expect(screen.queryByText('catchall')).toBeNull(); - }); - - it('renders null if no route matches', () => { - setWindowOrigin('http://dashboard.example.com/#/cat'); - render( - - - first - second - third - - , - ); - - expect(screen.queryByText('first')).toBeNull(); - expect(screen.queryByText('second')).toBeNull(); - expect(screen.queryByText('third')).toBeNull(); - }); - - it('always matches a Route without path', () => { - setWindowOrigin('http://dashboard.example.com/#/cat'); - render( - - - first 1 - first 2 - catchall - - , - ); - - expect(screen.queryByText('first 1')).toBeNull(); - expect(screen.queryByText('first 2')).toBeNull(); - expect(screen.queryByText('catchall')).toBeDefined(); - }); - - it('always matches a Route without path even when other routes match down the tree', () => { - setWindowOrigin('http://dashboard.example.com/#/first'); - render( - - - catchall - first - - , - ); - - expect(screen.queryByText('catchall')).toBeDefined(); - expect(screen.queryByText('firs')).toBeNull(); - }); - - it('always matches a Route without path even if its an index route', () => { - setWindowOrigin('http://dashboard.example.com/#/first'); - render( - - - catchall - first - - , - ); - - expect(screen.queryByText('catchall')).toBeDefined(); - expect(screen.queryByText('firs')).toBeNull(); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/router/__tests__/VirtualRouter.test.tsx b/packages/clerk-js/src/ui.retheme/router/__tests__/VirtualRouter.test.tsx deleted file mode 100644 index 18b7eef065f..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/__tests__/VirtualRouter.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { Route, useRouter, VirtualRouter } from '..'; - -const mockNavigate = jest.fn(); - -jest.mock('@clerk/shared/react', () => ({ - useClerk: () => ({ - navigate: jest.fn(to => { - mockNavigate(to); - if (to) { - // @ts-ignore - window.location = new URL(to, window.location.origin); - } - return Promise.resolve(); - }), - }), -})); - -const Button = ({ to, children }: React.PropsWithChildren<{ to: string }>) => { - const router = useRouter(); - return ( - - ); -}; -const ShowPreserved = () => { - const { queryParams } = useRouter(); - return
{`preserved=${queryParams.preserved}`}
; -}; -const Tester = () => ( - - -
Index
- -
- -
Created
- -
- - - -
-); - -describe('VirtualRouter', () => { - const oldWindowLocation = window.location; - - beforeAll(() => { - // @ts-ignore - delete window.location; - }); - - afterAll(() => { - window.location = oldWindowLocation; - }); - - beforeEach(() => { - mockNavigate.mockReset(); - }); - - describe('when mounted', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/virtual'); - }); - - it('it loads the start path', async () => { - render(); - expect(screen.queryByText('Index')).toBeInTheDocument(); - }); - }); - - describe('when a preserved query param is created internally', () => { - beforeEach(() => { - // @ts-ignore - window.location = new URL('https://www.example.com/virtual'); - }); - - it('preserves the param for internal navigation', async () => { - render(); - const createButton = screen.getByRole('button', { name: /Create/i }); - expect(createButton).toBeInTheDocument(); - await userEvent.click(createButton); - const preserveButton = screen.getByText(/Preserve/i); - expect(preserveButton).toBeInTheDocument(); - await userEvent.click(preserveButton); - expect(screen.queryByText('preserved=1')).toBeInTheDocument(); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/router/index.tsx b/packages/clerk-js/src/ui.retheme/router/index.tsx deleted file mode 100644 index aab3a4c0b55..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export * from './RouteContext'; -export * from './HashRouter'; -export * from './PathRouter'; -export * from './VirtualRouter'; -export * from './Route'; -export * from './Switch'; - -export type { ParsedQs } from 'qs'; diff --git a/packages/clerk-js/src/ui.retheme/router/newPaths.ts b/packages/clerk-js/src/ui.retheme/router/newPaths.ts deleted file mode 100644 index 9419af11987..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/newPaths.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const newPaths = (oldIndexPath: string, oldFullPath: string, path?: string, index?: boolean) => { - let indexPath = oldIndexPath; - if (path) { - indexPath = oldFullPath; - if (!index) { - indexPath += '/' + path; - } - } - if (indexPath.startsWith('//')) { - indexPath = indexPath.substr(1); - } - - let fullPath = oldFullPath + (path ? '/' + path : ''); - if (fullPath.startsWith('//')) { - fullPath = fullPath.substr(1); - } - return [indexPath, fullPath]; -}; diff --git a/packages/clerk-js/src/ui.retheme/router/pathToRegexp.ts b/packages/clerk-js/src/ui.retheme/router/pathToRegexp.ts deleted file mode 100644 index 1e69a23e3a9..00000000000 --- a/packages/clerk-js/src/ui.retheme/router/pathToRegexp.ts +++ /dev/null @@ -1,611 +0,0 @@ -/** - * Tokenizer results. - */ -interface LexToken { - type: 'OPEN' | 'CLOSE' | 'PATTERN' | 'NAME' | 'CHAR' | 'ESCAPED_CHAR' | 'MODIFIER' | 'END'; - index: number; - value: string; -} - -/** - * Tokenize input string. - */ -function lexer(str: string): LexToken[] { - const tokens: LexToken[] = []; - let i = 0; - - while (i < str.length) { - const char = str[i]; - - if (char === '*' || char === '+' || char === '?') { - tokens.push({ type: 'MODIFIER', index: i, value: str[i++] }); - continue; - } - - if (char === '\\') { - tokens.push({ type: 'ESCAPED_CHAR', index: i++, value: str[i++] }); - continue; - } - - if (char === '{') { - tokens.push({ type: 'OPEN', index: i, value: str[i++] }); - continue; - } - - if (char === '}') { - tokens.push({ type: 'CLOSE', index: i, value: str[i++] }); - continue; - } - - if (char === ':') { - let name = ''; - let j = i + 1; - - while (j < str.length) { - const code = str.charCodeAt(j); - - if ( - // `0-9` - (code >= 48 && code <= 57) || - // `A-Z` - (code >= 65 && code <= 90) || - // `a-z` - (code >= 97 && code <= 122) || - // `_` - code === 95 - ) { - name += str[j++]; - continue; - } - - break; - } - - if (!name) { - throw new TypeError(`Missing parameter name at ${i}`); - } - - tokens.push({ type: 'NAME', index: i, value: name }); - i = j; - continue; - } - - if (char === '(') { - let count = 1; - let pattern = ''; - let j = i + 1; - - if (str[j] === '?') { - throw new TypeError(`Pattern cannot start with "?" at ${j}`); - } - - while (j < str.length) { - if (str[j] === '\\') { - pattern += str[j++] + str[j++]; - continue; - } - - if (str[j] === ')') { - count--; - if (count === 0) { - j++; - break; - } - } else if (str[j] === '(') { - count++; - if (str[j + 1] !== '?') { - throw new TypeError(`Capturing groups are not allowed at ${j}`); - } - } - - pattern += str[j++]; - } - - if (count) { - throw new TypeError(`Unbalanced pattern at ${i}`); - } - if (!pattern) { - throw new TypeError(`Missing pattern at ${i}`); - } - - tokens.push({ type: 'PATTERN', index: i, value: pattern }); - i = j; - continue; - } - - tokens.push({ type: 'CHAR', index: i, value: str[i++] }); - } - - tokens.push({ type: 'END', index: i, value: '' }); - - return tokens; -} - -export interface ParseOptions { - /** - * Set the default delimiter for repeat parameters. (default: `'/'`) - */ - delimiter?: string; - /** - * List of characters to automatically consider prefixes when parsing. - */ - prefixes?: string; -} - -/** - * Parse a string for the raw tokens. - */ -export function parse(str: string, options: ParseOptions = {}): Token[] { - const tokens = lexer(str); - const { prefixes = './' } = options; - const defaultPattern = `[^${escapeString(options.delimiter || '/#?')}]+?`; - const result: Token[] = []; - let key = 0; - let i = 0; - let path = ''; - - const tryConsume = (type: LexToken['type']): string | undefined => { - if (i < tokens.length && tokens[i].type === type) { - return tokens[i++].value; - } - return undefined; - }; - - const mustConsume = (type: LexToken['type']): string => { - const value = tryConsume(type); - if (value !== undefined) { - return value; - } - const { type: nextType, index } = tokens[i]; - throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`); - }; - - const consumeText = (): string => { - let result = ''; - let value: string | undefined; - // tslint:disable-next-line - while ((value = tryConsume('CHAR') || tryConsume('ESCAPED_CHAR'))) { - result += value; - } - return result; - }; - - while (i < tokens.length) { - const char = tryConsume('CHAR'); - const name = tryConsume('NAME'); - const pattern = tryConsume('PATTERN'); - - if (name || pattern) { - let prefix = char || ''; - - if (prefixes.indexOf(prefix) === -1) { - path += prefix; - prefix = ''; - } - - if (path) { - result.push(path); - path = ''; - } - - result.push({ - name: name || key++, - prefix, - suffix: '', - pattern: pattern || defaultPattern, - modifier: tryConsume('MODIFIER') || '', - }); - continue; - } - - const value = char || tryConsume('ESCAPED_CHAR'); - if (value) { - path += value; - continue; - } - - if (path) { - result.push(path); - path = ''; - } - - const open = tryConsume('OPEN'); - if (open) { - const prefix = consumeText(); - const name = tryConsume('NAME') || ''; - const pattern = tryConsume('PATTERN') || ''; - const suffix = consumeText(); - - mustConsume('CLOSE'); - - result.push({ - name: name || (pattern ? key++ : ''), - pattern: name && !pattern ? defaultPattern : pattern, - prefix, - suffix, - modifier: tryConsume('MODIFIER') || '', - }); - continue; - } - - mustConsume('END'); - } - - return result; -} - -export interface TokensToFunctionOptions { - /** - * When `true` the regexp will be case sensitive. (default: `false`) - */ - sensitive?: boolean; - /** - * Function for encoding input strings for output. - */ - encode?: (value: string, token: Key) => string; - /** - * When `false` the function can produce an invalid (unmatched) path. (default: `true`) - */ - validate?: boolean; -} - -/** - * Compile a string to a template function for the path. - */ -export function compile

(str: string, options?: ParseOptions & TokensToFunctionOptions) { - return tokensToFunction

(parse(str, options), options); -} - -export type PathFunction

= (data?: P) => string; - -/** - * Expose a method for transforming tokens into the path function. - */ -export function tokensToFunction

( - tokens: Token[], - options: TokensToFunctionOptions = {}, -): PathFunction

{ - const reFlags = flags(options); - const { encode = (x: string) => x, validate = true } = options; - - // Compile all the tokens into regexps. - const matches = tokens.map(token => { - if (typeof token === 'object') { - return new RegExp(`^(?:${token.pattern})$`, reFlags); - } - return undefined; - }); - - return (data: Record | null | undefined) => { - let path = ''; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - - if (typeof token === 'string') { - path += token; - continue; - } - - const value = data ? data[token.name] : undefined; - const optional = token.modifier === '?' || token.modifier === '*'; - const repeat = token.modifier === '*' || token.modifier === '+'; - - if (Array.isArray(value)) { - if (!repeat) { - throw new TypeError(`Expected "${token.name}" to not repeat, but got an array`); - } - - if (value.length === 0) { - if (optional) { - continue; - } - - throw new TypeError(`Expected "${token.name}" to not be empty`); - } - - for (let j = 0; j < value.length; j++) { - const segment = encode(value[j], token); - - if (validate && !(matches[i] as RegExp).test(segment)) { - throw new TypeError(`Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`); - } - - path += token.prefix + segment + token.suffix; - } - - continue; - } - - if (typeof value === 'string' || typeof value === 'number') { - const segment = encode(String(value), token); - - if (validate && !(matches[i] as RegExp).test(segment)) { - throw new TypeError(`Expected "${token.name}" to match "${token.pattern}", but got "${segment}"`); - } - - path += token.prefix + segment + token.suffix; - continue; - } - - if (optional) { - continue; - } - - const typeOfMessage = repeat ? 'an array' : 'a string'; - throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`); - } - - return path; - }; -} - -export interface RegexpToFunctionOptions { - /** - * Function for decoding strings for params. - */ - decode?: (value: string, token: Key) => string; -} - -/** - * A match result contains data about the path match. - */ -export interface MatchResult

{ - path: string; - index: number; - params: P; -} - -/** - * A match is either `false` (no match) or a match result. - */ -export type Match

= false | MatchResult

; - -/** - * The match function takes a string and returns whether it matched the path. - */ -export type MatchFunction

= (path: string) => Match

; - -/** - * Create path match function from `path-to-regexp` spec. - */ -export function match

( - str: Path, - options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions, -) { - const keys: Key[] = []; - const re = pathToRegexp(str, keys, options); - return regexpToFunction

(re, keys, options); -} - -/** - * Create a path match function from `path-to-regexp` output. - */ -export function regexpToFunction

( - re: RegExp, - keys: Key[], - options: RegexpToFunctionOptions = {}, -): MatchFunction

{ - const { decode = (x: string) => x } = options; - - return function (pathname: string) { - const m = re.exec(pathname); - if (!m) { - return false; - } - - const { 0: path, index } = m; - const params = Object.create(null); - - for (let i = 1; i < m.length; i++) { - // tslint:disable-next-line - if (m[i] === undefined) { - continue; - } - - const key = keys[i - 1]; - - if (key.modifier === '*' || key.modifier === '+') { - params[key.name] = m[i].split(key.prefix + key.suffix).map(value => { - return decode(value, key); - }); - } else { - params[key.name] = decode(m[i], key); - } - } - - return { path, index, params }; - }; -} - -/** - * Escape a regular expression string. - */ -function escapeString(str: string) { - return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1'); -} - -/** - * Get the flags for a regexp from the options. - */ -function flags(options?: { sensitive?: boolean }) { - return options && options.sensitive ? '' : 'i'; -} - -/** - * Metadata about a key. - */ -export interface Key { - name: string | number; - prefix: string; - suffix: string; - pattern: string; - modifier: string; -} - -/** - * A token is a string (nothing special) or key metadata (capture group). - */ -export type Token = string | Key; - -/** - * Pull out keys from a regexp. - */ -function regexpToRegexp(path: RegExp, keys?: Key[]): RegExp { - if (!keys) { - return path; - } - - // Use a negative lookahead to match only capturing groups. - const groups = path.source.match(/\((?!\?)/g); - - if (groups) { - for (let i = 0; i < groups.length; i++) { - keys.push({ - name: i, - prefix: '', - suffix: '', - modifier: '', - pattern: '', - }); - } - } - - return path; -} - -/** - * Transform an array into a regexp. - */ -function arrayToRegexp( - paths: Array, - keys?: Key[], - options?: TokensToRegexpOptions & ParseOptions, -): RegExp { - const parts = paths.map(path => pathToRegexp(path, keys, options).source); - return new RegExp(`(?:${parts.join('|')})`, flags(options)); -} - -/** - * Create a path regexp from string input. - */ -function stringToRegexp(path: string, keys?: Key[], options?: TokensToRegexpOptions & ParseOptions) { - return tokensToRegexp(parse(path, options), keys, options); -} - -export interface TokensToRegexpOptions { - /** - * When `true` the regexp will be case sensitive. (default: `false`) - */ - sensitive?: boolean; - /** - * When `true` the regexp allows an optional trailing delimiter to match. (default: `false`) - */ - strict?: boolean; - /** - * When `true` the regexp will match to the end of the string. (default: `true`) - */ - end?: boolean; - /** - * When `true` the regexp will match from the beginning of the string. (default: `true`) - */ - start?: boolean; - /** - * Sets the final character for non-ending optimistic matches. (default: `/`) - */ - delimiter?: string; - /** - * List of characters that can also be "end" characters. - */ - endsWith?: string; - /** - * Encode path tokens for use in the `RegExp`. - */ - encode?: (value: string) => string; -} - -/** - * Expose a function for taking tokens and returning a RegExp. - */ -export function tokensToRegexp(tokens: Token[], keys?: Key[], options: TokensToRegexpOptions = {}) { - const { strict = false, start = true, end = true, encode = (x: string) => x } = options; - const endsWith = `[${escapeString(options.endsWith || '')}]|$`; - const delimiter = `[${escapeString(options.delimiter || '/#?')}]`; - let route = start ? '^' : ''; - - // Iterate over the tokens and create our regexp string. - for (const token of tokens) { - if (typeof token === 'string') { - route += escapeString(encode(token)); - } else { - const prefix = escapeString(encode(token.prefix)); - const suffix = escapeString(encode(token.suffix)); - - if (token.pattern) { - if (keys) { - keys.push(token); - } - - if (prefix || suffix) { - if (token.modifier === '+' || token.modifier === '*') { - const mod = token.modifier === '*' ? '?' : ''; - route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`; - } else { - route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`; - } - } else { - route += `(${token.pattern})${token.modifier}`; - } - } else { - route += `(?:${prefix}${suffix})${token.modifier}`; - } - } - } - - if (end) { - if (!strict) { - route += `${delimiter}?`; - } - - route += !options.endsWith ? '$' : `(?=${endsWith})`; - } else { - const endToken = tokens[tokens.length - 1]; - const isEndDelimited = - typeof endToken === 'string' - ? delimiter.indexOf(endToken[endToken.length - 1]) > -1 - : // tslint:disable-next-line - endToken === undefined; - - if (!strict) { - route += `(?:${delimiter}(?=${endsWith}))?`; - } - - if (!isEndDelimited) { - route += `(?=${delimiter}|${endsWith})`; - } - } - - return new RegExp(route, flags(options)); -} - -/** - * Supported `path-to-regexp` input types. - */ -export type Path = string | RegExp | Array; - -/** - * Normalize the given path string, returning a regular expression. - * - * An empty array can be passed in for the keys, which will hold the - * placeholder key descriptions. For example, using `/user/:id`, `keys` will - * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. - */ -export function pathToRegexp(path: Path, keys?: Key[], options?: TokensToRegexpOptions & ParseOptions) { - if (path instanceof RegExp) { - return regexpToRegexp(path, keys); - } - if (Array.isArray(path)) { - return arrayToRegexp(path, keys, options); - } - return stringToRegexp(path, keys, options); -} diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/InternalThemeProvider.tsx b/packages/clerk-js/src/ui.retheme/styledSystem/InternalThemeProvider.tsx deleted file mode 100644 index 240869dc968..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/InternalThemeProvider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import createCache from '@emotion/cache'; -// eslint-disable-next-line no-restricted-imports -import { CacheProvider, ThemeProvider } from '@emotion/react'; -import React from 'react'; - -import { useAppearance } from '../customizables'; -import type { InternalTheme } from './index'; - -const el = document.querySelector('style#cl-style-insertion-point'); - -const cache = createCache({ - key: 'cl-internal', - prepend: !el, - insertionPoint: el ? (el as HTMLElement) : undefined, -}); - -type InternalThemeProviderProps = React.PropsWithChildren<{ - theme?: InternalTheme; -}>; - -export const InternalThemeProvider = (props: InternalThemeProviderProps) => { - const { parsedInternalTheme } = useAppearance(); - - return ( - - {props.children} - - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/__tests__/createVariants.test.ts b/packages/clerk-js/src/ui.retheme/styledSystem/__tests__/createVariants.test.ts deleted file mode 100644 index 35858f4ed05..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/__tests__/createVariants.test.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { createCssVariables } from '../createCssVariables'; -import { createVariants } from '../createVariants'; - -const baseTheme = { - fontSizes: { - sm: 'sm', - md: 'md', - }, - colors: { - primary500: 'primary500', - success500: 'success500', - }, - radii: { - full: 'full', - none: 'none', - }, -}; - -describe('createVariants', () => { - it('applies base styles', () => { - const { applyVariants } = createVariants(theme => ({ - base: { - backgroundColor: theme.colors.primary500, - }, - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm, backgroundColor: baseTheme.colors.primary500 }); - }); - - it('merges nested pseudo-selector objecst', () => { - const { applyVariants } = createVariants(theme => ({ - base: { - backgroundColor: theme.colors.primary500, - '&:active': { - backgroundColor: theme.colors.success500, - }, - }, - variants: { - size: { - small: { - fontSize: theme.fontSizes.sm, - '&:active': { - transform: 'scale(0.98)', - }, - }, - medium: { fontSize: theme.fontSizes.md }, - }, - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ - fontSize: baseTheme.fontSizes.sm, - backgroundColor: baseTheme.colors.primary500, - '&:active': { - backgroundColor: baseTheme.colors.success500, - transform: 'scale(0.98)', - }, - }); - }); - - it('supports termplate literals with references to the theme prop', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: `${theme.fontSizes.sm}` }, - }, - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm }); - }); - - it('supports template literals with references to the theme prop', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: `${theme.fontSizes.sm}` }, - }, - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm }); - }); - - it('respects variant specificity - the variant that comes last wins', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - type: { - subtitle: { color: theme.colors.success500, fontSize: theme.fontSizes.sm }, - }, - size: { - small: { fontSize: `${theme.fontSizes.sm}` }, - md: { fontSize: `${theme.fontSizes.md}` }, - }, - }, - })); - - const res = applyVariants({ type: 'subtitle', size: 'md' })(baseTheme); - expect(res).toEqual({ color: baseTheme.colors.success500, fontSize: baseTheme.fontSizes.md }); - }); - - it('applies variants based on props', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - color: { - blue: { backgroundColor: theme.colors.primary500 }, - green: { backgroundColor: theme.colors.success500 }, - }, - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm }); - }); - - it('applies boolean-based variants based on props', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - rounded: { - true: { - borderRadius: theme.radii.full, - }, - }, - }, - })); - - // @ts-ignore - const res = applyVariants({ size: 'small', rounded: true })(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm, borderRadius: 'full' }); - }); - - it('applies boolean-based variants based on default variants', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - rounded: { - true: { - borderRadius: theme.radii.full, - }, - }, - }, - defaultVariants: { - // @ts-expect-error - rounded: true, - size: 'small', - }, - })); - - const res = applyVariants()(baseTheme); - expect(res).toEqual({ fontSize: baseTheme.fontSizes.sm, borderRadius: 'full' }); - }); - - it('applies falsy boolean-based variants', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - rounded: { - false: { - borderRadius: theme.radii.none, - }, - }, - }, - defaultVariants: { - // @ts-expect-error - rounded: false, - }, - })); - - const res = applyVariants()(baseTheme); - expect(res).toEqual({ borderRadius: baseTheme.radii.none }); - }); - - it('applies variants based on props and default variants if found', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - color: { - blue: { backgroundColor: theme.colors.primary500 }, - green: { backgroundColor: theme.colors.success500 }, - }, - }, - defaultVariants: { - color: 'blue', - }, - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ - fontSize: baseTheme.fontSizes.sm, - backgroundColor: 'primary500', - }); - }); - - it('applies rules from compound variants', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - color: { - blue: { backgroundColor: theme.colors.primary500 }, - green: { backgroundColor: theme.colors.success500 }, - }, - }, - defaultVariants: { - color: 'blue', - }, - compoundVariants: [ - { condition: { size: 'small', color: 'blue' }, styles: { borderRadius: theme.radii.full } }, - { condition: { size: 'small', color: 'green' }, styles: { backgroundColor: 'notpossible' } }, - ], - })); - - const res = applyVariants({ size: 'small' })(baseTheme); - expect(res).toEqual({ - fontSize: baseTheme.fontSizes.sm, - backgroundColor: 'primary500', - borderRadius: 'full', - }); - }); - - it('correctly overrides styles though compound variants rules', () => { - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - color: { - blue: { backgroundColor: theme.colors.primary500 }, - green: { backgroundColor: theme.colors.success500 }, - }, - }, - defaultVariants: { - size: 'small', - color: 'blue', - }, - compoundVariants: [ - { condition: { size: 'small', color: 'blue' }, styles: { borderRadius: theme.radii.full } }, - { condition: { size: 'medium', color: 'green' }, styles: { backgroundColor: 'gainsboro' } }, - ], - })); - - const res = applyVariants({ size: 'medium', color: 'green' })(baseTheme); - expect(res).toEqual({ - fontSize: baseTheme.fontSizes.md, - backgroundColor: 'gainsboro', - }); - }); - - it('sanitizes vss variable keys before use', () => { - const { color } = createCssVariables('color'); - const { applyVariants } = createVariants(theme => ({ - variants: { - size: { - small: { [color]: theme.colors.primary500, fontSize: baseTheme.fontSizes.sm }, - }, - color: { - blue: { backgroundColor: color }, - }, - }, - })); - - const res = applyVariants({ size: 'small', color: 'blue' })(baseTheme); - expect(res).toEqual({ - fontSize: baseTheme.fontSizes.sm, - [color.replace('var(', '').replace(')', '')]: baseTheme.colors.primary500, - backgroundColor: color, - }); - }); - - it('gives access to props inside the config function', () => { - type Props = { size: any; color: any; isLoading: boolean }; - const { applyVariants } = createVariants((theme, props) => ({ - base: { - color: props.isLoading ? theme.colors.success500 : theme.colors.primary500, - }, - variants: {}, - })); - - const res = applyVariants({ isLoading: true } as any)(baseTheme); - expect(res).toEqual({ color: baseTheme.colors.success500 }); - }); - - it('removes variant keys from passed props', () => { - const { filterProps } = createVariants(theme => ({ - variants: { - size: { - small: { fontSize: theme.fontSizes.sm }, - medium: { fontSize: theme.fontSizes.md }, - }, - color: { - blue: { backgroundColor: theme.colors.primary500 }, - green: { backgroundColor: theme.colors.success500 }, - }, - }, - })); - - const res = filterProps({ size: 'small', color: 'blue', normalProp1: '1', normalProp2: '2' }); - expect(res).toEqual({ normalProp1: '1', normalProp2: '2' }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/animations.ts b/packages/clerk-js/src/ui.retheme/styledSystem/animations.ts deleted file mode 100644 index 5a20654fd4c..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/animations.ts +++ /dev/null @@ -1,141 +0,0 @@ -// TODO: This is forbidden by ESLint -// eslint-disable-next-line no-restricted-imports -import { keyframes } from '@emotion/react'; - -const spinning = keyframes` - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); }`; - -const dropdownSlideInScaleAndFade = keyframes` - 0% { - opacity: 0; - transform: scaleY(1) translateY(-6px); - } - - 100% { - opacity: 1; - transform: scaleY(1) translateY(0px); - } -`; - -const modalSlideAndFade = keyframes` - 0% { - opacity: 0; - transform: translateY(0.5rem); - } - 100% { - opacity: 1; - transform: translateY(0); - } -`; - -const fadeIn = keyframes` - 0% { opacity: 0; } - 100% { opacity: 1; } -`; - -const inAnimation = keyframes` - 0% { - opacity: 0; - transform: translateY(-5px); - max-height: 0; - } - 100% { - opacity: 1; - transform: translateY(0px); - max-height: 6rem; - } -`; - -const inDelayAnimation = keyframes` - 0% { - opacity: 0; - transform: translateY(-5px); - max-height: 0; - } - 50% { - opacity: 0; - transform: translateY(-5px); - max-height: 0; - } - 100% { - opacity: 1; - transform: translateY(0px); - max-height: 6rem; - } -`; - -const notificationAnimation = keyframes` - 0% { - opacity: 0; - transform: translateY(5px) scale(.5); - } - - 50% { - opacity: 1; - transform: translateY(0px) scale(1.2); - } - - 100% { - opacity: 1; - transform: translateY(0px) scale(1); - } -`; - -const outAnimation = keyframes` - 0% { - opacity:1; - translateY(0px); - max-height: 6rem; - visibility: visible; - } - 100% { - opacity: 0; - transform: translateY(5px); - max-height: 0; - visibility: visible; - } -`; - -const textInSmall = keyframes` - 0% {opacity: 0;max-height: 0;} - 100% {opacity: 1;max-height: 3rem;} -`; - -const textInBig = keyframes` - 0% {opacity: 0;max-height: 0;} - 100% {opacity: 1;max-height: 8rem;} -`; - -const blockBigIn = keyframes` - 0% {opacity: 0;max-height: 0;} - 99% {opacity: 1;max-height: 10rem;} - 100% {opacity: 1;max-height: unset;} -`; - -const expandIn = (max: string) => keyframes` - 0% {opacity: 0;max-height: 0;} - 99% {opacity: 1;max-height: ${max};} - 100% {opacity: 1;max-height: unset;} -`; - -const navbarSlideIn = keyframes` - 0% {opacity: 0; transform: translateX(-100%);} - 100% {opacity: 1; transform: translateX(0);} -`; - -export const animations = { - spinning, - dropdownSlideInScaleAndFade, - modalSlideAndFade, - fadeIn, - textInSmall, - textInBig, - blockBigIn, - expandIn, - navbarSlideIn, - inAnimation, - inDelayAnimation, - outAnimation, - notificationAnimation, -}; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/breakpoints.tsx b/packages/clerk-js/src/ui.retheme/styledSystem/breakpoints.tsx deleted file mode 100644 index 23b536d202c..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/breakpoints.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { fromEntries } from '../utils/fromEntries'; - -const breakpoints = Object.freeze({ - xs: '21em', // 336px - sm: '30em', // 480px - md: '48em', // 768px - lg: '62em', // 992px - xl: '80em', // 1280px - '2xl': '96em', // 1536px -} as const); - -const deviceQueries = { - ios: '@supports (-webkit-touch-callout: none)', -} as const; - -// export const mq = Object.fromEntries( -// Object.entries(breakpoints).map(([k, v]) => [k, `@media (min-width: ${v})`]), -// ) as Record; - -export const mqu = { - ...deviceQueries, - ...fromEntries(Object.entries(breakpoints).map(([k, v]) => [k, `@media (max-width: ${v})`])), -} as Record; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/common.ts b/packages/clerk-js/src/ui.retheme/styledSystem/common.ts deleted file mode 100644 index 072c84cff55..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/common.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type { InternalTheme } from './types'; - -const textVariants = (t: InternalTheme) => { - const base = { - WebkitFontSmoothing: t.options.$fontSmoothing, - fontFamily: 'inherit', - letterSpacing: t.letterSpacings.$normal, - }; - - const h1 = { - ...base, - fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$xl, - lineHeight: t.lineHeights.$large, - } as const; - - const h2 = { - ...base, - fontWeight: t.fontWeights.$bold, - fontSize: t.fontSizes.$lg, - lineHeight: t.lineHeights.$medium, - } as const; - - const h3 = { - ...base, - fontWeight: t.fontWeights.$bold, - fontSize: t.fontSizes.$md, - lineHeight: t.lineHeights.$small, - } as const; - - const subtitle = { - ...base, - fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$md, - lineHeight: t.lineHeights.$small, - } as const; - - const body = { - ...base, - fontWeight: t.fontWeights.$normal, - fontSize: t.fontSizes.$md, - lineHeight: t.lineHeights.$small, - } as const; - - const caption = { - ...base, - fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$xs, - lineHeight: t.lineHeights.$none, - } as const; - - const buttonLarge = { - ...base, - fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$md, - lineHeight: t.lineHeights.$small, - fontFamily: t.fonts.$buttons, - } as const; - - const buttonSmall = { - ...base, - fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$sm, - lineHeight: t.lineHeights.$none, - fontFamily: t.fonts.$buttons, - } as const; - - return { - h1, - h2, - h3, - subtitle, - body, - caption, - buttonLarge, - buttonSmall, - } as const; -}; - -const borderVariants = (t: InternalTheme, props?: any) => { - const defaultBoxShadow = t.shadows.$input - .replace('{{color1}}', t.colors.$blackAlpha200) - .replace('{{color2}}', t.colors.$blackAlpha300); - const hoverBoxShadow = t.shadows.$inputHover - .replace('{{color1}}', t.colors.$blackAlpha300) - .replace('{{color2}}', t.colors.$blackAlpha400); - const hoverStyles = { - '&:hover': { - WebkitTapHighlightColor: 'transparent', - boxShadow: [defaultBoxShadow, hoverBoxShadow].toString(), - }, - }; - const focusStyles = - props?.focusRing === false - ? {} - : { - '&:focus': { - WebkitTapHighlightColor: 'transparent', - boxShadow: [ - defaultBoxShadow, - hoverBoxShadow, - t.shadows.$focusRing.replace('{{color}}', props?.hasError ? t.colors.$danger300 : t.colors.$primary300), - ].toString(), - }, - }; - return { - normal: { - borderRadius: t.radii.$lg, - border: 'none', - boxShadow: defaultBoxShadow, - transitionProperty: t.transitionProperty.$common, - transitionTimingFunction: t.transitionTiming.$common, - transitionDuration: t.transitionDuration.$focusRing, - ...hoverStyles, - ...focusStyles, - }, - } as const; -}; - -const borderColor = (t: InternalTheme, props?: any) => { - return { - borderColor: props?.hasError ? t.colors.$danger500 : t.colors.$blackAlpha300, - } as const; -}; - -const focusRing = (t: InternalTheme) => { - return { - '&:focus': { - '&::-moz-focus-inner': { border: '0' }, - WebkitTapHighlightColor: 'transparent', - boxShadow: t.shadows.$focusRing.replace('{{color}}', t.colors.$primary200), - transitionProperty: t.transitionProperty.$common, - transitionTimingFunction: t.transitionTiming.$common, - transitionDuration: t.transitionDuration.$focusRing, - }, - } as const; -}; - -const focusRingInput = (t: InternalTheme, props?: any) => { - return { - '&:focus': { - WebkitTapHighlightColor: 'transparent', - boxShadow: t.shadows.$focusRing.replace( - '{{color}}', - props?.hasError ? t.colors.$danger400 : t.colors.$primary400, - ), - transitionProperty: t.transitionProperty.$common, - transitionTimingFunction: t.transitionTiming.$common, - transitionDuration: t.transitionDuration.$focusRing, - }, - } as const; -}; - -const buttonShadow = (t: InternalTheme) => { - return { boxShadow: t.shadows.$buttonShadow.replace('{{color}}', t.colors.$primary800) }; -}; - -const disabled = (t: InternalTheme) => { - return { - '&:disabled,&[data-disabled]': { - cursor: 'not-allowed', - pointerEvents: 'none', - opacity: t.opacity.$disabled, - }, - } as const; -}; - -const centeredFlex = (display: 'flex' | 'inline-flex' = 'flex') => ({ - display: display, - justifyContent: 'center', - alignItems: 'center', -}); - -const unstyledScrollbar = (t: InternalTheme) => ({ - '::-webkit-scrollbar': { - background: 'transparent', - width: '8px', - height: '8px', - }, - '::-webkit-scrollbar-thumb': { - background: t.colors.$blackAlpha500, - }, - '::-webkit-scrollbar-track': { - background: 'transparent', - }, -}); - -const maxHeightScroller = (t: InternalTheme) => - ({ - height: '100%', - overflowY: 'auto', - ...unstyledScrollbar(t), - } as const); - -export const common = { - textVariants, - borderVariants, - focusRing, - focusRingInput, - buttonShadow, - disabled, - borderColor, - centeredFlex, - maxHeightScroller, - unstyledScrollbar, -}; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/createCssVariables.ts b/packages/clerk-js/src/ui.retheme/styledSystem/createCssVariables.ts deleted file mode 100644 index aea114a1ec1..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/createCssVariables.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { fromEntries } from '../utils/fromEntries'; - -export const createCssVariables = (...names: T): { [k in T[number]]: string } => { - return fromEntries(names.map(name => [name, `var(--${name})`])); -}; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/createVariants.ts b/packages/clerk-js/src/ui.retheme/styledSystem/createVariants.ts deleted file mode 100644 index 9c6f00d7afd..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/createVariants.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { createInfiniteAccessProxy, fastDeepMergeAndReplace } from '../utils'; -import type { InternalTheme, StyleRule } from './types'; - -type UnwrapBooleanVariant = T extends 'true' | 'false' ? boolean : T; - -type VariantDefinition = Record; - -type Variants = Record; - -type VariantNameToKeyMap = { - [key in keyof V]?: UnwrapBooleanVariant; -}; - -type DefaultVariants = VariantNameToKeyMap; - -type CompoundVariant = { - condition: VariantNameToKeyMap; - styles?: StyleRule; -}; - -type CreateVariantsConfig = { - base?: StyleRule; - variants: V; - compoundVariants?: Array>; - defaultVariants?: DefaultVariants; -}; - -type ApplyVariants = { - (props?: VariantNameToKeyMap): (theme: T) => StyleRule; -}; - -export type StyleVariants any> = Parameters[0]; - -type CreateVariantsReturn = { - applyVariants: ApplyVariants; - filterProps: (props: Props) => { - [k in Exclude]: Props[k]; - }; -}; - -interface CreateVariants { -

, T = InternalTheme, V extends Variants = Variants>( - param: (theme: T, props: P) => CreateVariantsConfig, - ): CreateVariantsReturn; -} - -export const createVariants: CreateVariants = configFn => { - const applyVariants = - (props: any = {}) => - (theme: any) => { - const { base, variants = {}, compoundVariants = [], defaultVariants = {} } = configFn(theme, props); - const variantsToApply = calculateVariantsToBeApplied(variants, props, defaultVariants); - const computedStyles = {}; - applyBaseRules(computedStyles, base); - applyVariantRules(computedStyles, variantsToApply, variants); - applyCompoundVariantRules(computedStyles, variantsToApply, compoundVariants); - sanitizeCssVariables(computedStyles); - return computedStyles; - }; - - // We need to get the variant keys in order to remove them from the props. - // However, we don't have access to the theme value when createVariants is called. - // Instead of the theme, we pass an infinite proxy because we only care about - // the keys of the returned object and not the actual values. - const fakeProxyTheme = createInfiniteAccessProxy(); - const variantKeys = Object.keys(configFn(fakeProxyTheme, fakeProxyTheme).variants || {}); - const filterProps = (props: any) => getPropsWithoutVariants(props, variantKeys); - return { applyVariants, filterProps } as any; -}; - -const getPropsWithoutVariants = (props: Record, variants: string[]) => { - const res = { ...props }; - for (const key of variants) { - delete res[key]; - } - return res; -}; - -const applyBaseRules = (computedStyles: any, base?: StyleRule) => { - if (base && typeof base === 'object') { - Object.assign(computedStyles, base); - } -}; - -const applyVariantRules = (computedStyles: any, variantsToApply: Variants, variants: Variants) => { - for (const key in variantsToApply) { - // @ts-ignore - fastDeepMergeAndReplace(variants[key][variantsToApply[key]], computedStyles); - } -}; - -const applyCompoundVariantRules = ( - computedStyles: any, - variantsToApply: Variants, - compoundVariants: Array>, -) => { - for (const compoundVariant of compoundVariants) { - if (conditionMatches(compoundVariant, variantsToApply)) { - fastDeepMergeAndReplace(compoundVariant.styles, computedStyles); - } - } -}; - -const sanitizeCssVariables = (computedStyles: any) => { - for (const key in computedStyles) { - if (key.startsWith('var(')) { - computedStyles[key.slice(4, -1)] = computedStyles[key]; - delete computedStyles[key]; - } - } -}; - -const calculateVariantsToBeApplied = ( - variants: Variants, - props: Record, - defaultVariants: DefaultVariants, -) => { - const variantsToApply = {}; - for (const key in variants) { - if (key in props) { - // @ts-ignore - variantsToApply[key] = props[key]; - } else if (key in defaultVariants) { - // @ts-ignore - variantsToApply[key] = defaultVariants[key as keyof typeof defaultVariants]; - } - } - return variantsToApply; -}; - -const conditionMatches = ({ condition }: CompoundVariant, variants: Variants) => { - for (const key in condition) { - // @ts-ignore - if (condition[key] !== variants[key]) { - return false; - } - } - return true; -}; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/index.ts b/packages/clerk-js/src/ui.retheme/styledSystem/index.ts deleted file mode 100644 index 9b37515dba9..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './types'; -export * from './InternalThemeProvider'; -export * from './createVariants'; -export * from './createCssVariables'; -export * from './common'; -export * from './animations'; -export * from './breakpoints'; diff --git a/packages/clerk-js/src/ui.retheme/styledSystem/types.ts b/packages/clerk-js/src/ui.retheme/styledSystem/types.ts deleted file mode 100644 index 082f6ab0e19..00000000000 --- a/packages/clerk-js/src/ui.retheme/styledSystem/types.ts +++ /dev/null @@ -1,63 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import type { Interpolation as _Interpolation } from '@emotion/react'; -import type React from 'react'; - -import type { InternalTheme } from '../foundations'; - -type StyleRule = Exclude<_Interpolation, string | number | boolean>; - -/** - * Primitives can override their styles using the css prop. - * For customising layout/theme, prefer using the props defined on the component. - */ -type ThemableCssProp = ((params: InternalTheme) => StyleRule) | StyleRule; -type CssProp = { css?: ThemableCssProp }; - -export type AsProp = { as?: React.ElementType | undefined }; - -type ElementProps = { - div: React.JSX.IntrinsicElements['div']; - input: React.JSX.IntrinsicElements['input']; - button: React.JSX.IntrinsicElements['button']; - heading: React.JSX.IntrinsicElements['h1']; - p: React.JSX.IntrinsicElements['p']; - a: React.JSX.IntrinsicElements['a']; - label: React.JSX.IntrinsicElements['label']; - img: React.JSX.IntrinsicElements['img']; - form: React.JSX.IntrinsicElements['form']; - table: React.JSX.IntrinsicElements['table']; - thead: React.JSX.IntrinsicElements['thead']; - tbody: React.JSX.IntrinsicElements['tbody']; - th: React.JSX.IntrinsicElements['th']; - tr: React.JSX.IntrinsicElements['tr']; - td: React.JSX.IntrinsicElements['td']; -}; - -/** - * Some elements, like Flex can accept StateProps - * simply because they need to be targettable when their container - * component has a specific state. We then remove the props - * before rendering the element to the DOM - */ -type StateProps = Partial>; -/** - * The form control elements can also accept a isRequired prop on top of the StateProps - * We're handling it differently since this is a prop that cannot change - a required field - * will remain required throughout the lifecycle of the component - */ -type RequiredProp = Partial>; - -type PrimitiveProps = ElementProps[HtmlT] & CssProp; -type PickSiblingProps[0]> = Pick[0], T>; -type PropsOfComponent any> = Parameters[0]; - -export type { - InternalTheme, - PrimitiveProps, - PickSiblingProps, - PropsOfComponent, - StyleRule, - ThemableCssProp, - StateProps, - RequiredProp, -}; diff --git a/packages/clerk-js/src/ui.retheme/types.ts b/packages/clerk-js/src/ui.retheme/types.ts deleted file mode 100644 index f5ed046777d..00000000000 --- a/packages/clerk-js/src/ui.retheme/types.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { - CreateOrganizationProps, - OrganizationListProps, - OrganizationProfileProps, - OrganizationSwitcherProps, - SignInProps, - SignUpProps, - UserButtonProps, - UserProfileProps, -} from '@clerk/types'; - -export type { - SignInProps, - SignUpProps, - UserButtonProps, - UserProfileProps, - OrganizationSwitcherProps, - OrganizationProfileProps, - CreateOrganizationProps, - OrganizationListProps, -}; - -export type AvailableComponentProps = - | SignInProps - | SignUpProps - | UserProfileProps - | UserButtonProps - | OrganizationSwitcherProps - | OrganizationProfileProps - | CreateOrganizationProps - | OrganizationListProps; - -type ComponentMode = 'modal' | 'mounted'; - -export type SignInCtx = SignInProps & { - componentName: 'SignIn'; - mode?: ComponentMode; -}; - -export type UserProfileCtx = UserProfileProps & { - componentName: 'UserProfile'; - mode?: ComponentMode; -}; - -export type SignUpCtx = SignUpProps & { - componentName: 'SignUp'; - mode?: ComponentMode; -}; - -export type UserButtonCtx = UserButtonProps & { - componentName: 'UserButton'; - mode?: ComponentMode; -}; - -export type OrganizationProfileCtx = OrganizationProfileProps & { - componentName: 'OrganizationProfile'; - mode?: ComponentMode; -}; - -export type CreateOrganizationCtx = CreateOrganizationProps & { - componentName: 'CreateOrganization'; - mode?: ComponentMode; -}; - -export type OrganizationSwitcherCtx = OrganizationSwitcherProps & { - componentName: 'OrganizationSwitcher'; - mode?: ComponentMode; -}; - -export type OrganizationListCtx = OrganizationListProps & { - componentName: 'OrganizationList'; - mode?: ComponentMode; -}; - -export type AvailableComponentCtx = - | SignInCtx - | SignUpCtx - | UserButtonCtx - | UserProfileCtx - | OrganizationProfileCtx - | CreateOrganizationCtx - | OrganizationSwitcherCtx - | OrganizationListCtx; diff --git a/packages/clerk-js/src/ui.retheme/utils/ExternalElementMounter.tsx b/packages/clerk-js/src/ui.retheme/utils/ExternalElementMounter.tsx deleted file mode 100644 index 9ab73a7015c..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/ExternalElementMounter.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useRef } from 'react'; - -type ExternalElementMounterProps = { - mount: (el: HTMLDivElement) => void; - unmount: (el?: HTMLDivElement) => void; -}; - -export const ExternalElementMounter = ({ mount, unmount, ...rest }: ExternalElementMounterProps) => { - const nodeRef = useRef(null); - - useEffect(() => { - let elRef: HTMLDivElement | undefined; - if (nodeRef.current) { - elRef = nodeRef.current; - mount(nodeRef.current); - } - return () => { - unmount(elRef); - }; - }, [nodeRef.current]); - - return ( -

- ); -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/__tests__/colors.test.ts b/packages/clerk-js/src/ui.retheme/utils/__tests__/colors.test.ts deleted file mode 100644 index 7762f9dc9cd..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/__tests__/colors.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { HslaColor } from '@clerk/types'; - -import { colors } from '../colors'; - -describe('colors.toHslaColor(color)', function () { - const hsla = { h: 195, s: 100, l: 50, a: 1 }; - const cases: Array<[string, any]> = [ - // ['', undefined], - // ['00bfff', hsla], - ['transparent', { h: 0, s: 0, l: 0, a: 0 }], - ['#00bfff', hsla], - ['rgb(0, 191, 255)', hsla], - ['rgba(0, 191, 255, 0.3)', { ...hsla, a: 0.3 }], - ['hsl(195, 100%, 50%)', hsla], - ['hsla(195, 100%, 50%, 1)', hsla], - ]; - - it.each(cases)('colors.toHslaColor(%s) => %s', (a, expected) => { - expect(colors.toHslaColor(a)).toEqual(expected); - }); -}); - -describe('colors.toHslaColor(color)', function () { - const cases: Array<[HslaColor, any]> = [ - [colors.toHslaColor('transparent'), `hsla(0, 0%, 0%, 0)`], - [colors.toHslaColor('#00bfff'), 'hsla(195, 100%, 50%, 1)'], - [colors.toHslaColor('rgb(0, 191, 255)'), 'hsla(195, 100%, 50%, 1)'], - [colors.toHslaColor('rgba(0, 191, 255, 0.3)'), 'hsla(195, 100%, 50%, 0.3)'], - [colors.toHslaColor('hsl(195, 100%, 50%)'), 'hsla(195, 100%, 50%, 1)'], - [colors.toHslaColor('hsla(195, 100%, 50%, 1)'), 'hsla(195, 100%, 50%, 1)'], - ]; - - it.each(cases)('colors.toHslaColor(%s) => %s', (a, expected) => { - expect(colors.toHslaString(a)).toEqual(expected); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/__tests__/createCustomPages.test.ts b/packages/clerk-js/src/ui.retheme/utils/__tests__/createCustomPages.test.ts deleted file mode 100644 index 0ddff304799..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/__tests__/createCustomPages.test.ts +++ /dev/null @@ -1,616 +0,0 @@ -import type { CustomPage } from '@clerk/types'; - -import { createOrganizationProfileCustomPages, createUserProfileCustomPages } from '../createCustomPages'; - -describe('createCustomPages', () => { - describe('createUserProfileCustomPages', () => { - it('should return the default pages if no custom pages are passed', () => { - const { routes, contents } = createUserProfileCustomPages([]); - expect(routes.length).toEqual(2); - expect(routes[0].id).toEqual('account'); - expect(routes[1].id).toEqual('security'); - expect(contents.length).toEqual(0); - }); - - it('should return the custom pages after the default pages', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].id).toEqual('account'); - expect(routes[1].id).toEqual('security'); - expect(routes[2].name).toEqual('Custom1'); - expect(routes[3].name).toEqual('Custom2'); - expect(contents.length).toEqual(2); - expect(contents[0].url).toEqual('custom1'); - expect(contents[1].url).toEqual('custom2'); - }); - - it('should reorder the default pages when their label is used to target them', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'account' }, - { label: 'security' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].name).toEqual('Custom1'); - expect(routes[1].id).toEqual('account'); - expect(routes[2].id).toEqual('security'); - expect(routes[3].name).toEqual('Custom2'); - expect(contents.length).toEqual(2); - expect(contents[0].url).toEqual('custom1'); - expect(contents[1].url).toEqual('custom2'); - }); - - it('ignores invalid entries', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'account' }, - { label: 'security' }, - { label: 'Aaaaaa' }, - { label: 'account', mount: () => undefined }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].name).toEqual('Custom1'); - expect(routes[1].id).toEqual('account'); - expect(routes[2].id).toEqual('security'); - expect(routes[3].name).toEqual('Custom2'); - }); - - it('sets the path of the first page to be the root (/)', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'account' }, - { label: 'security' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('account'); - expect(routes[2].path).toEqual('account'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('sets the path of both account and security pages to root (/) if account is first', () => { - const customPages: CustomPage[] = [ - { label: 'account' }, - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'security' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('custom1'); - expect(routes[2].path).toEqual('/'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('sets the path of both account and security pages to root (/) if security is first', () => { - const customPages: CustomPage[] = [ - { label: 'security' }, - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'account' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('custom1'); - expect(routes[2].path).toEqual('/'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('throws if the first item in the navbar is an external link', () => { - const customPages: CustomPage[] = [ - { - label: 'Link1', - url: '/link1', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'account' }, - { label: 'security' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - expect(() => createUserProfileCustomPages(customPages)).toThrow(); - }); - - it('adds an external link to the navbar routes', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link1', - url: '/link1', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].id).toEqual('account'); - expect(routes[1].id).toEqual('security'); - expect(routes[2].name).toEqual('Custom1'); - expect(routes[3].name).toEqual('Link1'); - expect(contents.length).toEqual(1); - expect(contents[0].url).toEqual('custom1'); - }); - - it('sanitizes the path for external links', () => { - const customPages: CustomPage[] = [ - { - label: 'Link1', - url: 'https://www.fullurl.com', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link2', - url: '/url-with-slash', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link3', - url: 'url-without-slash', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(5); - expect(routes[2].path).toEqual('https://www.fullurl.com'); - expect(routes[3].path).toEqual('/url-with-slash'); - expect(routes[4].path).toEqual('/url-without-slash'); - }); - - it('sanitizes the path for custom pages', () => { - const customPages: CustomPage[] = [ - { - label: 'Page1', - url: '/url-with-slash', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Page2', - url: 'url-without-slash', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createUserProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[2].path).toEqual('url-with-slash'); - expect(routes[3].path).toEqual('url-without-slash'); - }); - - it('throws when a custom page has an absolute URL', () => { - const customPages: CustomPage[] = [ - { - label: 'Page1', - url: 'https://www.fullurl.com', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - expect(() => createUserProfileCustomPages(customPages)).toThrow(); - }); - }); - - describe('createOrganizationProfileCustomPages', () => { - it('should return the default pages if no custom pages are passed', () => { - const { routes, contents } = createOrganizationProfileCustomPages([]); - expect(routes.length).toEqual(2); - expect(routes[0].id).toEqual('members'); - expect(routes[1].id).toEqual('settings'); - expect(contents.length).toEqual(0); - }); - - it('should return the custom pages after the default pages', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].id).toEqual('members'); - expect(routes[1].id).toEqual('settings'); - expect(routes[2].name).toEqual('Custom1'); - expect(routes[3].name).toEqual('Custom2'); - expect(contents.length).toEqual(2); - expect(contents[0].url).toEqual('custom1'); - expect(contents[1].url).toEqual('custom2'); - }); - - it('should reorder the default pages when their label is used to target them', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'members' }, - { label: 'settings' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].name).toEqual('Custom1'); - expect(routes[1].id).toEqual('members'); - expect(routes[2].id).toEqual('settings'); - expect(routes[3].name).toEqual('Custom2'); - expect(contents.length).toEqual(2); - expect(contents[0].url).toEqual('custom1'); - expect(contents[1].url).toEqual('custom2'); - }); - - it('ignores invalid entries', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'members' }, - { label: 'settings' }, - { label: 'Aaaaaa' }, - { label: 'members', mount: () => undefined }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].name).toEqual('Custom1'); - expect(routes[1].id).toEqual('members'); - expect(routes[2].id).toEqual('settings'); - expect(routes[3].name).toEqual('Custom2'); - }); - - it('sets the path of the first page to be the root (/)', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'members' }, - { label: 'settings' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('organization-members'); - expect(routes[2].path).toEqual('organization-settings'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('sets the path of members pages to root (/) if it is first', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'settings' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('custom1'); - expect(routes[2].path).toEqual('organization-settings'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('sets the path of settings pages to root (/) if it is first', () => { - const customPages: CustomPage[] = [ - { label: 'settings' }, - { label: 'members' }, - - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].path).toEqual('/'); - expect(routes[1].path).toEqual('organization-members'); - expect(routes[3].path).toEqual('custom2'); - }); - - it('throws if the first item in the navbar is an external link', () => { - const customPages: CustomPage[] = [ - { - label: 'Link1', - url: '/link1', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { label: 'members' }, - { label: 'settings' }, - { - label: 'Custom2', - url: 'custom2', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - expect(() => createOrganizationProfileCustomPages(customPages)).toThrow(); - }); - - it('adds an external link to the navbar routes', () => { - const customPages: CustomPage[] = [ - { - label: 'Custom1', - url: 'custom1', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link1', - url: '/link1', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes, contents } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[0].id).toEqual('members'); - expect(routes[1].id).toEqual('settings'); - expect(routes[2].name).toEqual('Custom1'); - expect(routes[3].name).toEqual('Link1'); - expect(contents.length).toEqual(1); - expect(contents[0].url).toEqual('custom1'); - }); - - it('sanitizes the path for external links', () => { - const customPages: CustomPage[] = [ - { - label: 'Link1', - url: 'https://www.fullurl.com', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link2', - url: '/url-with-slash', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Link3', - url: 'url-without-slash', - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(5); - expect(routes[2].path).toEqual('https://www.fullurl.com'); - expect(routes[3].path).toEqual('/url-with-slash'); - expect(routes[4].path).toEqual('/url-without-slash'); - }); - - it('sanitizes the path for custom pages', () => { - const customPages: CustomPage[] = [ - { - label: 'Page1', - url: '/url-with-slash', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - { - label: 'Page2', - url: 'url-without-slash', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - const { routes } = createOrganizationProfileCustomPages(customPages); - expect(routes.length).toEqual(4); - expect(routes[2].path).toEqual('url-with-slash'); - expect(routes[3].path).toEqual('url-without-slash'); - }); - - it('throws when a custom page has an absolute URL', () => { - const customPages: CustomPage[] = [ - { - label: 'Page1', - url: 'https://www.fullurl.com', - mount: () => undefined, - unmount: () => undefined, - mountIcon: () => undefined, - unmountIcon: () => undefined, - }, - ]; - expect(() => createOrganizationProfileCustomPages(customPages)).toThrow(); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/__tests__/fastDeepMerge.test.ts b/packages/clerk-js/src/ui.retheme/utils/__tests__/fastDeepMerge.test.ts deleted file mode 100644 index 8c5c91e27df..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/__tests__/fastDeepMerge.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { fastDeepMergeAndKeep, fastDeepMergeAndReplace } from '../fastDeepMerge'; - -describe('fastDeepMergeReplace', () => { - it('merges simple objects', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = {}; - fastDeepMergeAndReplace(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3' }); - }); - - it('merges all keys when objects have different keys', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = { d: '4', e: '5', f: '6' }; - fastDeepMergeAndReplace(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }); - }); - - it('source overrides target when they have same keys', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = { a: '10' }; - fastDeepMergeAndReplace(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3' }); - }); - - it('source overrides target when they have same keys even for nested objects', () => { - const source = { a: '1', b: '2', c: '3', obj: { a: '1', b: '2' } }; - const target = { a: '10', obj: { a: '10', b: '20' } }; - fastDeepMergeAndReplace(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3', obj: { a: '1', b: '2' } }); - }); -}); - -describe('fastDeepMergeKeep', () => { - it('merges simple objects', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = {}; - fastDeepMergeAndKeep(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3' }); - }); - - it('merges all keys when objects have different keys', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = { d: '4', e: '5', f: '6' }; - fastDeepMergeAndKeep(source, target); - expect(target).toEqual({ a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }); - }); - - it('source overrides target when they have same keys', () => { - const source = { a: '1', b: '2', c: '3' }; - const target = { a: '10' }; - fastDeepMergeAndKeep(source, target); - expect(target).toEqual({ a: '10', b: '2', c: '3' }); - }); - - it('source overrides target when they have same keys even for nested objects', () => { - const source = { a: '1', b: '2', c: '3', obj: { a: '1', b: '2' } }; - const target = { a: '10', obj: { a: '10', b: '20' } }; - fastDeepMergeAndKeep(source, target); - expect(target).toEqual({ a: '10', b: '2', c: '3', obj: { a: '10', b: '20' } }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/__tests__/phoneUtils.test.ts b/packages/clerk-js/src/ui.retheme/utils/__tests__/phoneUtils.test.ts deleted file mode 100644 index bfa3b32fd8e..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/__tests__/phoneUtils.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { - extractDigits, - formatPhoneNumber, - getCountryFromPhoneString, - getCountryIsoFromFormattedNumber, - getFlagEmojiFromCountryIso, -} from '../phoneUtils'; - -describe('phoneUtils', () => { - describe('countryIsoToFlagEmoji(iso)', () => { - it('handles undefined', () => { - const res = getFlagEmojiFromCountryIso(undefined as any); - expect(res).toBe('🇺🇸'); - }); - - it('handles us iso', () => { - const res = getFlagEmojiFromCountryIso('us'); - expect(res).toBe('🇺🇸'); - }); - - it('handles gr iso', () => { - const res = getFlagEmojiFromCountryIso('gr'); - expect(res).toBe('🇬🇷'); - }); - }); - - describe('extractDigits(formattedPhone)', () => { - it('handles undefined', () => { - const res = extractDigits(undefined as any); - expect(res).toBe(''); - }); - - it('handles empty string', () => { - const res = extractDigits(''); - expect(res).toBe(''); - }); - - it('extracts digits from formatted phone number', () => { - const res = extractDigits('+1 (202) 123 123 1234'); - expect(res).toBe('12021231231234'); - }); - - it('extracts digits from number with only digits', () => { - const res = extractDigits('12021231231234'); - expect(res).toBe('12021231231234'); - }); - }); - - describe('formatPhoneNumber(formattedPhone,pattern)', () => { - it('handles edge cases', () => { - const res = formatPhoneNumber('', undefined); - expect(res).toBe(''); - }); - - it('does not format a number with less than 3 digits', () => { - const pattern = '(...) ... ....'; - expect(formatPhoneNumber('1', pattern)).toBe('1'); - expect(formatPhoneNumber('123', pattern)).toBe('123'); - }); - - it('formats a number according to pattern before max number of digits is reached', () => { - const pattern = '(...) ... ....'; - expect(formatPhoneNumber('1231', pattern)).toBe('(123) 1'); - expect(formatPhoneNumber('1231231', pattern)).toBe('(123) 123 1'); - }); - - it('formats a number according to pattern when max number of digits is reached', () => { - const cases = [ - { - pattern: '(...) ... ....', - result: '(123) 123 1234', - }, - { - pattern: '..........', - result: '1231231234', - }, - { - pattern: ' .-.......(.). ', - result: ' 1-2312312(3)4', - }, - ]; - cases.forEach(({ pattern, result }) => { - expect(formatPhoneNumber('1231231234', pattern)).toBe(result); - }); - }); - - // https://en.wikipedia.org/wiki/E.164 - it('respects the E.164 standard', () => { - const cases = [ - { - input: '123 45678901', - pattern: '... .......', - result: '123 45678901', - countryCode: undefined, - }, - { - input: '123 45678901', - pattern: '... .......', - result: '123 45678901', - countryCode: '49', - }, - { - input: '1234567890123', - pattern: '(...) ...-....', - result: '(123) 456-7890123', - countryCode: '1', - }, - { - input: '1234567890123456', - pattern: '(...) ...-....', - result: '(123) 456-7890123', - countryCode: '1', - }, - { - input: '123 456789012345', - pattern: '... .......', - result: '123 456789012', - countryCode: '49', - }, - ]; - cases.forEach(({ input, pattern, result, countryCode }) => { - expect(formatPhoneNumber(input, pattern, countryCode)).toBe(result); - }); - }); - - it('immediately returns if the input is too short', () => { - const cases = [ - { - input: '12', - pattern: '..-..', - result: '12', - }, - { - input: '123', - pattern: '..-..', - result: '123', - }, - { - input: '1234', - pattern: '..-..', - result: '12-34', - }, - ]; - cases.forEach(({ input, pattern, result }) => { - expect(formatPhoneNumber(input, pattern)).toBe(result); - }); - }); - - it('formats a number according to pattern', () => { - const cases = [ - { - pattern: '....', - result: '1234', - }, - { - pattern: '. ()-(..)-() .', - result: '1 ()-(23)-() 4', - }, - ]; - cases.forEach(({ pattern, result }) => { - expect(formatPhoneNumber('1234', pattern)).toBe(result); - }); - }); - }); - - describe('getCountryIsoFromFormattedNumber(formattedNumber)', () => { - it('handles edge cases', () => { - const res = getCountryIsoFromFormattedNumber(undefined as any); - expect(res).toBe('us'); - }); - - it('fallbacks to us for very short (potentially wrong) numbers', () => { - const res = getCountryIsoFromFormattedNumber('123'); - expect(res).toBe('us'); - }); - - it('handles a US phone starting with 1 with a known US subarea following', () => { - const res = getCountryIsoFromFormattedNumber('+1 (202) 123 1234'); - expect(res).toBe('us'); - }); - - it('handles a CA phone starting with 1 with a known CA subarea following', () => { - const res = getCountryIsoFromFormattedNumber('+1 613-555-0150'); - expect(res).toBe('ca'); - }); - - it('prioritizes US for a non-US phone starting with 1 that has a potential non-US code', () => { - const res = getCountryIsoFromFormattedNumber('+1242 123123'); - expect(res).toBe('us'); - }); - - it('handles a non-US phone starting with code != 1', () => { - const res = getCountryIsoFromFormattedNumber('+30 6999999999'); - expect(res).toBe('gr'); - }); - - it('handles a non-US phone without a format pattern', () => { - expect(getCountryIsoFromFormattedNumber('+71111111111')).toBe('ru'); - }); - }); - - describe('getLongestValidCountryCode(value)', () => { - it('finds valid country and returns phone number value without country code', () => { - const res = getCountryFromPhoneString('+12064563059'); - expect(res.number).toBe('2064563059'); - expect(res.country).not.toBe(undefined); - expect(res.country.code).toBe('1'); - }); - - it('finds valid country by applying priority', () => { - const res = getCountryFromPhoneString('+1242123123'); - expect(res.number).toBe('242123123'); - expect(res.country).not.toBe(undefined); - expect(res.country.code).toBe('1'); - }); - - it('falls back to US if a valid country code is not found', () => { - const res = getCountryFromPhoneString('+699090909090'); - expect(res.number).toBe('99090909090'); - expect(res.country.iso).toBe('us'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/colorPredicates.ts b/packages/clerk-js/src/ui.retheme/utils/colorPredicates.ts deleted file mode 100644 index 53348167bec..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/colorPredicates.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Color, ColorString, HslaColor, RgbaColor, TransparentColor } from '@clerk/types'; - -const IS_HEX_COLOR_REGEX = /^#?([A-F0-9]{6}|[A-F0-9]{3})$/i; - -const IS_RGB_COLOR_REGEX = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/i; -const IS_RGBA_COLOR_REGEX = /^rgba\((\d+),\s*(\d+),\s*(\d+)(,\s*\d+(\.\d+)?)\)$/i; - -const IS_HSL_COLOR_REGEX = /^hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)$/i; -const IS_HSLA_COLOR_REGEX = /^hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%(,\s*\d+(\.\d+)?)*\)$/i; - -export const isValidHexString = (s: string): s is ColorString => { - return !!s.match(IS_HEX_COLOR_REGEX); -}; - -export const isValidRgbaString = (s: string): s is ColorString => { - return !!(s.match(IS_RGB_COLOR_REGEX) || s.match(IS_RGBA_COLOR_REGEX)); -}; - -export const isValidHslaString = (s: string): s is ColorString => { - return !!s.match(IS_HSL_COLOR_REGEX) || !!s.match(IS_HSLA_COLOR_REGEX); -}; - -export const isRGBColor = (c: Color): c is RgbaColor => { - return typeof c !== 'string' && 'r' in c; -}; - -export const isHSLColor = (c: Color): c is HslaColor => { - return typeof c !== 'string' && 'h' in c; -}; - -export const isTransparent = (c: Color): c is TransparentColor => { - return c === 'transparent'; -}; - -export const hasAlpha = (color: Color): boolean => { - return typeof color !== 'string' && color.a != undefined && color.a < 1; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/colorTransformations.ts b/packages/clerk-js/src/ui.retheme/utils/colorTransformations.ts deleted file mode 100644 index 25e874a40ee..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/colorTransformations.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { Color, HslaColor, RgbaColor, TransparentColor } from '@clerk/types'; - -import { - isHSLColor, - isRGBColor, - isTransparent, - isValidHexString, - isValidHslaString, - isValidRgbaString, -} from './colorPredicates'; - -const CLEAN_HSLA_REGEX = /[hsla()]/g; -const CLEAN_RGBA_REGEX = /[rgba()]/g; - -export const stringToHslaColor = (value: string | undefined): HslaColor | undefined => { - if (!value) { - return undefined; - } - if (value === 'transparent') { - return { h: 0, s: 0, l: 0, a: 0 }; - } - if (isValidHexString(value)) { - return hexStringToHslaColor(value); - } - if (isValidHslaString(value)) { - return parseHslaString(value); - } - if (isValidRgbaString(value)) { - return rgbaStringToHslaColor(value); - } - return undefined; -}; - -export const colorToSameTypeString = (color: Color): string | TransparentColor => { - if (typeof color === 'string' && (isValidHexString(color) || isTransparent(color))) { - return color; - } - if (isRGBColor(color)) { - return rgbaColorToRgbaString(color); - } - if (isHSLColor(color)) { - return hslaColorToHslaString(color); - } - return ''; -}; - -export const hexStringToRgbaColor = (hex: string): RgbaColor => { - hex = hex.replace('#', ''); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - return { r, g, b }; -}; - -const rgbaColorToRgbaString = (color: RgbaColor): string => { - const { a, b, g, r } = color; - return color.a === 0 ? 'transparent' : color.a != undefined ? `rgba(${r},${g},${b},${a})` : `rgb(${r},${g},${b})`; -}; - -export const hslaColorToHslaString = (color: HslaColor): string => { - const { h, s, l, a } = color; - const sPerc = Math.round(s * 100); - const lPerc = Math.round(l * 100); - return color.a === 0 - ? 'transparent' - : color.a != undefined - ? `hsla(${h},${sPerc}%,${lPerc}%,${a})` - : `hsl(${h},${sPerc}%,${lPerc}%)`; -}; - -const hexStringToHslaColor = (hex: string): HslaColor => { - const rgbaString = colorToSameTypeString(hexStringToRgbaColor(hex)); - return rgbaStringToHslaColor(rgbaString); -}; - -const rgbaStringToHslaColor = (rgba: string): HslaColor => { - const rgbaColor = parseRgbaString(rgba); - const r = rgbaColor.r / 255; - const g = rgbaColor.g / 255; - const b = rgbaColor.b / 255; - - const max = Math.max(r, g, b), - min = Math.min(r, g, b); - let h, s; - const l = (max + min) / 2; - - if (max == min) { - h = s = 0; - } else { - const d = max - min; - s = l >= 0.5 ? d / (2 - (max + min)) : d / (max + min); - switch (max) { - case r: - h = ((g - b) / d) * 60; - break; - case g: - h = ((b - r) / d + 2) * 60; - break; - default: - h = ((r - g) / d + 4) * 60; - break; - } - } - - const a = rgbaColor.a || 1; - return { h: Math.round(h), s, l, a }; -}; - -const parseRgbaString = (str: string): RgbaColor => { - const [r, g, b, a] = str - .replace(CLEAN_RGBA_REGEX, '') - .split(',') - .map(c => Number.parseFloat(c)); - return { r, g, b, a }; -}; - -const parseHslaString = (str: string): HslaColor => { - const [h, s, l, a] = str - .replace(CLEAN_HSLA_REGEX, '') - .split(',') - .map(c => Number.parseFloat(c)); - return { h, s: s / 100, l: l / 100, a }; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/colors.ts b/packages/clerk-js/src/ui.retheme/utils/colors.ts deleted file mode 100644 index c5423c911d7..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/colors.ts +++ /dev/null @@ -1,347 +0,0 @@ -/* eslint-disable no-useless-escape */ -/** - * These helpers have been extracted from the following libraries, - * converted to Typescript and adapted to our needs. - * - * https://github.com/Qix-/color-convert - * https://github.com/Qix-/color-name - * https://github.com/Qix-/color - */ - -import type { HslaColor, HslaColorString } from '@clerk/types'; - -const abbrRegex = /^#([a-f0-9]{3,4})$/i; -const hexRegex = /^#([a-f0-9]{6})([a-f0-9]{2})?$/i; -const rgbaRegex = - /^rgba?\(\s*([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)\s*(?:[,|\/]\s*([+-]?[\d\.]+)(%?)\s*)?\)$/; -const perRegex = - /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,?\s*([+-]?[\d\.]+)\%\s*,?\s*([+-]?[\d\.]+)\%\s*(?:[,|\/]\s*([+-]?[\d\.]+)(%?)\s*)?\)$/; -const hslRegex = - /^hsla?\(\s*([+-]?(?:\d{0,3}\.)?\d+)(?:deg)?\s*,?\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*(?:[,|\/]\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/; -const hwbRegex = - /^hwb\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/; - -// List of common css keywords. -// https://github.com/colorjs/color-name -const keywords = { - black: [0, 0, 0, 1], - blue: [0, 0, 255, 1], - red: [255, 0, 0, 1], - green: [0, 128, 0, 1], - grey: [128, 128, 128, 1], - gray: [128, 128, 128, 1], - white: [255, 255, 255, 1], - yellow: [255, 255, 0, 1], - transparent: [0, 0, 0, 0], -}; - -type ColorTuple = [number, number, number, number?]; -type ParsedResult = { model: 'hsl' | 'rgb' | 'hwb'; value: ColorTuple }; - -const clamp = (num: number, min: number, max: number) => Math.min(Math.max(min, num), max); - -const parseRgb = (str: string): ColorTuple | null => { - if (!str) { - return null; - } - const rgb = [0, 0, 0, 1]; - let match; - let i; - let hexAlpha; - - if ((match = str.match(hexRegex))) { - hexAlpha = match[2]; - match = match[1]; - - for (i = 0; i < 3; i++) { - const i2 = i * 2; - rgb[i] = parseInt(match.slice(i2, i2 + 2), 16); - } - - if (hexAlpha) { - rgb[3] = parseInt(hexAlpha, 16) / 255; - } - } else if ((match = str.match(abbrRegex))) { - match = match[1]; - hexAlpha = match[3]; - - for (i = 0; i < 3; i++) { - rgb[i] = parseInt(match[i] + match[i], 16); - } - - if (hexAlpha) { - rgb[3] = parseInt(hexAlpha + hexAlpha, 16) / 255; - } - } else if ((match = str.match(rgbaRegex))) { - for (i = 0; i < 3; i++) { - rgb[i] = parseInt(match[i + 1], 0); - } - - if (match[4]) { - if (match[5]) { - rgb[3] = parseFloat(match[4]) * 0.01; - } else { - rgb[3] = parseFloat(match[4]); - } - } - } else if ((match = str.match(perRegex))) { - for (i = 0; i < 3; i++) { - rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); - } - - if (match[4]) { - if (match[5]) { - rgb[3] = parseFloat(match[4]) * 0.01; - } else { - rgb[3] = parseFloat(match[4]); - } - } - } else if (str in keywords) { - return keywords[str as keyof typeof keywords] as any; - } else { - return null; - } - - for (i = 0; i < 3; i++) { - rgb[i] = clamp(rgb[i], 0, 255); - } - rgb[3] = clamp(rgb[3], 0, 1); - - return rgb as any; -}; - -const parseHsl = (str: string): ColorTuple | null => { - if (!str) { - return null; - } - const match = str.match(hslRegex); - return match ? transformHslOrHwb(match) : null; -}; - -const parseHwb = function (str: string): ColorTuple | null { - if (!str) { - return null; - } - const match = str.match(hwbRegex); - return match ? transformHslOrHwb(match) : null; -}; - -const transformHslOrHwb = (match: any): ColorTuple => { - const alpha = parseFloat(match[4]); - const hh = ((parseFloat(match[1]) % 360) + 360) % 360; - const sw = clamp(parseFloat(match[2]), 0, 100); - const lb = clamp(parseFloat(match[3]), 0, 100); - const aa = clamp(isNaN(alpha) ? 1 : alpha, 0, 1); - return [hh, sw, lb, aa]; -}; - -const hslaTupleToHslaColor = (hsla: ColorTuple): HslaColor => { - return { h: hsla[0], s: hsla[1], l: hsla[2], a: hsla[3] ?? 1 }; -}; - -const hwbTupleToRgbTuple = (hwb: ColorTuple): ColorTuple => { - const h = hwb[0] / 360; - let wh = hwb[1] / 100; - let bl = hwb[2] / 100; - const a = hwb[3] ?? 1; - const ratio = wh + bl; - let f; - - // Wh + bl cant be > 1 - if (ratio > 1) { - wh /= ratio; - bl /= ratio; - } - - const i = Math.floor(6 * h); - const v = 1 - bl; - f = 6 * h - i; - - if ((i & 0x01) !== 0) { - f = 1 - f; - } - - const n = wh + f * (v - wh); // Linear interpolation - - let r; - let g; - let b; - - switch (i) { - default: - case 6: - case 0: - r = v; - g = n; - b = wh; - break; - case 1: - r = n; - g = v; - b = wh; - break; - case 2: - r = wh; - g = v; - b = n; - break; - case 3: - r = wh; - g = n; - b = v; - break; - case 4: - r = n; - g = wh; - b = v; - break; - case 5: - r = v; - g = wh; - b = n; - break; - } - /* eslint-enable max-statements-per-line,no-multi-spaces */ - - return [r * 255, g * 255, b * 255, a]; -}; - -const rgbaTupleToHslaColor = (rgb: ColorTuple): HslaColor => { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const a = rgb[3] ?? 1; - const min = Math.min(r, g, b); - const max = Math.max(r, g, b); - const delta = max - min; - let h; - let s; - - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } - - // @ts-ignore - h = Math.min(h * 60, 360); - - if (h < 0) { - h += 360; - } - - const l = (min + max) / 2; - - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - - return { h: Math.floor(h), s: Math.floor(s * 100), l: Math.floor(l * 100), a }; -}; - -const hwbTupleToHslaColor = (hwb: ColorTuple): HslaColor => { - return rgbaTupleToHslaColor(hwbTupleToRgbTuple(hwb)); -}; - -const hslaColorToHslaString = ({ h, s, l, a }: HslaColor): HslaColorString => { - return `hsla(${h}, ${s}%, ${l}%, ${a ?? 1})` as HslaColorString; -}; - -const parse = (str: string): ParsedResult => { - const prefix = str.substr(0, 3).toLowerCase(); - let res; - if (prefix === 'hsl') { - res = { model: 'hsl', value: parseHsl(str) }; - } else if (prefix === 'hwb') { - res = { model: 'hwb', value: parseHwb(str) }; - } else { - res = { model: 'rgb', value: parseRgb(str) }; - } - if (!res || !res.value) { - throw new Error(`Clerk: "${str}" cannot be used as a color within 'variables'. You can pass one of: -- any valid hsl or hsla color -- any valid rgb or rgba color -- any valid hex color -- any valid hwb color -- ${Object.keys(keywords).join(', ')} -`); - } - return res as ParsedResult; -}; - -const toHslaColor = (str: string): HslaColor => { - const { model, value } = parse(str); - switch (model) { - case 'hsl': - return hslaTupleToHslaColor(value); - case 'hwb': - return hwbTupleToHslaColor(value); - case 'rgb': - return rgbaTupleToHslaColor(value); - } -}; - -const toHslaString = (hsla: HslaColor | string): HslaColorString => { - return typeof hsla === 'string' ? hslaColorToHslaString(toHslaColor(hsla)) : hslaColorToHslaString(hsla); -}; - -const changeHslaLightness = (color: HslaColor, num: number): HslaColor => { - return { ...color, l: color.l + num }; -}; - -const setHslaAlpha = (color: HslaColor, num: number): HslaColor => { - return { ...color, a: num }; -}; - -const changeHslaAlpha = (color: HslaColor, num: number): HslaColor => { - return { ...color, a: color.a ? color.a - num : undefined }; -}; - -const lighten = (color: string | undefined, percentage = 0): string | undefined => { - if (!color) { - return undefined; - } - const hsla = toHslaColor(color); - return toHslaString(changeHslaLightness(hsla, hsla.l * percentage)); -}; - -const makeSolid = (color: string | undefined): string | undefined => { - if (!color) { - return undefined; - } - return toHslaString({ ...toHslaColor(color), a: 1 }); -}; - -const makeTransparent = (color: string | undefined, percentage = 0): string | undefined => { - if (!color || color.toString() === '') { - return undefined; - } - const hsla = toHslaColor(color); - return toHslaString(changeHslaAlpha(hsla, (hsla.a ?? 1) * percentage)); -}; - -const setAlpha = (color: string, alpha: number) => { - if (!color.toString()) { - return color; - } - return toHslaString(setHslaAlpha(toHslaColor(color), alpha)); -}; - -export const colors = { - toHslaColor, - toHslaString, - changeHslaLightness, - setHslaAlpha, - lighten, - makeTransparent, - makeSolid, - setAlpha, -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/containsAllOf.ts b/packages/clerk-js/src/ui.retheme/utils/containsAllOf.ts deleted file mode 100644 index 3a46e1ce07a..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/containsAllOf.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Enforces that an array contains ALL keys of T - */ -export const containsAllOfType = - () => - >(array: U & ([T] extends [U[number]] ? unknown : 'Invalid')) => - array; diff --git a/packages/clerk-js/src/ui.retheme/utils/createCustomPages.tsx b/packages/clerk-js/src/ui.retheme/utils/createCustomPages.tsx deleted file mode 100644 index cff378eaf3d..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/createCustomPages.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import { isDevelopmentEnvironment } from '@clerk/shared'; -import type { CustomPage } from '@clerk/types'; - -import { isValidUrl } from '../../utils'; -import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID, USER_PROFILE_NAVBAR_ROUTE_ID } from '../constants'; -import type { NavbarRoute } from '../elements'; -import { CogFilled, TickShield, User } from '../icons'; -import { localizationKeys } from '../localization'; -import { ExternalElementMounter } from './ExternalElementMounter'; - -export type CustomPageContent = { - url: string; - mount: (el: HTMLDivElement) => void; - unmount: (el?: HTMLDivElement) => void; -}; - -type ProfileReorderItem = { - label: 'account' | 'security' | 'members' | 'settings'; -}; - -type ProfileCustomPage = { - label: string; - url: string; - mountIcon: (el: HTMLDivElement) => void; - unmountIcon: (el?: HTMLDivElement) => void; - mount: (el: HTMLDivElement) => void; - unmount: (el?: HTMLDivElement) => void; -}; - -type ProfileCustomLink = { - label: string; - url: string; - mountIcon: (el: HTMLDivElement) => void; - unmountIcon: (el?: HTMLDivElement) => void; -}; - -type GetDefaultRoutesReturnType = { - INITIAL_ROUTES: NavbarRoute[]; - pageToRootNavbarRouteMap: Record; - validReorderItemLabels: string[]; -}; - -type CreateCustomPagesParams = { - customPages: CustomPage[]; - getDefaultRoutes: () => GetDefaultRoutesReturnType; - setFirstPathToRoot: (routes: NavbarRoute[]) => NavbarRoute[]; - excludedPathsFromDuplicateWarning: string[]; -}; - -export const createUserProfileCustomPages = (customPages: CustomPage[]) => { - return createCustomPages({ - customPages, - getDefaultRoutes: getUserProfileDefaultRoutes, - setFirstPathToRoot: setFirstPathToUserProfileRoot, - excludedPathsFromDuplicateWarning: ['/', 'account'], - }); -}; - -export const createOrganizationProfileCustomPages = (customPages: CustomPage[]) => { - return createCustomPages({ - customPages, - getDefaultRoutes: getOrganizationProfileDefaultRoutes, - setFirstPathToRoot: setFirstPathToOrganizationProfileRoot, - excludedPathsFromDuplicateWarning: [], - }); -}; - -const createCustomPages = ({ - customPages, - getDefaultRoutes, - setFirstPathToRoot, - excludedPathsFromDuplicateWarning, -}: CreateCustomPagesParams) => { - const { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels } = getDefaultRoutes(); - - if (isDevelopmentEnvironment()) { - checkForDuplicateUsageOfReorderingItems(customPages, validReorderItemLabels); - } - - const validCustomPages = customPages.filter(cp => { - if (!isValidPageItem(cp, validReorderItemLabels)) { - if (isDevelopmentEnvironment()) { - console.error('Clerk: Invalid custom page data: ', cp); - } - return false; - } - return true; - }); - - const { allRoutes, contents } = getRoutesAndContents({ - customPages: validCustomPages, - defaultRoutes: INITIAL_ROUTES, - }); - - assertExternalLinkAsRoot(allRoutes); - - const routes = setFirstPathToRoot(allRoutes); - - if (isDevelopmentEnvironment()) { - warnForDuplicatePaths(routes, excludedPathsFromDuplicateWarning); - } - - return { - routes, - contents, - pageToRootNavbarRouteMap, - }; -}; - -type GetRoutesAndContentsParams = { - customPages: CustomPage[]; - defaultRoutes: NavbarRoute[]; -}; - -const getRoutesAndContents = ({ customPages, defaultRoutes }: GetRoutesAndContentsParams) => { - let remainingDefaultRoutes: NavbarRoute[] = defaultRoutes.map(r => r); - const contents: CustomPageContent[] = []; - - const routesWithoutDefaults: NavbarRoute[] = customPages.map((cp, index) => { - if (isCustomLink(cp)) { - return { - name: cp.label, - id: `custom-page-${index}`, - icon: props => ( - - ), - path: sanitizeCustomLinkURL(cp.url), - external: true, - }; - } - if (isCustomPage(cp)) { - const pageURL = sanitizeCustomPageURL(cp.url); - contents.push({ url: pageURL, mount: cp.mount, unmount: cp.unmount }); - return { - name: cp.label, - id: `custom-page-${index}`, - icon: props => ( - - ), - path: pageURL, - }; - } - const reorderItem = defaultRoutes.find(r => r.id === cp.label) as NavbarRoute; - remainingDefaultRoutes = remainingDefaultRoutes.filter(({ id }) => id !== cp.label); - return { ...reorderItem }; - }); - - const allRoutes = [...remainingDefaultRoutes, ...routesWithoutDefaults]; - - return { allRoutes, contents }; -}; - -// Set the path of the first route to '/' or if the first route is account or security, set the path of both account and security to '/' -const setFirstPathToUserProfileRoot = (routes: NavbarRoute[]): NavbarRoute[] => { - if (routes[0].id === 'account' || routes[0].id === 'security') { - return routes.map(r => { - if (r.id === 'account' || r.id === 'security') { - return { ...r, path: '/' }; - } - return r; - }); - } else { - return routes.map((r, index) => (index === 0 ? { ...r, path: '/' } : r)); - } -}; - -const setFirstPathToOrganizationProfileRoot = (routes: NavbarRoute[]): NavbarRoute[] => { - return routes.map((r, index) => (index === 0 ? { ...r, path: '/' } : r)); -}; - -const checkForDuplicateUsageOfReorderingItems = (customPages: CustomPage[], validReorderItems: string[]) => { - const reorderItems = customPages.filter(cp => isReorderItem(cp, validReorderItems)); - reorderItems.reduce((acc, cp) => { - if (acc.includes(cp.label)) { - console.error( - `Clerk: The "${cp.label}" item is used more than once when reordering pages. This may cause unexpected behavior.`, - ); - } - return [...acc, cp.label]; - }, [] as string[]); -}; - -//path !== '/' && path !== 'account' -const warnForDuplicatePaths = (routes: NavbarRoute[], pathsToFilter: string[]) => { - const paths = routes - .filter(({ external, path }) => !external && pathsToFilter.every(p => p !== path)) - .map(({ path }) => path); - const duplicatePaths = paths.filter((p, index) => paths.indexOf(p) !== index); - duplicatePaths.forEach(p => { - console.error(`Clerk: Duplicate path "${p}" found in custom pages. This may cause unexpected behavior.`); - }); -}; - -const isValidPageItem = (cp: CustomPage, validReorderItems: string[]): cp is CustomPage => { - return isCustomPage(cp) || isCustomLink(cp) || isReorderItem(cp, validReorderItems); -}; - -const isCustomPage = (cp: CustomPage): cp is ProfileCustomPage => { - return !!cp.url && !!cp.label && !!cp.mount && !!cp.unmount && !!cp.mountIcon && !!cp.unmountIcon; -}; - -const isCustomLink = (cp: CustomPage): cp is ProfileCustomLink => { - return !!cp.url && !!cp.label && !cp.mount && !cp.unmount && !!cp.mountIcon && !!cp.unmountIcon; -}; - -const isReorderItem = (cp: CustomPage, validItems: string[]): cp is ProfileReorderItem => { - return ( - !cp.url && !cp.mount && !cp.unmount && !cp.mountIcon && !cp.unmountIcon && validItems.some(v => v === cp.label) - ); -}; - -const sanitizeCustomPageURL = (url: string): string => { - if (!url) { - throw new Error('Clerk: URL is required for custom pages'); - } - if (isValidUrl(url)) { - throw new Error('Clerk: Absolute URLs are not supported for custom pages'); - } - return (url as string).charAt(0) === '/' && (url as string).length > 1 ? (url as string).substring(1) : url; -}; - -const sanitizeCustomLinkURL = (url: string): string => { - if (!url) { - throw new Error('Clerk: URL is required for custom links'); - } - if (isValidUrl(url)) { - return url; - } - return (url as string).charAt(0) === '/' ? url : `/${url}`; -}; - -const assertExternalLinkAsRoot = (routes: NavbarRoute[]) => { - if (routes[0].external) { - throw new Error('Clerk: The first route cannot be a custom external link component'); - } -}; - -const getUserProfileDefaultRoutes = (): GetDefaultRoutesReturnType => { - const INITIAL_ROUTES: NavbarRoute[] = [ - { - name: localizationKeys('userProfile.start.headerTitle__account'), - id: USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT, - icon: User, - path: 'account', - }, - { - name: localizationKeys('userProfile.start.headerTitle__security'), - id: USER_PROFILE_NAVBAR_ROUTE_ID.SECURITY, - icon: TickShield, - path: 'account', - }, - ]; - - const pageToRootNavbarRouteMap: Record = { - profile: INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - 'email-address': INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - 'phone-number': INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - 'connected-account': INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - 'web3-wallet': INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - username: INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT) as NavbarRoute, - 'multi-factor': INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.SECURITY) as NavbarRoute, - password: INITIAL_ROUTES.find(r => r.id === USER_PROFILE_NAVBAR_ROUTE_ID.SECURITY) as NavbarRoute, - }; - - const validReorderItemLabels: string[] = INITIAL_ROUTES.map(r => r.id); - - return { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels }; -}; - -const getOrganizationProfileDefaultRoutes = (): GetDefaultRoutesReturnType => { - const INITIAL_ROUTES: NavbarRoute[] = [ - { - name: localizationKeys('organizationProfile.start.headerTitle__members'), - id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS, - icon: User, - path: 'organization-members', - }, - { - name: localizationKeys('organizationProfile.start.headerTitle__settings'), - id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS, - icon: CogFilled, - path: 'organization-settings', - }, - ]; - - const pageToRootNavbarRouteMap: Record = { - 'invite-members': INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS) as NavbarRoute, - domain: INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS) as NavbarRoute, - profile: INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS) as NavbarRoute, - leave: INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS) as NavbarRoute, - delete: INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS) as NavbarRoute, - }; - - const validReorderItemLabels: string[] = INITIAL_ROUTES.map(r => r.id); - - return { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels }; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/createInfiniteAccessProxy.ts b/packages/clerk-js/src/ui.retheme/utils/createInfiniteAccessProxy.ts deleted file mode 100644 index 5b6db71d7eb..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/createInfiniteAccessProxy.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * This function leverages JS Proxies to create an object that - * can be used as a placeholder when you want to run a callback - * but you don't have the necessary arguments ready yet. - * - * This proxy recursively returns itself so it can be passed safely - * when you don't know how the caller will try to access it. - * - * Eg: - * `const userCallback = (theme) => theme.prop1.prop2.prop3.replace();` - * - * Calling `userCallback` as follows is safe: - * `userCallback(createInfiniteAccessProxy())` - */ -export const createInfiniteAccessProxy = () => { - const get: ProxyHandler['get'] = (_, prop) => { - if (prop === Symbol.toPrimitive) { - return () => ''; - } else if (prop in Object.getPrototypeOf('')) { - return (args: unknown) => Object.getPrototypeOf('')[prop].call('', args); - } - return prop === Symbol.toPrimitive ? () => '' : proxy; - }; - - const proxy: any = new Proxy({}, { get }); - return proxy; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/createSlug.ts b/packages/clerk-js/src/ui.retheme/utils/createSlug.ts deleted file mode 100644 index 15c9eaf7638..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/createSlug.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const createSlug = (str: string): string => { - const trimmedStr = str.trim().toLowerCase(); - const slug = trimmedStr.replace(/[^a-z0-9]+/g, '-'); - return slug; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/errorHandler.ts b/packages/clerk-js/src/ui.retheme/utils/errorHandler.ts deleted file mode 100644 index 9864611c52e..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/errorHandler.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { snakeToCamel } from '@clerk/shared'; -import type { ClerkAPIError, ClerkRuntimeError } from '@clerk/types'; - -import { - isClerkAPIResponseError, - isClerkRuntimeError, - isKnownError, - isMetamaskError, -} from '../../core/resources/internal'; -import type { FormControlState } from './useFormControl'; - -interface ParserErrors { - fieldErrors: ClerkAPIError[]; - globalErrors: ClerkAPIError[]; -} - -const getFirstError = (err: ClerkAPIError[]) => err[0]; - -function setFieldErrors(fieldStates: Array>, errors: ClerkAPIError[]) { - if (!errors || errors.length < 1) { - return; - } - - fieldStates.forEach(field => { - let buildErrorMessage = field?.buildErrorMessage; - - if (!buildErrorMessage) { - buildErrorMessage = getFirstError; - } - - const errorsArray = errors.filter(err => { - return err.meta!.paramName === field.id || snakeToCamel(err.meta!.paramName) === field.id; - }); - - field.setError(errorsArray.length ? buildErrorMessage(errorsArray) : undefined); - }); -} - -function parseErrors(errors: ClerkAPIError[]): ParserErrors { - return (errors || []).reduce( - (memo, err) => { - if (err.meta!.paramName) { - memo.fieldErrors.push(err); - } else { - memo.globalErrors.push(err); - } - return memo; - }, - { - fieldErrors: Array(0), - globalErrors: Array(0), - }, - ); -} - -type HandleError = { - ( - err: Error, - fieldStates: Array>, - setGlobalError?: (err: ClerkRuntimeError | ClerkAPIError | string | undefined) => void, - ): void; -}; - -export const handleError: HandleError = (err, fieldStates, setGlobalError) => { - // Throw unknown errors - if (!isKnownError(err)) { - throw err; - } - - if (isMetamaskError(err)) { - return handleMetamaskError(err, fieldStates, setGlobalError); - } - - if (isClerkAPIResponseError(err)) { - return handleClerkApiError(err, fieldStates, setGlobalError); - } - - if (isClerkRuntimeError(err)) { - return handleClerkRuntimeError(err, fieldStates, setGlobalError); - } -}; - -// Returns the first global API error or undefined if none exists. -export function getGlobalError(err: Error): ClerkAPIError | undefined { - if (!isClerkAPIResponseError(err)) { - return; - } - const { globalErrors } = parseErrors(err.errors); - if (!globalErrors.length) { - return; - } - return globalErrors[0]; -} - -// Returns the first field API error or undefined if none exists. -export function getFieldError(err: Error): ClerkAPIError | undefined { - if (!isClerkAPIResponseError(err)) { - return; - } - const { fieldErrors } = parseErrors(err.errors); - - if (!fieldErrors.length) { - return; - } - - return fieldErrors[0]; -} - -export function getClerkAPIErrorMessage(err: ClerkAPIError): string { - return err.longMessage || err.message; -} - -const handleMetamaskError: HandleError = (err, _, setGlobalError) => { - return setGlobalError?.(err.message); -}; - -const handleClerkApiError: HandleError = (err, fieldStates, setGlobalError) => { - if (!isClerkAPIResponseError(err)) { - return; - } - - const { fieldErrors, globalErrors } = parseErrors(err.errors); - setFieldErrors(fieldStates, fieldErrors); - - if (setGlobalError) { - setGlobalError(undefined); - // Show only the first global error until we have snack bar stacks if applicable - // TODO: Make global errors localizable - const firstGlobalError = globalErrors[0]; - if (firstGlobalError) { - setGlobalError(firstGlobalError); - } - } -}; - -const handleClerkRuntimeError: HandleError = (err, _, setGlobalError) => { - if (!isClerkRuntimeError(err)) { - return; - } - - if (setGlobalError) { - setGlobalError(undefined); - const firstGlobalError = err; - if (firstGlobalError) { - setGlobalError(firstGlobalError); - } - } -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/factorSorting.ts b/packages/clerk-js/src/ui.retheme/utils/factorSorting.ts deleted file mode 100644 index d2569d10dfb..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/factorSorting.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { SignInFactor, SignInStrategy } from '@clerk/types'; - -const makeSortingOrderMap = (arr: T[]): Record => - arr.reduce((acc, k, i) => { - acc[k] = i; - return acc; - }, {} as Record); - -const STRATEGY_SORT_ORDER_PASSWORD_PREF = makeSortingOrderMap([ - 'password', - 'email_link', - 'email_code', - 'phone_code', -] as SignInStrategy[]); - -const STRATEGY_SORT_ORDER_OTP_PREF = makeSortingOrderMap([ - 'email_link', - 'email_code', - 'phone_code', - 'password', -] as SignInStrategy[]); - -const STRATEGY_SORT_ORDER_ALL_STRATEGIES_BUTTONS = makeSortingOrderMap([ - 'email_link', - 'email_code', - 'phone_code', - 'password', -] as SignInStrategy[]); - -const STRATEGY_SORT_ORDER_BACKUP_CODE_PREF = makeSortingOrderMap([ - 'totp', - 'phone_code', - 'backup_code', -] as SignInStrategy[]); - -const makeSortingFunction = - (sortingMap: Record) => - (a: SignInFactor, b: SignInFactor): number => { - const orderA = sortingMap[a.strategy]; - const orderB = sortingMap[b.strategy]; - if (orderA === undefined || orderB === undefined) { - return 0; - } - return orderA - orderB; - }; - -export const passwordPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_PASSWORD_PREF); -export const otpPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_OTP_PREF); -export const backupCodePrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_BACKUP_CODE_PREF); -export const allStrategiesButtonsComparator = makeSortingFunction(STRATEGY_SORT_ORDER_ALL_STRATEGIES_BUTTONS); diff --git a/packages/clerk-js/src/ui.retheme/utils/fastDeepMerge.ts b/packages/clerk-js/src/ui.retheme/utils/fastDeepMerge.ts deleted file mode 100644 index 007eb4cc495..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/fastDeepMerge.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Merges 2 objects without creating new object references - * The merged props will appear on the `target` object - * If `target` already has a value for a given key it will not be overwritten - */ -export const fastDeepMergeAndReplace = ( - source: Record | undefined | null, - target: Record | undefined | null, -) => { - if (!source || !target) { - return; - } - - for (const key in source) { - if (source[key] !== null && typeof source[key] === `object`) { - if (target[key] === undefined) { - target[key] = new (Object.getPrototypeOf(source[key]).constructor)(); - } - fastDeepMergeAndReplace(source[key], target[key]); - } else { - target[key] = source[key]; - } - } -}; - -export const fastDeepMergeAndKeep = ( - source: Record | undefined | null, - target: Record | undefined | null, -) => { - if (!source || !target) { - return; - } - - for (const key in source) { - if (source[key] !== null && typeof source[key] === `object`) { - if (target[key] === undefined) { - target[key] = new (Object.getPrototypeOf(source[key]).constructor)(); - } - fastDeepMergeAndKeep(source[key], target[key]); - } else if (target[key] === undefined) { - target[key] = source[key]; - } - } -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.test.ts b/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.test.ts deleted file mode 100644 index 15f57a6544d..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { formatSafeIdentifier } from './formatSafeIdentifier'; - -describe('formatSafeIdentifier', () => { - const cases = [ - ['hello@example.com', 'hello@example.com'], - ['h***@***.com', 'h***@***.com'], - ['username', 'username'], - ['u***e', 'u***e'], - ['+71111111111', '+7 111 111-11-11'], - ['+791*******1', '+791*******1'], - ]; - - it.each(cases)('formats the safe identifier', (str, expected) => { - expect(formatSafeIdentifier(str)).toBe(expected); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.ts b/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.ts deleted file mode 100644 index 2684f22f6f0..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/formatSafeIdentifier.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { stringToFormattedPhoneString } from './phoneUtils'; - -export const isMaskedIdentifier = (str: string | undefined | null) => str && str.includes('**'); - -/** - * Formats a string that can contain an email, a username or a phone number. - * Depending on the scenario, the string might be obfuscated (parts of the identifier replaced with "*") - * Refer to the tests for examples. - */ -export const formatSafeIdentifier = (str: string | undefined | null) => { - if (!str || str.includes('@') || isMaskedIdentifier(str) || str.match(/[a-zA-Z]/)) { - return str; - } - return stringToFormattedPhoneString(str); -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/fromEntries.ts b/packages/clerk-js/src/ui.retheme/utils/fromEntries.ts deleted file mode 100644 index 0de89b5b2b0..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/fromEntries.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const fromEntries = (iterable: Iterable) => { - return [...iterable].reduce((obj, [key, val]) => { - obj[key] = val; - return obj; - }, {}); -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/getRelativeToNowDateKey.ts b/packages/clerk-js/src/ui.retheme/utils/getRelativeToNowDateKey.ts deleted file mode 100644 index 93ca664e02f..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/getRelativeToNowDateKey.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { formatRelative } from '@clerk/shared'; - -import { localizationKeys } from '../localization'; - -export const getRelativeToNowDateKey = (date: Date) => { - const relativeDate = formatRelative({ date: date || new Date(), relativeTo: new Date() }); - if (!relativeDate) { - return ''; - } - switch (relativeDate.relativeDateCase) { - case 'previous6Days': - return localizationKeys('dates.previous6Days', { date: relativeDate.date }); - case 'lastDay': - return localizationKeys('dates.lastDay', { date: relativeDate.date }); - case 'sameDay': - return localizationKeys('dates.sameDay', { date: relativeDate.date }); - case 'nextDay': - return localizationKeys('dates.nextDay', { date: relativeDate.date }); - case 'next6Days': - return localizationKeys('dates.next6Days', { date: relativeDate.date }); - case 'other': - default: - return localizationKeys('dates.numeric', { date: relativeDate.date }); - } -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/getValidReactChildren.ts b/packages/clerk-js/src/ui.retheme/utils/getValidReactChildren.ts deleted file mode 100644 index edf8bae4c9e..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/getValidReactChildren.ts +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export function getValidChildren(children: React.ReactNode) { - return React.Children.toArray(children).filter(child => React.isValidElement(child)) as React.ReactElement[]; -} diff --git a/packages/clerk-js/src/ui.retheme/utils/index.ts b/packages/clerk-js/src/ui.retheme/utils/index.ts deleted file mode 100644 index 782bf87bcd7..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export * from './fromEntries'; -export * from './containsAllOf'; -export * from './createInfiniteAccessProxy'; -export * from './fastDeepMerge'; -export * from './intl'; -export * from './colors'; -export * from './factorSorting'; -export * from './sleep'; -export * from './isMobileDevice'; -export * from './phoneUtils'; -export * from './formatSafeIdentifier'; -export * from './removeUndefinedProps'; -export * from './readObjectPath'; -export * from './useFormControl'; -export * from './errorHandler'; -export * from './range'; -export * from './getValidReactChildren'; -export * from './roleLocalizationKey'; -export * from './getRelativeToNowDateKey'; -export * from './mergeRefs'; -export * from './createSlug'; -export * from './passwordUtils'; -export * from './createCustomPages'; -export * from './ExternalElementMounter'; diff --git a/packages/clerk-js/src/ui.retheme/utils/intl.ts b/packages/clerk-js/src/ui.retheme/utils/intl.ts deleted file mode 100644 index 93acf59ea12..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/intl.ts +++ /dev/null @@ -1,15 +0,0 @@ -function supportedLocalesOf(locale?: string | string[]) { - if (!locale) { - return false; - } - const locales = Array.isArray(locale) ? locale : [locale]; - return (Intl as any).ListFormat.supportedLocalesOf(locales).length === locales.length; -} - -/** - * Intl.ListFormat was introduced in 2021 - * It is recommended to first check for browser support before using it - */ -export function canUseListFormat(locale: string | undefined) { - return 'ListFormat' in Intl && supportedLocalesOf(locale); -} diff --git a/packages/clerk-js/src/ui.retheme/utils/isMobileDevice.ts b/packages/clerk-js/src/ui.retheme/utils/isMobileDevice.ts deleted file mode 100644 index 4523cab1c29..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/isMobileDevice.ts +++ /dev/null @@ -1,13 +0,0 @@ -const mobileNavigatorsRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; - -export const isMobileDevice = (): boolean => { - if (typeof window === 'undefined' || typeof window.document === 'undefined') { - return false; - } - - return !!( - window.matchMedia('only screen and (max-width: 760px)').matches || - mobileNavigatorsRegex.test(navigator.userAgent) || - ('ontouchstart' in document.documentElement && navigator.userAgent.match(/Mobi/)) - ); -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/mergeRefs.ts b/packages/clerk-js/src/ui.retheme/utils/mergeRefs.ts deleted file mode 100644 index bd48cbf471d..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/mergeRefs.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const mergeRefs = (...refs: React.RefObject[]) => { - return (node: any) => { - for (const _ref of refs) { - if (_ref) { - //@ts-expect-error - _ref.current = node; - } - } - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/passwordUtils.test.tsx b/packages/clerk-js/src/ui.retheme/utils/passwordUtils.test.tsx deleted file mode 100644 index 6c95e5ca6e5..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/passwordUtils.test.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import { renderHook } from '../../testUtils'; -import { OptionsProvider } from '../contexts'; -import { useLocalizations } from '../customizables'; -import { createPasswordError } from './passwordUtils'; -import { bindCreateFixtures } from './test/createFixtures'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -describe('createPasswordError() constructs error that password', () => { - const createLocalizationConfig = t => ({ - t, - locale: 'en-US', - passwordSettings: { max_length: 72, min_length: 8 }, - }); - - it('is too short', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [{ code: 'form_password_length_too_short', message: '' }], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain 8 or more characters.'); - }); - - it('is too short and needs an uppercase character. Shows only min_length error.', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { code: 'form_password_length_too_short', message: '' }, - { code: 'form_password_no_uppercase', message: '' }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain 8 or more characters.'); - }); - - it('needs an uppercase character', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [{ code: 'form_password_no_uppercase', message: '' }], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain an uppercase letter.'); - }); - - it('is too short and needs an lowercase character. Shows only min_length error', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { code: 'form_password_length_too_short', message: '' }, - { code: 'form_password_no_lowercase', message: '' }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain 8 or more characters.'); - }); - - it('needs a lowercase and an uppercase character', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { code: 'form_password_no_lowercase', message: '' }, - { code: 'form_password_no_uppercase', message: '' }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain a lowercase letter and an uppercase letter.'); - }); - - it('needs a lowercase, an uppercase and a number', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { code: 'form_password_no_number', message: '' }, - { code: 'form_password_no_lowercase', message: '' }, - { code: 'form_password_no_uppercase', message: '' }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password must contain a number, a lowercase letter, and an uppercase letter.'); - }); - - it('needs a lowercase, an uppercase and a number', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { code: 'form_password_no_special_char', message: '' }, - { code: 'form_password_no_number', message: '' }, - { code: 'form_password_no_lowercase', message: '' }, - { code: 'form_password_no_uppercase', message: '' }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe( - 'Your password must contain a special character, a number, a lowercase letter, and an uppercase letter.', - ); - }); - - // - // zxcvbn - // - // - it('is not strong enough', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { - code: 'form_password_not_strong_enough', - message: '', - meta: { - zxcvbn: { - suggestions: [{ code: 'anotherWord', message: '' }], - }, - }, - }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe('Your password is not strong enough. Add more words that are less common.'); - }); - - it('is not strong enough and has repeated characters', async () => { - const { wrapper: Wrapper } = await createFixtures(); - - const wrapperBefore = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); - - const res = createPasswordError( - [ - { - code: 'form_password_not_strong_enough', - message: '', - meta: { - zxcvbn: { - suggestions: [ - { code: 'anotherWord', message: '' }, - { code: 'repeated', message: '' }, - ], - }, - }, - }, - ], - createLocalizationConfig(result.current.t), - ); - expect(res).toBe( - 'Your password is not strong enough. Add more words that are less common. Avoid repeated words and characters.', - ); - }); -}); diff --git a/packages/clerk-js/src/ui.retheme/utils/passwordUtils.ts b/packages/clerk-js/src/ui.retheme/utils/passwordUtils.ts deleted file mode 100644 index b05ef79688a..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/passwordUtils.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { ClerkAPIError, PasswordSettingsData } from '@clerk/types'; - -import type { LocalizationKey } from '../localization'; -import { localizationKeys } from '../localization/localizationKeys'; -import { canUseListFormat } from '../utils'; - -// match FAPI error codes with localization keys -export const mapComplexityErrors = (passwordSettings: Pick) => { - return { - form_password_length_too_long: [ - 'unstable__errors.passwordComplexity.maximumLength', - 'length', - passwordSettings.max_length, - ], - form_password_length_too_short: [ - 'unstable__errors.passwordComplexity.minimumLength', - 'length', - passwordSettings.min_length, - ], - form_password_no_uppercase: 'unstable__errors.passwordComplexity.requireUppercase', - form_password_no_lowercase: 'unstable__errors.passwordComplexity.requireLowercase', - form_password_no_number: 'unstable__errors.passwordComplexity.requireNumbers', - form_password_no_special_char: 'unstable__errors.passwordComplexity.requireSpecialCharacter', - }; -}; - -type LocalizationConfigProps = { - t: (localizationKey: LocalizationKey | string | undefined) => string; - locale: string; - passwordSettings: Pick; -}; - -export const createPasswordError = (errors: ClerkAPIError[], localizationConfig: LocalizationConfigProps) => { - if (!localizationConfig) { - return errors[0].longMessage; - } - - const { t, locale, passwordSettings } = localizationConfig; - - if (errors?.[0]?.code === 'form_password_size_in_bytes_exceeded' || errors?.[0]?.code === 'form_password_pwned') { - return `${t(localizationKeys(`unstable__errors.${errors?.[0]?.code}` as any)) || errors?.[0]?.message}`; - } - - if (errors?.[0]?.code === 'form_password_not_strong_enough') { - const message = errors[0].meta?.zxcvbn?.suggestions - ?.map(s => { - return t(localizationKeys(`unstable__errors.zxcvbn.suggestions.${s.code}` as any)); - }) - .join(' '); - - return `${t(localizationKeys('unstable__errors.zxcvbn.notEnough'))} ${message}`; - } - - // show min length error first by itself - const minLenErrors = errors.filter(e => e.code === 'form_password_length_too_short'); - - const message = (minLenErrors.length ? minLenErrors : errors).map((s: any) => { - const localizedKey = (mapComplexityErrors(passwordSettings) as any)[s.code]; - - if (Array.isArray(localizedKey)) { - const [lk, attr, val] = localizedKey; - return t(localizationKeys(lk, { [attr]: val })); - } - return t(localizationKeys(localizedKey)); - }); - - const messageWithPrefix = createListFormat(message, locale); - - const passwordErrorMessage = addFullStop( - `${t(localizationKeys('unstable__errors.passwordComplexity.sentencePrefix'))} ${messageWithPrefix}`, - ); - - return passwordErrorMessage; -}; - -export const addFullStop = (string: string | undefined) => { - return !string ? '' : string.endsWith('.') ? string : `${string}.`; -}; - -export const createListFormat = (message: string[], locale: string) => { - let messageWithPrefix: string; - if (canUseListFormat(locale)) { - const formatter = new Intl.ListFormat(locale, { style: 'long', type: 'conjunction' }); - messageWithPrefix = formatter.format(message); - } else { - messageWithPrefix = message.join(', '); - } - - return messageWithPrefix; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/phoneUtils.ts b/packages/clerk-js/src/ui.retheme/utils/phoneUtils.ts deleted file mode 100644 index 25b565ea6d2..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/phoneUtils.ts +++ /dev/null @@ -1,129 +0,0 @@ -import type { CountryEntry, CountryIso } from '../elements/PhoneInput/countryCodeData'; -import { CodeToCountriesMap, IsoToCountryMap, SubAreaCodeSets } from '../elements/PhoneInput/countryCodeData'; - -// offset between uppercase ascii and regional indicator symbols -const OFFSET = 127397; -const emojiCache = {} as Record; -export function getFlagEmojiFromCountryIso(iso: CountryIso, fallbackIso = 'us'): string { - iso = iso || fallbackIso; - if (emojiCache[iso]) { - return emojiCache[iso]; - } - const codePoints = [...iso.toUpperCase()].map(c => c.codePointAt(0)! + OFFSET); - const res = String.fromCodePoint(...codePoints); - emojiCache[iso] = res; - return res; -} - -export function getCountryIsoFromFormattedNumber(formattedNumber: string, fallbackIso = 'us'): string { - const number = extractDigits(formattedNumber); - if (!number || number.length < 4) { - return fallbackIso; - } - - // Try to match US first based on subarea code - if (number.startsWith('1') && phoneNumberBelongsTo('us', number)) { - return 'us'; - } - - // Try to match CA first based on subarea code - if (number.startsWith('1') && phoneNumberBelongsTo('ca', number)) { - return 'ca'; - } - - // Otherwise, use the most specific code or fallback to US - return getCountryFromPhoneString(number).country.iso; -} - -export function formatPhoneNumber(phoneNumber: string, pattern: string | undefined, countryCode?: string): string { - if (!phoneNumber || !pattern) { - return phoneNumber; - } - - const digits = [...extractDigits(phoneNumber)].slice(0, maxE164CompliantLength(countryCode)); - - if (digits.length <= 3) { - return digits.join(''); - } - - let res = ''; - for (let i = 0; digits.length > 0; i++) { - if (i > pattern.length - 1) { - res += digits.shift(); - } else { - res += pattern[i] === '.' ? digits.shift() : pattern[i]; - } - } - return res; -} - -export function extractDigits(formattedPhone: string): string { - return (formattedPhone || '').replace(/[^\d]/g, ''); -} - -function phoneNumberBelongsTo(iso: 'us' | 'ca', phoneWithCode: string) { - if (!iso || !IsoToCountryMap.get(iso) || !phoneWithCode) { - return false; - } - - const code = phoneWithCode[0]; - const subArea = phoneWithCode.substring(1, 4); - return ( - code === IsoToCountryMap.get(iso)?.code && - phoneWithCode.length - 1 === maxDigitCountForPattern(IsoToCountryMap.get(iso)?.pattern || '') && - SubAreaCodeSets[iso].has(subArea) - ); -} - -function maxDigitCountForPattern(pattern: string) { - return (pattern.match(/\./g) || []).length; -} - -// https://en.wikipedia.org/wiki/E.164 -const MAX_PHONE_NUMBER_LENGTH = 15; -function maxE164CompliantLength(countryCode?: string) { - const usCountryCode = '1'; - countryCode = countryCode || usCountryCode; - const codeWithPrefix = countryCode.includes('+') ? countryCode : '+' + countryCode; - return MAX_PHONE_NUMBER_LENGTH - codeWithPrefix.length; -} - -export function parsePhoneString(str: string) { - const digits = extractDigits(str); - const iso = getCountryIsoFromFormattedNumber(digits) as CountryIso; - const pattern = IsoToCountryMap.get(iso)?.pattern || ''; - const code = IsoToCountryMap.get(iso)?.code || ''; - const number = digits.slice(code.length); - const formattedNumberWithCode = `+${code} ${formatPhoneNumber(number, pattern, code)}`; - return { iso, pattern, code, number, formattedNumberWithCode }; -} - -export function stringToFormattedPhoneString(str: string): string { - const parsed = parsePhoneString(str); - return `+${parsed.code} ${formatPhoneNumber(parsed.number, parsed.pattern, parsed.code)}`; -} - -export const byPriority = (a: CountryEntry, b: CountryEntry) => { - return b.priority - a.priority; -}; - -export function getCountryFromPhoneString(phone: string): { number: string; country: CountryEntry } { - const phoneWithCode = extractDigits(phone); - const matchingCountries = []; - - // Max country code length is 4. Try to match more specific codes first - for (const i of [4, 3, 2, 1]) { - const potentialCode = phoneWithCode.substring(0, i); - const countries = CodeToCountriesMap.get(potentialCode as any) || []; - - if (countries.length) { - matchingCountries.push(...countries); - } - } - - const fallbackCountry = IsoToCountryMap.get('us'); - const country: CountryEntry = matchingCountries.sort(byPriority)[0] || fallbackCountry; - const number = phoneWithCode.slice(country?.code.length || 0); - - return { number, country }; -} diff --git a/packages/clerk-js/src/ui.retheme/utils/range.ts b/packages/clerk-js/src/ui.retheme/utils/range.ts deleted file mode 100644 index 5ef7ae1c5e6..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/range.ts +++ /dev/null @@ -1 +0,0 @@ -export const range = (min: number, max: number) => Array.from({ length: max - min + 1 }, (_, i) => min + i); diff --git a/packages/clerk-js/src/ui.retheme/utils/readObjectPath.ts b/packages/clerk-js/src/ui.retheme/utils/readObjectPath.ts deleted file mode 100644 index 02fe67baceb..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/readObjectPath.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const readObjectPath = >(obj: O, path: string) => { - const props = (path || '').split('.'); - let cur = obj; - for (let i = 0; i < props.length; i++) { - cur = cur[props[i]]; - if (cur === undefined) { - return undefined; - } - } - return cur; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/removeUndefinedProps.ts b/packages/clerk-js/src/ui.retheme/utils/removeUndefinedProps.ts deleted file mode 100644 index d46f046079d..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/removeUndefinedProps.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const removeUndefinedProps = (obj: Record) => { - Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key]); - return obj; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/roleLocalizationKey.ts b/packages/clerk-js/src/ui.retheme/utils/roleLocalizationKey.ts deleted file mode 100644 index 3e4d01930dd..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/roleLocalizationKey.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { MembershipRole } from '@clerk/types'; - -import type { LocalizationKey } from '../localization/localizationKeys'; -import { localizationKeys } from '../localization/localizationKeys'; - -const roleToLocalizationKey: Record = { - basic_member: localizationKeys('membershipRole__basicMember'), - guest_member: localizationKeys('membershipRole__guestMember'), - admin: localizationKeys('membershipRole__admin'), -}; - -export const roleLocalizationKey = (role: MembershipRole | undefined): LocalizationKey | undefined => { - if (!role) { - return undefined; - } - return roleToLocalizationKey[role]; -}; - -export const customRoleLocalizationKey = (role: MembershipRole | undefined): LocalizationKey | undefined => { - if (!role) { - return undefined; - } - return localizationKeys(`roles.${role}`); -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/sleep.ts b/packages/clerk-js/src/ui.retheme/utils/sleep.ts deleted file mode 100644 index b35643128c4..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/sleep.ts +++ /dev/null @@ -1 +0,0 @@ -export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); diff --git a/packages/clerk-js/src/ui.retheme/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui.retheme/utils/test/createFixtures.tsx deleted file mode 100644 index 9135864fcaf..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/test/createFixtures.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import type { ClerkOptions, ClientJSON, EnvironmentJSON, LoadedClerk } from '@clerk/types'; -import { jest } from '@jest/globals'; -import React from 'react'; - -import { Clerk as ClerkCtor } from '../../../core/clerk'; -import { Client, Environment } from '../../../core/resources'; -import { ComponentContext, CoreClerkContextWrapper, EnvironmentProvider, OptionsProvider } from '../../contexts'; -import { AppearanceProvider } from '../../customizables'; -import { FlowMetadataProvider } from '../../elements'; -import { RouteContext } from '../../router'; -import { InternalThemeProvider } from '../../styledSystem'; -import { createClientFixtureHelpers, createEnvironmentFixtureHelpers } from './fixtureHelpers'; -import { createBaseClientJSON, createBaseEnvironmentJSON } from './fixtures'; -import { mockClerkMethods, mockRouteContextValue } from './mockHelpers'; - -type UnpackContext = NonNullable ? U : T>; - -const createInitialStateConfigParam = (baseEnvironment: EnvironmentJSON, baseClient: ClientJSON) => { - return { - ...createEnvironmentFixtureHelpers(baseEnvironment), - ...createClientFixtureHelpers(baseClient), - }; -}; - -type FParam = ReturnType; -type ConfigFn = (f: FParam) => void; - -export const bindCreateFixtures = ( - componentName: Parameters[0], - mockOpts?: { - router?: Parameters[0]; - }, -) => { - return { createFixtures: unboundCreateFixtures(componentName, mockOpts) }; -}; - -const unboundCreateFixtures = ['componentName']>( - componentName: N, - mockOpts?: { - router?: Parameters[0]; - }, -) => { - const createFixtures = async (...configFns: ConfigFn[]) => { - const baseEnvironment = createBaseEnvironmentJSON(); - const baseClient = createBaseClientJSON(); - configFns = configFns.filter(Boolean); - - if (configFns.length) { - const fParam = createInitialStateConfigParam(baseEnvironment, baseClient); - configFns.forEach(configFn => configFn(fParam)); - } - - const environmentMock = new Environment(baseEnvironment); - Environment.getInstance().fetch = jest.fn(() => Promise.resolve(environmentMock)); - - // @ts-expect-error - const clientMock = new Client(baseClient); - Client.getInstance().fetch = jest.fn(() => Promise.resolve(clientMock)); - - // Use a FAPI value for local production instances to avoid triggering the devInit flow during testing - const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k'; - const tempClerk = new ClerkCtor(productionPublishableKey); - await tempClerk.load(); - const clerkMock = mockClerkMethods(tempClerk as LoadedClerk); - const optionsMock = {} as ClerkOptions; - const routerMock = mockRouteContextValue(mockOpts?.router || {}); - - const fixtures = { - clerk: clerkMock, - signIn: clerkMock.client.signIn, - signUp: clerkMock.client.signUp, - environment: environmentMock, - router: routerMock, - options: optionsMock, - }; - - let componentContextProps: Partial & { componentName: N }>; - const props = { - setProps: (props: typeof componentContextProps) => { - componentContextProps = props; - }, - }; - - const MockClerkProvider = (props: any) => { - const { children } = props; - return ( - new Map() }} - > - - - - - - - - {children} - - - - - - - - - ); - }; - - return { wrapper: MockClerkProvider, fixtures, props }; - }; - createFixtures.config = (fn: ConfigFn) => fn; - return createFixtures; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui.retheme/utils/test/fixtureHelpers.ts deleted file mode 100644 index de920be73d0..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/test/fixtureHelpers.ts +++ /dev/null @@ -1,430 +0,0 @@ -import type { - ClientJSON, - DisplayConfigJSON, - EmailAddressJSON, - EnvironmentJSON, - ExternalAccountJSON, - OAuthProvider, - OrganizationEnrollmentMode, - PhoneNumberJSON, - PublicUserDataJSON, - SamlAccountJSON, - SessionJSON, - SignInJSON, - SignUpJSON, - UserJSON, - UserSettingsJSON, -} from '@clerk/types'; - -import type { OrgParams } from '../../../core/test/fixtures'; -import { createUser, getOrganizationId } from '../../../core/test/fixtures'; -import { createUserFixture } from './fixtures'; - -export const createEnvironmentFixtureHelpers = (baseEnvironment: EnvironmentJSON) => { - return { - ...createAuthConfigFixtureHelpers(baseEnvironment), - ...createDisplayConfigFixtureHelpers(baseEnvironment), - ...createOrganizationSettingsFixtureHelpers(baseEnvironment), - ...createUserSettingsFixtureHelpers(baseEnvironment), - }; -}; - -export const createClientFixtureHelpers = (baseClient: ClientJSON) => { - return { - ...createSignInFixtureHelpers(baseClient), - ...createSignUpFixtureHelpers(baseClient), - ...createUserFixtureHelpers(baseClient), - }; -}; - -const createUserFixtureHelpers = (baseClient: ClientJSON) => { - type WithUserParams = Omit< - Partial, - 'email_addresses' | 'phone_numbers' | 'external_accounts' | 'saml_accounts' | 'organization_memberships' - > & { - email_addresses?: Array>; - phone_numbers?: Array>; - external_accounts?: Array>; - saml_accounts?: Array>; - organization_memberships?: Array; - }; - - const createPublicUserData = (params: WithUserParams) => { - return { - first_name: 'FirstName', - last_name: 'LastName', - image_url: '', - identifier: 'email@test.com', - user_id: '', - ...params, - } as PublicUserDataJSON; - }; - - const withUser = (params: WithUserParams) => { - baseClient.sessions = baseClient.sessions || []; - - // set the first organization as active - let activeOrganization: string | null = null; - if (params?.organization_memberships?.length) { - activeOrganization = - typeof params.organization_memberships[0] === 'string' - ? params.organization_memberships[0] - : getOrganizationId(params.organization_memberships[0]); - } - - const session = { - status: 'active', - id: baseClient.sessions.length.toString(), - object: 'session', - last_active_organization_id: activeOrganization, - actor: null, - user: createUser(params), - public_user_data: createPublicUserData(params), - created_at: new Date().getTime(), - updated_at: new Date().getTime(), - last_active_token: { - jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzU4NzY3OTAsImRhdGEiOiJmb29iYXIiLCJpYXQiOjE2NzU4NzY3MzB9.Z1BC47lImYvaAtluJlY-kBo0qOoAk42Xb-gNrB2SxJg', - }, - } as SessionJSON; - baseClient.sessions.push(session); - }; - - return { withUser }; -}; - -const createSignInFixtureHelpers = (baseClient: ClientJSON) => { - type SignInWithEmailAddressParams = { - identifier?: string; - supportPassword?: boolean; - supportEmailCode?: boolean; - supportEmailLink?: boolean; - supportResetPassword?: boolean; - }; - - type SignInWithPhoneNumberParams = { - identifier?: string; - supportPassword?: boolean; - supportPhoneCode?: boolean; - supportResetPassword?: boolean; - }; - - type SignInFactorTwoParams = { - identifier?: string; - supportPhoneCode?: boolean; - supportTotp?: boolean; - supportBackupCode?: boolean; - supportResetPasswordEmail?: boolean; - supportResetPasswordPhone?: boolean; - }; - - const startSignInWithEmailAddress = (params?: SignInWithEmailAddressParams) => { - const { - identifier = 'hello@clerk.com', - supportPassword = true, - supportEmailCode, - supportEmailLink, - supportResetPassword, - } = params || {}; - baseClient.sign_in = { - status: 'needs_first_factor', - identifier, - supported_identifiers: ['email_address'], - supported_first_factors: [ - ...(supportPassword ? [{ strategy: 'password' }] : []), - ...(supportEmailCode ? [{ strategy: 'email_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), - ...(supportEmailLink ? [{ strategy: 'email_link', safe_identifier: identifier || 'n*****@clerk.com' }] : []), - ...(supportResetPassword - ? [ - { - strategy: 'reset_password_email_code', - safe_identifier: identifier || 'n*****@clerk.com', - emailAddressId: 'someEmailId', - }, - ] - : []), - ], - user_data: { ...(createUserFixture() as any) }, - } as SignInJSON; - }; - - const startSignInWithPhoneNumber = (params?: SignInWithPhoneNumberParams) => { - const { - identifier = '+301234567890', - supportPassword = true, - supportPhoneCode, - supportResetPassword, - } = params || {}; - baseClient.sign_in = { - status: 'needs_first_factor', - identifier, - supported_identifiers: ['phone_number'], - supported_first_factors: [ - ...(supportPassword ? [{ strategy: 'password' }] : []), - ...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: '+30********90' }] : []), - ...(supportResetPassword - ? [ - { - strategy: 'reset_password_phone_code', - safe_identifier: identifier || '+30********90', - phoneNumberId: 'someNumberId', - }, - ] - : []), - ], - user_data: { ...(createUserFixture() as any) }, - } as SignInJSON; - }; - - const startSignInFactorTwo = (params?: SignInFactorTwoParams) => { - const { - identifier = '+30 691 1111111', - supportPhoneCode = true, - supportTotp, - supportBackupCode, - supportResetPasswordEmail, - supportResetPasswordPhone, - } = params || {}; - baseClient.sign_in = { - status: 'needs_second_factor', - identifier, - ...(supportResetPasswordEmail - ? { - first_factor_verification: { - status: 'verified', - strategy: 'reset_password_email_code', - }, - } - : {}), - ...(supportResetPasswordPhone - ? { - first_factor_verification: { - status: 'verified', - strategy: 'reset_password_phone_code', - }, - } - : {}), - supported_identifiers: ['email_address', 'phone_number'], - supported_second_factors: [ - ...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), - ...(supportTotp ? [{ strategy: 'totp', safe_identifier: identifier || 'n*****@clerk.com' }] : []), - ...(supportBackupCode ? [{ strategy: 'backup_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), - ], - user_data: { ...(createUserFixture() as any) }, - } as SignInJSON; - }; - - return { startSignInWithEmailAddress, startSignInWithPhoneNumber, startSignInFactorTwo }; -}; - -const createSignUpFixtureHelpers = (baseClient: ClientJSON) => { - type SignUpWithEmailAddressParams = { - emailAddress?: string; - supportEmailCode?: boolean; - supportEmailLink?: boolean; - }; - - type SignUpWithPhoneNumberParams = { - phoneNumber?: string; - }; - - const startSignUpWithEmailAddress = (params?: SignUpWithEmailAddressParams) => { - const { emailAddress = 'hello@clerk.com', supportEmailLink = true, supportEmailCode = true } = params || {}; - baseClient.sign_up = { - id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', - status: 'missing_requirements', - email_address: emailAddress, - verifications: (supportEmailLink || supportEmailCode) && { - email_address: { - strategy: (supportEmailLink && 'email_link') || (supportEmailCode && 'email_code'), - }, - }, - } as SignUpJSON; - }; - - const startSignUpWithPhoneNumber = (params?: SignUpWithPhoneNumberParams) => { - const { phoneNumber = '+301234567890' } = params || {}; - baseClient.sign_up = { - id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', - status: 'missing_requirements', - phone_number: phoneNumber, - } as SignUpJSON; - }; - - return { startSignUpWithEmailAddress, startSignUpWithPhoneNumber }; -}; - -const createAuthConfigFixtureHelpers = (environment: EnvironmentJSON) => { - const ac = environment.auth_config; - const withMultiSessionMode = () => { - // TODO: - ac.single_session_mode = false; - }; - return { withMultiSessionMode }; -}; - -const createDisplayConfigFixtureHelpers = (environment: EnvironmentJSON) => { - const dc = environment.display_config; - const withSupportEmail = (opts?: { email: string }) => { - dc.support_email = opts?.email || 'support@clerk.com'; - }; - const withoutClerkBranding = () => { - dc.branded = false; - }; - const withPreferredSignInStrategy = (opts: { strategy: DisplayConfigJSON['preferred_sign_in_strategy'] }) => { - dc.preferred_sign_in_strategy = opts.strategy; - }; - return { withSupportEmail, withoutClerkBranding, withPreferredSignInStrategy }; -}; - -const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) => { - const os = environment.organization_settings; - const withOrganizations = () => { - os.enabled = true; - }; - const withMaxAllowedMemberships = ({ max = 5 }) => { - os.max_allowed_memberships = max; - }; - - const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[]) => { - os.domains.enabled = true; - os.domains.enrollment_modes = modes || ['automatic_invitation', 'automatic_invitation', 'manual_invitation']; - }; - return { withOrganizations, withMaxAllowedMemberships, withOrganizationDomains }; -}; - -const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { - const us = environment.user_settings; - us.password_settings = { - allowed_special_characters: '', - disable_hibp: false, - min_length: 8, - max_length: 999, - require_special_char: false, - require_numbers: false, - require_uppercase: false, - require_lowercase: false, - show_zxcvbn: false, - min_zxcvbn_strength: 0, - }; - const emptyAttribute = { - first_factors: [], - second_factors: [], - verifications: [], - used_for_first_factor: false, - used_for_second_factor: false, - verify_at_sign_up: false, - }; - - const withPasswordComplexity = (opts?: Partial) => { - us.password_settings = { - ...us.password_settings, - ...opts, - }; - }; - - const withEmailAddress = (opts?: Partial) => { - us.attributes.email_address = { - ...emptyAttribute, - enabled: true, - required: false, - used_for_first_factor: true, - first_factors: ['email_code'], - used_for_second_factor: false, - second_factors: [], - verifications: ['email_code'], - verify_at_sign_up: true, - ...opts, - }; - }; - - const withEmailLink = () => { - withEmailAddress({ first_factors: ['email_link'], verifications: ['email_link'] }); - }; - - const withPhoneNumber = (opts?: Partial) => { - us.attributes.phone_number = { - ...emptyAttribute, - enabled: true, - required: false, - used_for_first_factor: true, - first_factors: ['phone_code'], - used_for_second_factor: false, - second_factors: [], - verifications: ['phone_code'], - verify_at_sign_up: true, - ...opts, - }; - }; - - const withUsername = (opts?: Partial) => { - us.attributes.username = { - ...emptyAttribute, - enabled: true, - required: false, - used_for_first_factor: true, - ...opts, - }; - }; - - const withWeb3Wallet = (opts?: Partial) => { - us.attributes.web3_wallet = { - ...emptyAttribute, - enabled: true, - required: false, - used_for_first_factor: true, - first_factors: ['web3_metamask_signature'], - verifications: ['web3_metamask_signature'], - ...opts, - }; - }; - - const withName = (opts?: Partial) => { - const attr = { - ...emptyAttribute, - enabled: true, - required: false, - ...opts, - }; - us.attributes.first_name = attr; - us.attributes.last_name = attr; - }; - - const withPassword = (opts?: Partial) => { - us.attributes.password = { - ...emptyAttribute, - enabled: true, - required: false, - ...opts, - }; - }; - - const withSocialProvider = (opts: { provider: OAuthProvider; authenticatable?: boolean }) => { - const { authenticatable = true, provider } = opts || {}; - const strategy = 'oauth_' + provider; - // @ts-expect-error - us.social[strategy] = { - enabled: true, - authenticatable, - strategy: strategy, - }; - }; - - const withSaml = () => { - us.saml = { enabled: true }; - }; - - // TODO: Add the rest, consult pkg/generate/auth_config.go - - return { - withEmailAddress, - withEmailLink, - withPhoneNumber, - withUsername, - withWeb3Wallet, - withName, - withPassword, - withPasswordComplexity, - withSocialProvider, - withSaml, - }; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/test/fixtures.ts b/packages/clerk-js/src/ui.retheme/utils/test/fixtures.ts deleted file mode 100644 index 586729e07a6..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/test/fixtures.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type { - AuthConfigJSON, - ClientJSON, - DisplayConfigJSON, - EnvironmentJSON, - OrganizationSettingsJSON, - UserJSON, - UserSettingsJSON, -} from '@clerk/types'; - -import { containsAllOfType } from '../containsAllOf'; - -export const createBaseEnvironmentJSON = (): EnvironmentJSON => { - return { - id: 'env_1', - object: 'environment', - auth_config: createBaseAuthConfig(), - display_config: createBaseDisplayConfig(), - organization_settings: createBaseOrganizationSettings(), - user_settings: createBaseUserSettings(), - meta: { responseHeaders: { country: 'us' } }, - }; -}; - -const createBaseAuthConfig = (): AuthConfigJSON => { - return { - object: 'auth_config', - id: 'aac_1', - single_session_mode: true, - }; -}; - -const createBaseDisplayConfig = (): DisplayConfigJSON => { - return { - object: 'display_config', - id: 'display_config_1', - instance_environment_type: 'production', - application_name: 'TestApp', - theme: { - buttons: { - font_color: '#ffffff', - font_family: '"Inter", sans-serif', - font_weight: '600', - }, - general: { - color: '#6c47ff', - padding: '1em', - box_shadow: '0 2px 8px rgba(0, 0, 0, 0.2)', - font_color: '#151515', - font_family: '"Inter", sans-serif', - border_radius: '0.5em', - background_color: '#ffffff', - label_font_weight: '600', - }, - accounts: { - background_color: '#f2f2f2', - }, - }, - preferred_sign_in_strategy: 'password', - logo_image_url: 'https://images.clerk.com/uploaded/img_logo.png', - favicon_image_url: 'https://images.clerk.com/uploaded/img_favicon.png', - home_url: 'https://dashboard.clerk.com', - sign_in_url: 'https://dashboard.clerk.com/sign-in', - sign_up_url: 'https://dashboard.clerk.com/sign-up', - user_profile_url: 'https://accounts.clerk.com/user', - after_sign_in_url: 'https://dashboard.clerk.com', - after_sign_up_url: 'https://dashboard.clerk.com', - after_sign_out_one_url: 'https://accounts.clerk.com/sign-in/choose', - after_sign_out_all_url: 'https://dashboard.clerk.com/sign-in', - after_switch_session_url: 'https://dashboard.clerk.com', - organization_profile_url: 'https://accounts.clerk.com/organization', - create_organization_url: 'https://accounts.clerk.com/create-organization', - after_leave_organization_url: 'https://dashboard.clerk.com', - after_create_organization_url: 'https://dashboard.clerk.com', - support_email: '', - branded: true, - clerk_js_version: '4', - }; -}; - -const createBaseOrganizationSettings = (): OrganizationSettingsJSON => { - return { - enabled: false, - max_allowed_memberships: 5, - domains: { - enabled: false, - enrollment_modes: [], - }, - } as unknown as OrganizationSettingsJSON; -}; - -const attributes = Object.freeze( - containsAllOfType()([ - 'email_address', - 'phone_number', - 'username', - 'web3_wallet', - 'first_name', - 'last_name', - 'password', - 'authenticator_app', - 'backup_code', - ]), -); - -const socials = Object.freeze( - containsAllOfType()([ - 'oauth_facebook', - 'oauth_google', - 'oauth_hubspot', - 'oauth_github', - 'oauth_tiktok', - 'oauth_gitlab', - 'oauth_discord', - 'oauth_twitter', - 'oauth_twitch', - 'oauth_linkedin', - 'oauth_linkedin_oidc', - 'oauth_dropbox', - 'oauth_atlassian', - 'oauth_bitbucket', - 'oauth_microsoft', - 'oauth_notion', - 'oauth_apple', - 'oauth_line', - 'oauth_instagram', - 'oauth_coinbase', - 'oauth_spotify', - 'oauth_xero', - 'oauth_box', - 'oauth_slack', - 'oauth_linear', - ]), -); - -const createBaseUserSettings = (): UserSettingsJSON => { - const attributeConfig = Object.fromEntries( - attributes.map(attribute => [ - attribute, - { - enabled: false, - required: false, - used_for_first_factor: false, - first_factors: [], - used_for_second_factor: false, - second_factors: [], - verifications: [], - verify_at_sign_up: false, - }, - ]), - ) as any as UserSettingsJSON['attributes']; - - const socialConfig = Object.fromEntries( - socials.map(social => [social, { enabled: false, required: false, authenticatable: false, strategy: social }]), - ) as any as UserSettingsJSON['social']; - - const passwordSettingsConfig = { - allowed_special_characters: '', - max_length: 0, - min_length: 8, - require_special_char: false, - require_numbers: false, - require_lowercase: false, - require_uppercase: false, - disable_hibp: true, - show_zxcvbn: false, - min_zxcvbn_strength: 0, - } as UserSettingsJSON['password_settings']; - - return { - attributes: { ...attributeConfig }, - actions: { delete_self: false, create_organization: false }, - social: { ...socialConfig }, - sign_in: { - second_factor: { - required: false, - }, - }, - sign_up: { - custom_action_required: false, - progressive: true, - captcha_enabled: false, - disable_hibp: false, - }, - restrictions: { - allowlist: { - enabled: false, - }, - blocklist: { - enabled: false, - }, - }, - password_settings: passwordSettingsConfig, - }; -}; - -export const createBaseClientJSON = (): ClientJSON => { - return {} as ClientJSON; -}; - -export const createUserFixture = (): UserJSON => { - return { - first_name: 'Firstname', - last_name: 'Lastname', - image_url: - 'https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLTmR2TUtFQzN5cUVpMVFjV0UzQjExbF9WUEVOWW5manlLMlVQd0tCSWw9czEwMDAtYyIsInMiOiJkRkowS3dTSkRINndiODE5cXJTUUxxaWF1ZS9QcHdndC84L0lUUlpYNHpnIn0?width=160', - } as UserJSON; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts deleted file mode 100644 index c509469eff2..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { LoadedClerk } from '@clerk/types'; -import { jest } from '@jest/globals'; - -import type { RouteContextValue } from '../../router'; - -type FunctionLike = (...args: any) => any; - -type DeepJestMocked = T extends FunctionLike - ? jest.Mocked - : T extends object - ? { - [k in keyof T]: DeepJestMocked; - } - : T; - -const mockProp = (obj: T, k: keyof T) => { - if (typeof obj[k] === 'function') { - // @ts-ignore - obj[k] = jest.fn(); - } -}; - -const mockMethodsOf = | null = any>(obj: T, options?: { exclude: (keyof T)[] }) => { - if (!obj) { - return; - } - Object.keys(obj) - .filter(key => !options?.exclude.includes(key as keyof T)) - .forEach(k => mockProp(obj, k)); -}; - -export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked => { - mockMethodsOf(clerk); - mockMethodsOf(clerk.client.signIn); - mockMethodsOf(clerk.client.signUp); - clerk.client.sessions.forEach(session => { - mockMethodsOf(session, { - exclude: ['checkAuthorization'], - }); - mockMethodsOf(session.user); - session.user?.emailAddresses.forEach(m => mockMethodsOf(m)); - session.user?.phoneNumbers.forEach(m => mockMethodsOf(m)); - session.user?.externalAccounts.forEach(m => mockMethodsOf(m)); - session.user?.organizationMemberships.forEach(m => { - mockMethodsOf(m); - mockMethodsOf(m.organization); - }); - }); - mockProp(clerk, 'navigate'); - mockProp(clerk, 'setActive'); - mockProp(clerk, '__internal_navigateWithError'); - return clerk as any as DeepJestMocked; -}; - -export const mockRouteContextValue = ({ queryString = '' }: Partial>) => { - return { - basePath: '', - startPath: '', - flowStartPath: '', - fullPath: '', - indexPath: '', - currentPath: '', - queryString, - queryParams: {}, - getMatchData: jest.fn(), - matches: jest.fn(), - baseNavigate: jest.fn(), - navigate: jest.fn(), - resolve: jest.fn((to: string) => new URL(to, 'https://clerk.com')), - refresh: jest.fn(), - params: {}, - } as RouteContextValue; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/test/runFakeTimers.ts b/packages/clerk-js/src/ui.retheme/utils/test/runFakeTimers.ts deleted file mode 100644 index 5fda44aeca3..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/test/runFakeTimers.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { jest } from '@jest/globals'; -import { act } from '@testing-library/react'; - -type WithAct = (fn: T) => T; -const withAct = ((fn: any) => - (...args: any) => { - act(() => { - fn(...args); - }); - }) as WithAct; - -const advanceTimersByTime = withAct(jest.advanceTimersByTime.bind(jest)); -const runAllTimers = withAct(jest.runAllTimers.bind(jest)); -const runOnlyPendingTimers = withAct(jest.runOnlyPendingTimers.bind(jest)); - -const createFakeTimersHelpers = () => { - return { advanceTimersByTime, runAllTimers, runOnlyPendingTimers }; -}; - -type FakeTimersHelpers = ReturnType; -type RunFakeTimersCallback = (timers: FakeTimersHelpers) => void | Promise; - -export const runFakeTimers = >( - cb: T, -): R extends Promise ? Promise : void => { - jest.useFakeTimers(); - const res = cb(createFakeTimersHelpers()); - if (res && 'then' in res) { - // @ts-expect-error - return res.finally(() => jest.useRealTimers()); - } - jest.useRealTimers(); - // @ts-ignore - return; -}; diff --git a/packages/clerk-js/src/ui.retheme/utils/useFormControl.ts b/packages/clerk-js/src/ui.retheme/utils/useFormControl.ts deleted file mode 100644 index 52f8200e9eb..00000000000 --- a/packages/clerk-js/src/ui.retheme/utils/useFormControl.ts +++ /dev/null @@ -1,207 +0,0 @@ -import type { ClerkAPIError } from '@clerk/types'; -import type { HTMLInputTypeAttribute } from 'react'; -import { useState } from 'react'; - -import { useDebounce } from '../hooks'; -import type { LocalizationKey } from '../localization'; -import { useLocalizations } from '../localization'; - -type SelectOption = { value: string; label?: string }; - -type Options = { - isRequired?: boolean; - placeholder?: string | LocalizationKey; - options?: SelectOption[]; - defaultChecked?: boolean; - infoText?: LocalizationKey | string; -} & ( - | { - label: string | LocalizationKey; - validatePassword?: never; - buildErrorMessage?: never; - type?: Exclude; - radioOptions?: never; - } - | { - label: string | LocalizationKey; - type: Extract; - validatePassword: boolean; - buildErrorMessage?: (err: ClerkAPIError[]) => ClerkAPIError | string | undefined; - radioOptions?: never; - } - | { - validatePassword?: never; - buildErrorMessage?: never; - type: Extract; - label?: string | LocalizationKey; - radioOptions: { - value: string; - label: string | LocalizationKey; - description?: string | LocalizationKey; - }[]; - } -); - -type FieldStateProps = { - id: Id; - name: Id; - value: string; - checked?: boolean; - onChange: React.ChangeEventHandler; - onBlur: React.FocusEventHandler; - onFocus: React.FocusEventHandler; - feedback: string; - feedbackType: FeedbackType; - setError: (error: string | ClerkAPIError | undefined) => void; - setWarning: (warning: string) => void; - setSuccess: (message: string) => void; - setInfo: (info: string) => void; - setHasPassedComplexity: (b: boolean) => void; - clearFeedback: () => void; - hasPassedComplexity: boolean; - isFocused: boolean; -} & Omit; - -export type FormControlState = FieldStateProps & { - setError: (error: string | ClerkAPIError | undefined) => void; - setSuccess: (message: string) => void; - setInfo: (info: string) => void; - setValue: (val: string | undefined) => void; - setChecked: (isChecked: boolean) => void; - clearFeedback: () => void; - props: FieldStateProps; -}; - -export type FeedbackType = 'success' | 'error' | 'warning' | 'info'; - -export const useFormControl = ( - id: Id, - initialState: string, - opts?: Options, -): FormControlState => { - opts = opts || { - type: 'text', - label: '', - isRequired: false, - placeholder: '', - options: [], - defaultChecked: false, - }; - - const { translateError, t } = useLocalizations(); - const [value, setValueInternal] = useState(initialState); - const [isFocused, setFocused] = useState(false); - const [checked, setCheckedInternal] = useState(opts?.defaultChecked || false); - const [hasPassedComplexity, setHasPassedComplexity] = useState(false); - const [feedback, setFeedback] = useState<{ message: string; type: FeedbackType }>({ - message: '', - type: 'info', - }); - - const onChange: FormControlState['onChange'] = event => { - if (opts?.type === 'checkbox') { - return setCheckedInternal(event.target.checked); - } - return setValueInternal(event.target.value || ''); - }; - - const setValue: FormControlState['setValue'] = val => setValueInternal(val || ''); - const setChecked: FormControlState['setChecked'] = checked => setCheckedInternal(checked); - const setError: FormControlState['setError'] = error => { - if (error) { - setFeedback({ message: translateError(error), type: 'error' }); - } - }; - const setSuccess: FormControlState['setSuccess'] = message => { - if (message) { - setFeedback({ message, type: 'success' }); - } - }; - - const setWarning: FormControlState['setWarning'] = warning => { - if (warning) { - setFeedback({ message: translateError(warning), type: 'warning' }); - } - }; - - const setInfo: FormControlState['setInfo'] = info => { - if (info) { - setFeedback({ message: info, type: 'info' }); - } - }; - - const clearFeedback: FormControlState['clearFeedback'] = () => { - setFeedback({ message: '', type: 'info' }); - }; - - const onFocus: FormControlState['onFocus'] = () => { - setFocused(true); - }; - - const onBlur: FormControlState['onBlur'] = () => { - setFocused(false); - }; - - const { defaultChecked, validatePassword: validatePasswordProp, buildErrorMessage, ...restOpts } = opts; - - const props = { - id, - name: id, - value, - checked, - setSuccess, - setError, - onChange, - onBlur, - onFocus, - setWarning, - feedback: feedback.message || t(opts.infoText), - feedbackType: feedback.type, - setInfo, - clearFeedback, - hasPassedComplexity, - setHasPassedComplexity, - validatePassword: opts.type === 'password' ? opts.validatePassword : undefined, - isFocused, - ...restOpts, - }; - - return { props, ...props, buildErrorMessage, setError, setValue, setChecked }; -}; - -type FormControlStateLike = Pick; - -export const buildRequest = (fieldStates: Array): Record => { - const request: { [x: string]: any } = {}; - fieldStates.forEach(x => { - request[x.id] = x.value; - }); - return request; -}; - -type DebouncedFeedback = { - debounced: { - feedback: string; - feedbackType: FeedbackType; - }; -}; - -type DebouncingOption = { - feedback?: string; - feedbackType?: FeedbackType; - isFocused?: boolean; - delayInMs?: number; -}; -export const useFormControlFeedback = (opts?: DebouncingOption): DebouncedFeedback => { - const { feedback = '', delayInMs = 100, feedbackType = 'info', isFocused = false } = opts || {}; - const shouldHide = isFocused ? false : ['info', 'warning'].includes(feedbackType); - - const debouncedState = useDebounce( - { feedback: shouldHide ? '' : feedback, feedbackType: shouldHide ? 'info' : feedbackType }, - delayInMs, - ); - - return { - debounced: debouncedState, - }; -}; diff --git a/packages/clerk-js/src/ui/common/BlockButtons.tsx b/packages/clerk-js/src/ui/common/BlockButtons.tsx index 21edca4a608..615c041cb62 100644 --- a/packages/clerk-js/src/ui/common/BlockButtons.tsx +++ b/packages/clerk-js/src/ui/common/BlockButtons.tsx @@ -21,7 +21,6 @@ export const AddBlockButton = (props: BlockButtonProps) => { const { leftIcon, ...rest } = props; return ( ({ justifyContent: 'flex-start', gap: theme.space.$2 })} leftIcon={ diff --git a/packages/clerk-js/src/ui/common/CalloutWithAction.tsx b/packages/clerk-js/src/ui/common/CalloutWithAction.tsx index 76077ed37d9..a10622dc91b 100644 --- a/packages/clerk-js/src/ui/common/CalloutWithAction.tsx +++ b/packages/clerk-js/src/ui/common/CalloutWithAction.tsx @@ -37,12 +37,7 @@ export const CalloutWithAction = (props: PropsWithChildren ({ - lineHeight: t.lineHeights.$base, - }), - textSx, - ]} + sx={textSx} localizationKey={text} > {props.children} @@ -51,7 +46,7 @@ export const CalloutWithAction = (props: PropsWithChildren diff --git a/packages/clerk-js/src/ui/common/EmailLinkStatusCard.tsx b/packages/clerk-js/src/ui/common/EmailLinkStatusCard.tsx index 953a707026b..28448344ecf 100644 --- a/packages/clerk-js/src/ui/common/EmailLinkStatusCard.tsx +++ b/packages/clerk-js/src/ui/common/EmailLinkStatusCard.tsx @@ -61,13 +61,13 @@ const StatusRow = (props: { status: VerificationStatus }) => { size='xl' colorScheme='primary' sx={theme => ({ margin: `${theme.space.$12} 0` })} + elementDescriptor={descriptors.spinner} /> ) : ( <> diff --git a/packages/clerk-js/src/ui/common/InfiniteListSpinner.tsx b/packages/clerk-js/src/ui/common/InfiniteListSpinner.tsx index 1a359c4ebe6..97ae637e73f 100644 --- a/packages/clerk-js/src/ui/common/InfiniteListSpinner.tsx +++ b/packages/clerk-js/src/ui/common/InfiniteListSpinner.tsx @@ -1,6 +1,6 @@ import { forwardRef } from 'react'; -import { Box, Spinner } from '../customizables'; +import { Box, descriptors, Spinner } from '../customizables'; export const InfiniteListSpinner = forwardRef((_, ref) => { return ( @@ -24,6 +24,7 @@ export const InfiniteListSpinner = forwardRef((_, ref) => { diff --git a/packages/clerk-js/src/ui/common/NotificationCountBadge.tsx b/packages/clerk-js/src/ui/common/NotificationCountBadge.tsx index 2bda7db3c25..a3b609a0554 100644 --- a/packages/clerk-js/src/ui/common/NotificationCountBadge.tsx +++ b/packages/clerk-js/src/ui/common/NotificationCountBadge.tsx @@ -3,13 +3,12 @@ import { useDelayedVisibility, usePrefersReducedMotion } from '../hooks'; import type { ThemableCssProp } from '../styledSystem'; import { animations } from '../styledSystem'; -export const NotificationCountBadge = ({ - notificationCount, - containerSx, -}: { +type NotificationCountBadgeProps = { notificationCount: number; containerSx?: ThemableCssProp; -}) => { +}; + +export const NotificationCountBadge = ({ notificationCount, containerSx }: NotificationCountBadgeProps) => { const prefersReducedMotion = usePrefersReducedMotion(); const showNotification = useDelayedVisibility(notificationCount > 0, 350) || false; @@ -25,9 +24,11 @@ export const NotificationCountBadge = ({ ({ - position: 'relative', - width: t.sizes.$4, - height: t.sizes.$4, + position: 'absolute', + top: `-${t.space.$2}`, + right: `-${t.space.$1}`, + width: t.sizes.$2, + height: t.sizes.$2, }), containerSx, ]} diff --git a/packages/clerk-js/src/ui/common/RemoveResourcePage.tsx b/packages/clerk-js/src/ui/common/RemoveResourcePage.tsx index 3eb81b00b2d..c95d9dfe864 100644 --- a/packages/clerk-js/src/ui/common/RemoveResourcePage.tsx +++ b/packages/clerk-js/src/ui/common/RemoveResourcePage.tsx @@ -37,15 +37,9 @@ export const RemoveResourcePage = withCardStateProvider((props: RemovePageProps) Breadcrumbs={props.Breadcrumbs} > - - - + + + { - describe('normalizeRoutingOptions', () => { - it("returns routing: 'path' if path was provided and no routing was provided", () => { - expect(normalizeRoutingOptions({ path: 'test' })).toEqual({ routing: 'path', path: 'test' }); - }); - - it('it throws an error when path is provided and routing strategy is not path', () => { - expect(() => { - normalizeRoutingOptions({ path: 'test', routing: 'hash' }); - }).toThrow('ClerkJS: Invalid routing strategy, path cannot be used in tandem with hash.'); - expect(() => { - normalizeRoutingOptions({ path: 'test', routing: 'virtual' }); - }).toThrow('ClerkJS: Invalid routing strategy, path cannot be used in tandem with virtual.'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx b/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx index 89c253927d1..8dd9d54f6a0 100644 --- a/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx +++ b/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { bindCreateFixtures, render, screen } from '../../../testUtils'; +import { render, screen } from '../../../testUtils'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { withRedirectToHomeOrganizationGuard, withRedirectToHomeSingleSessionGuard, diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx index 6ad0557d02d..62959279bfd 100644 --- a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx +++ b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx @@ -104,8 +104,8 @@ export const CreateOrganizationForm = (props: CreateOrganizationFormProps) => { slugField.setValue(val); }; - const headerTitleTextVariant = props.flow === 'organizationList' ? 'xlargeMedium' : undefined; - const headerSubtitleTextVariant = props.flow === 'organizationList' ? 'headingRegularRegular' : undefined; + const headerTitleTextVariant = props.flow === 'organizationList' ? 'h2' : undefined; + const headerSubtitleTextVariant = props.flow === 'organizationList' ? 'subtitle' : undefined; return ( @@ -125,7 +125,6 @@ export const CreateOrganizationForm = (props: CreateOrganizationFormProps) => { avatarPreviewPlaceholder={ { /> } sx={theme => ({ - width: theme.sizes.$11, - height: theme.sizes.$11, + width: theme.sizes.$12, + height: theme.sizes.$12, borderRadius: theme.radii.$md, backgroundColor: theme.colors.$avatarBackground, ':hover': { diff --git a/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx index d5e85e94646..8b1010caca0 100644 --- a/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx +++ b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx @@ -73,12 +73,12 @@ const FabContent = ({ title, signOutText }: FabContentProps) => { ({ alignSelf: 'flex-start', diff --git a/packages/clerk-js/src/ui/components/OrganizationList/OrganizationListPage.tsx b/packages/clerk-js/src/ui/components/OrganizationList/OrganizationListPage.tsx index c94da2f10af..3abb84084ee 100644 --- a/packages/clerk-js/src/ui/components/OrganizationList/OrganizationListPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationList/OrganizationListPage.tsx @@ -68,6 +68,7 @@ export const OrganizationListPage = withCardStateProvider(() => { )} @@ -204,10 +205,8 @@ const OrganizationListPageList = (props: { onCreateOrganizationClick: () => void ); diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OtherOrganizationActions.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OtherOrganizationActions.tsx index 4897b785dbe..d5c4c0e0b72 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OtherOrganizationActions.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OtherOrganizationActions.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { descriptors, localizationKeys } from '../../customizables'; import { Action, SecondaryActions } from '../../elements'; -import { Plus } from '../../icons'; +import { Add } from '../../icons'; import { UserInvitationSuggestionList } from './UserInvitationSuggestionList'; import type { UserMembershipListProps } from './UserMembershipList'; import { UserMembershipList } from './UserMembershipList'; @@ -29,11 +29,19 @@ const CreateOrganizationButton = ({ iconBoxElementId={descriptors.organizationSwitcherPopoverActionButtonIconBox.setId('createOrganization')} iconElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIcon} iconElementId={descriptors.organizationSwitcherPopoverActionButtonIcon.setId('createOrganization')} - textElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonText} - textElementId={descriptors.organizationSwitcherPopoverActionButtonText.setId('createOrganization')} - icon={Plus} + icon={Add} label={localizationKeys('organizationSwitcher.action__createOrganization')} onClick={onCreateOrganizationClick} + sx={t => ({ + color: t.colors.$blackAlpha600, + ':hover': { + color: t.colors.$blackAlpha600, + }, + })} + iconSx={t => ({ + width: t.sizes.$9, + height: t.sizes.$6, + })} /> ); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx index fd7ca0e25f2..323038562e2 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx @@ -51,7 +51,6 @@ const AcceptRejectSuggestionButtons = (props: OrganizationSuggestionResource) => if (props.status === 'accepted') { return ( @@ -61,9 +60,8 @@ const AcceptRejectSuggestionButtons = (props: OrganizationSuggestionResource) => return ( ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/ActiveDevicesSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/ActiveDevicesSection.tsx index 9a7976ac1ad..2fda4613c58 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ActiveDevicesSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ActiveDevicesSection.tsx @@ -62,7 +62,7 @@ const DeviceAccordion = (props: { session: SessionWithActivitiesResource }) => { {!isCurrent && ( { center gap={2} > - {title} + {title} {isCurrent && ( { /> )} - - {browser} - - + {browser} + {ipAddress} ({location}) - - {t(getRelativeToNowDateKey(props.session.lastActiveAt))} - + {t(getRelativeToNowDateKey(props.session.lastActiveAt))} ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx b/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx index 3a49d7f340b..e5bfbf9e501 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx @@ -106,7 +106,7 @@ export const AddAuthenticatorApp = (props: AddAuthenticatorAppProps) => { ); diff --git a/packages/clerk-js/src/ui/elements/Alert.tsx b/packages/clerk-js/src/ui/elements/Alert.tsx index 80e46b02c3a..658f9ae8f9e 100644 --- a/packages/clerk-js/src/ui/elements/Alert.tsx +++ b/packages/clerk-js/src/ui/elements/Alert.tsx @@ -42,7 +42,7 @@ export const Alert = (props: AlertProps): JSX.Element | null => { elementDescriptor={descriptors.alertText} elementId={descriptors.alert.setId(variant)} colorScheme='neutral' - variant='smallRegular' + variant='body' localizationKey={title} > {children} @@ -52,7 +52,7 @@ export const Alert = (props: AlertProps): JSX.Element | null => { elementDescriptor={descriptors.alertText} elementId={descriptors.alert.setId(variant)} colorScheme='neutral' - variant='smallRegular' + variant='body' localizationKey={subtitle} /> )} diff --git a/packages/clerk-js/src/ui/elements/ArrowBlockButton.tsx b/packages/clerk-js/src/ui/elements/ArrowBlockButton.tsx index 6f98aeed51d..4f463fe93c5 100644 --- a/packages/clerk-js/src/ui/elements/ArrowBlockButton.tsx +++ b/packages/clerk-js/src/ui/elements/ArrowBlockButton.tsx @@ -48,8 +48,7 @@ export const ArrowBlockButton = (props: ArrowBlockButtonProps) => { return ( { elementId={textElementId} as='span' truncate - colorScheme='inherit' - variant='buttonSmallRegular' + variant='buttonSmall' localizationKey={textLocalizationKey} > {children} diff --git a/packages/clerk-js/src/ui/elements/AvatarUploader.tsx b/packages/clerk-js/src/ui/elements/AvatarUploader.tsx index 38b488afc8e..4399dbefd6e 100644 --- a/packages/clerk-js/src/ui/elements/AvatarUploader.tsx +++ b/packages/clerk-js/src/ui/elements/AvatarUploader.tsx @@ -72,7 +72,7 @@ export const AvatarUploader = (props: AvatarUploaderProps) => { & { boxElementDescriptor?: ElementDescriptor; linkElementDescriptor?: ElementDescriptor; - iconElementDescriptor?: ElementDescriptor; }; export const BackLink = (props: BackLinkProps) => { - const { boxElementDescriptor, linkElementDescriptor, iconElementDescriptor, ...rest } = props; + const { boxElementDescriptor, linkElementDescriptor, ...rest } = props; return ( ({ marginBottom: theme.space.$2x5 })} + sx={theme => ({ margin: `${theme.space.$none} auto` })} > - diff --git a/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx b/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx index f7895c445fd..cf7435a93ea 100644 --- a/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx +++ b/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx @@ -56,8 +56,7 @@ export const BlockWithTrailingComponent = (props: BlockWithTrailingComponentProp elementId={textElementId} as='span' truncate - colorScheme='inherit' - variant='buttonSmallRegular' + variant='buttonSmall' localizationKey={textLocalizationKey} > {children} diff --git a/packages/clerk-js/src/ui/elements/Card.tsx b/packages/clerk-js/src/ui/elements/Card.tsx index 1256f940854..4a78f116b25 100644 --- a/packages/clerk-js/src/ui/elements/Card.tsx +++ b/packages/clerk-js/src/ui/elements/Card.tsx @@ -12,10 +12,13 @@ import { IconButton } from './IconButton'; import { useUnsafeModalContext } from './Modal'; import { PoweredByClerkTag } from './PoweredByClerk'; -type CardProps = PropsOfComponent & React.PropsWithChildren>; +type CardProps = PropsOfComponent & + React.PropsWithChildren<{ + footerItems?: React.ReactNode[]; + }>; export const Card = React.forwardRef((props, ref) => { - const { sx, children, ...rest } = props; + const { sx, children, footerItems, ...rest } = props; const appearance = useAppearance(); const flowMetadata = useFlowMetadata(); const { branded } = useEnvironment().displayConfig; @@ -25,7 +28,6 @@ export const Card = React.forwardRef((props, ref) => {appearance.parsedLayout.logoPlacement === 'outside' && ( ({ - margin: branded ? `0 ${t.space.$7} ${t.space.$8} ${t.space.$7}` : undefined, [mqu.sm]: { margin: `0 0 ${t.space.$7} 0`, }, @@ -33,31 +35,41 @@ export const Card = React.forwardRef((props, ref) => /> )} ({ - width: t.sizes.$100, - maxWidth: `calc(100vw - ${t.sizes.$20})`, - margin: branded ? `0 ${t.space.$7}` : undefined, - [mqu.sm]: { - maxWidth: `calc(100vw - ${t.sizes.$3})`, - margin: branded ? `0 0 ${t.space.$7} 0` : '0', - }, - padding: `${t.space.$9x5} ${t.space.$8} ${t.space.$12} ${t.space.$8}`, - [mqu.xs]: { - padding: `${t.space.$8} ${t.space.$5} ${t.space.$10} ${t.space.$5}`, - }, - }), - sx, - ]} ref={ref} > - {appearance.parsedLayout.logoPlacement === 'inside' && } - {children} - {branded && } + ({ + zIndex: t.zIndices.$card, + position: 'relative', + backgroundColor: t.colors.$colorBackground, + padding: t.space.$8, + boxShadow: t.shadows.$cardShadow, + maxWidth: `calc(100vw - ${t.sizes.$20})`, + width: t.sizes.$100, + borderRadius: `${t.radii.$card} ${t.radii.$card} ${t.radii.$lg} ${t.radii.$lg}`, + }), + sx, + ]} + {...rest} + > + {appearance.parsedLayout.logoPlacement === 'inside' && } + {children} + + + {footerItems?.map((item, index) => ( + {item} + ))} + {branded && ( + + + + )} + ); @@ -65,28 +77,19 @@ export const Card = React.forwardRef((props, ref) => export const ProfileCard = React.forwardRef((props, ref) => { const { sx, children, ...rest } = props; - const { branded } = useEnvironment().displayConfig; return ( ({ - padding: 0, width: t.sizes.$220, maxWidth: `calc(100vw - ${t.sizes.$20})`, - margin: branded ? `0 ${t.space.$7}` : undefined, - [mqu.sm]: { - maxWidth: `calc(100vw - ${t.sizes.$3})`, - margin: branded ? `0 0 ${t.space.$7} 0` : '0', - }, }), sx, ]} {...rest} - ref={ref} > {children} - {branded && } ); }); @@ -106,13 +109,15 @@ export const BaseCard = React.forwardRef((props, elementDescriptor={[descriptors.card, props.elementDescriptor as ElementDescriptor]} sx={[ t => ({ + background: `linear-gradient(${t.colors.$blackAlpha100},${t.colors.$blackAlpha100}), linear-gradient(${t.colors.$colorBackground}, ${t.colors.$colorBackground})`, + overflow: 'hidden', willChange: 'transform, opacity, height', - borderRadius: t.radii.$xl, - backgroundColor: t.colors.$colorBackground, transitionProperty: t.transitionProperty.$common, transitionDuration: '200ms', - boxShadow: t.shadows.$cardDropShadow, - border: '1px solid transparent', + borderRadius: t.radii.$xl, + boxShadow: + '0px 5px 15px 0px rgba(0, 0, 0, 0.08), 0px 15px 35px -5px rgba(25, 28, 33, 0.20), 0px 0px 0px 1px rgba(25, 28, 33, 0.06)', + backdropFilter: 'blur(10px)', }), sx, ]} @@ -122,7 +127,6 @@ export const BaseCard = React.forwardRef((props, ((props, /> } sx={t => ({ + color: t.colors.$colorTextTertiary, zIndex: t.zIndices.$modal, - opacity: t.opacity.$inactive, - ':hover': { - opacity: 0.8, - }, position: 'absolute', - top: t.space.$3, + top: t.space.$none, + right: t.space.$none, padding: t.space.$3, - right: t.space.$3, })} /> )} @@ -148,3 +149,51 @@ export const BaseCard = React.forwardRef((props, ); }); + +type CardFooterProps = PropsOfComponent; +export const CardFooter = React.forwardRef((props, ref) => { + return ( + ({ + '>:first-of-type': { + padding: `${t.space.$6} ${t.space.$2} ${t.space.$4} ${t.space.$2}`, + marginTop: `-${t.space.$2}`, + }, + '>:not(:first-of-type)': { + padding: `${t.space.$4} ${t.space.$2}`, + borderTop: t.borders.$normal, + borderColor: t.colors.$blackAlpha100, + }, + })} + {...props} + ref={ref} + /> + ); +}); + +type CardFooterItemProps = PropsOfComponent; +export const CardFooterItem = React.forwardRef((props, ref) => { + const { sx, ...rest } = props; + + return ( + ({ + position: 'relative', + width: '100%', + backgroundColor: t.colors.$blackAlpha50, + }), + ]} + {...rest} + ref={ref} + /> + ); +}); diff --git a/packages/clerk-js/src/ui/elements/ClipboardInput.tsx b/packages/clerk-js/src/ui/elements/ClipboardInput.tsx index 44283216051..84ea5d9c3e0 100644 --- a/packages/clerk-js/src/ui/elements/ClipboardInput.tsx +++ b/packages/clerk-js/src/ui/elements/ClipboardInput.tsx @@ -22,9 +22,8 @@ export const ClipboardInput = (props: PropsOfComponent) => { ); diff --git a/packages/clerk-js/src/ui/elements/SocialButtons.tsx b/packages/clerk-js/src/ui/elements/SocialButtons.tsx index a267a383a1a..a85752e525f 100644 --- a/packages/clerk-js/src/ui/elements/SocialButtons.tsx +++ b/packages/clerk-js/src/ui/elements/SocialButtons.tsx @@ -1,14 +1,30 @@ import type { OAuthProvider, OAuthStrategy, Web3Provider, Web3Strategy } from '@clerk/types'; -import React from 'react'; - -import { Button, descriptors, Grid, Image, localizationKeys, useAppearance } from '../customizables'; -import { useEnabledThirdPartyProviders } from '../hooks'; +import type { Ref } from 'react'; +import React, { forwardRef, isValidElement } from 'react'; + +import type { LocalizationKey } from '../customizables'; +import { + Button, + descriptors, + Flex, + Grid, + Icon, + Image, + localizationKeys, + SimpleButton, + Spinner, + Text, + useAppearance, +} from '../customizables'; +import { useEnabledThirdPartyProviders, useResizeObserver } from '../hooks'; import type { PropsOfComponent } from '../styledSystem'; import { sleep } from '../utils'; -import { ArrowBlockButton } from './ArrowBlockButton'; import { useCardState } from './contexts'; +import { distributeStrategiesIntoRows } from './utils'; const SOCIAL_BUTTON_BLOCK_THRESHOLD = 2; +const SOCIAL_BUTTON_PRE_TEXT_THRESHOLD = 1; +const MAX_STRATEGIES_PER_ROW = 6; export type SocialButtonsProps = React.PropsWithChildren<{ enableOAuthProviders: boolean; @@ -29,6 +45,7 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { const { web3Strategies, authenticatableOauthStrategies, strategyToDisplayData } = useEnabledThirdPartyProviders(); const card = useCardState(); const { socialButtonsVariant } = useAppearance().parsedLayout; + const [firstStrategyRef, firstElementRect] = useResizeObserver(); const strategies = [ ...(enableOAuthProviders ? authenticatableOauthStrategies : []), @@ -39,6 +56,8 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { return null; } + const strategyRows = distributeStrategiesIntoRows([...strategies], MAX_STRATEGIES_PER_ROW); + const preferBlockButtons = socialButtonsVariant === 'blockButton' ? true @@ -63,112 +82,169 @@ export const SocialButtons = React.memo((props: SocialButtonsRootProps) => { }; const ButtonElement = preferBlockButtons ? SocialButtonBlock : SocialButtonIcon; - const WrapperElement = preferBlockButtons ? ButtonRows : ButtonGrid; - - return ( - - {strategies.map(strategy => ( - ({ width: theme.sizes.$5, height: 'auto', maxWidth: '100%' })} - /> - } - /> - ))} - - ); -}); -const ButtonGrid = (props: React.PropsWithChildren) => { return ( - ({ - gridTemplateColumns: `repeat(auto-fit, minmax(${t.sizes.$12}, 1fr))`, - gridAutoRows: t.sizes.$12, - })} > - {props.children} - - ); -}; - -const ButtonRows = (props: React.PropsWithChildren) => { - return ( - - {props.children} - + {strategyRows.map((row, rowIndex) => ( + + {row.map((strategy, strategyIndex) => { + const label = + strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD + ? `Continue with ${strategyToDisplayData[strategy].name}` + : strategyToDisplayData[strategy].name; + + const localizedText = + strategies.length === SOCIAL_BUTTON_PRE_TEXT_THRESHOLD + ? localizationKeys('socialButtonsBlockButton', { + provider: strategyToDisplayData[strategy].name, + }) + : undefined; + + // When strategies break into 2 rows or more, use the first item of the first + // row as reference for the width of the buttons in the second row and beyond + const ref = + strategies.length > MAX_STRATEGIES_PER_ROW && rowIndex === 0 && strategyIndex === 0 + ? firstStrategyRef + : null; + + return ( + ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })} + /> + } + /> + ); + })} + + ))} + ); -}; +}); type SocialButtonProps = PropsOfComponent & { icon: React.ReactElement; id: OAuthProvider | Web3Provider; - providerName: string; + textLocalizationKey: LocalizationKey | undefined; label?: string; }; -const SocialButtonIcon = (props: SocialButtonProps): JSX.Element => { - const { icon, label, id, providerName, ...rest } = props; +const SocialButtonIcon = forwardRef((props: SocialButtonProps, ref: Ref | null): JSX.Element => { + const { icon, label, id, textLocalizationKey, ...rest } = props; + return ( ); -}; +}); const SocialButtonBlock = (props: SocialButtonProps): JSX.Element => { - const { label, id, providerName, sx, icon, ...rest } = props; + const { id, icon, isLoading, label, textLocalizationKey, ...rest } = props; + const isIconElement = isValidElement(icon); return ( - [ { - textOverflow: 'ellipsis', - overflow: 'hidden', + gap: theme.space.$4, + position: 'relative', + justifyContent: 'flex-start', + borderColor: theme.colors.$blackAlpha200, }, - sx, + props.sx, ]} - {...rest} > - {label} - + + {(isLoading || icon) && ( + ({ flex: `0 0 ${theme.space.$4}` })} + > + {isLoading ? ( + + ) : !isIconElement && icon ? ( + ({ + color: theme.colors.$blackAlpha600, + width: theme.sizes.$4, + position: 'absolute', + }), + ]} + /> + ) : ( + icon + )} + + )} + + {label} + + + ); }; diff --git a/packages/clerk-js/src/ui/elements/SuccessPage.tsx b/packages/clerk-js/src/ui/elements/SuccessPage.tsx index a97449e4f0d..53702a29382 100644 --- a/packages/clerk-js/src/ui/elements/SuccessPage.tsx +++ b/packages/clerk-js/src/ui/elements/SuccessPage.tsx @@ -28,7 +28,6 @@ export const SuccessPage = (props: SuccessPageProps) => { ({ display: 'inline', ':not(:last-of-type)': { @@ -38,17 +37,13 @@ export const SuccessPage = (props: SuccessPageProps) => { /> )) ) : ( - + )} {contents} { overflow: 'hidden', })} > - - {children} - + {children} { opacity: 1, }, })} - colorScheme='neutral' variant='ghost' > diff --git a/packages/clerk-js/src/ui/elements/TileButton.tsx b/packages/clerk-js/src/ui/elements/TileButton.tsx index 27c64288afa..cb53957bdf5 100644 --- a/packages/clerk-js/src/ui/elements/TileButton.tsx +++ b/packages/clerk-js/src/ui/elements/TileButton.tsx @@ -8,8 +8,7 @@ export const TileButton = (props: PropsOfComponent & { icon: Reac return ( ); }); @@ -214,10 +292,9 @@ const SimpleButton = React.forwardRef((props, re // Explicitly remove type=submit or type=button // to prevent global css resets (eg tailwind) from affecting // the default styles of our components - // eslint-disable-next-line react/button-has-type type={undefined} onClick={onClick} - css={applyVariants(parsedProps)} + css={applyVariants(parsedProps) as any} disabled={isDisabled} ref={ref} > diff --git a/packages/clerk-js/src/ui/primitives/Flex.tsx b/packages/clerk-js/src/ui/primitives/Flex.tsx index aea983ee5e9..40f4c08fe50 100644 --- a/packages/clerk-js/src/ui/primitives/Flex.tsx +++ b/packages/clerk-js/src/ui/primitives/Flex.tsx @@ -64,7 +64,7 @@ export const Flex = React.forwardRef((props, ref) => return ( ); diff --git a/packages/clerk-js/src/ui/primitives/FormErrorText.tsx b/packages/clerk-js/src/ui/primitives/FormErrorText.tsx index 39a723bc630..595827c1e2c 100644 --- a/packages/clerk-js/src/ui/primitives/FormErrorText.tsx +++ b/packages/clerk-js/src/ui/primitives/FormErrorText.tsx @@ -34,7 +34,6 @@ export const FormErrorText = forwardRef((props, return ( ((props, ref) return ( ({ base: { color: theme.colors.$colorText, - ...common.textVariants(theme).smallMedium, + ...common.textVariants(theme).subtitle, ...common.disabled(theme), }, variants: {}, diff --git a/packages/clerk-js/src/ui/primitives/FormSuccessText.tsx b/packages/clerk-js/src/ui/primitives/FormSuccessText.tsx index fa7e969936e..b879339bde9 100644 --- a/packages/clerk-js/src/ui/primitives/FormSuccessText.tsx +++ b/packages/clerk-js/src/ui/primitives/FormSuccessText.tsx @@ -27,11 +27,10 @@ export const FormSuccessText = forwardRef((props, re return ( ((props, re return ( ((props, ref) => return ( ); diff --git a/packages/clerk-js/src/ui/primitives/Heading.tsx b/packages/clerk-js/src/ui/primitives/Heading.tsx index 5c44a934659..b8c8fa6a4dc 100644 --- a/packages/clerk-js/src/ui/primitives/Heading.tsx +++ b/packages/clerk-js/src/ui/primitives/Heading.tsx @@ -9,15 +9,10 @@ const { applyVariants, filterProps } = createVariants(theme => ({ }, variants: { textVariant: { ...common.textVariants(theme) }, - as: { - h1: { - lineHeight: theme.lineHeights.$base, - }, - }, }, defaultVariants: { as: 'h1', - textVariant: 'xlargeMedium', + textVariant: 'h1', }, })); @@ -29,7 +24,7 @@ export const Heading = (props: HeadingProps) => { return ( ); }; diff --git a/packages/clerk-js/src/ui/primitives/Icon.tsx b/packages/clerk-js/src/ui/primitives/Icon.tsx index 264e3227cb1..38c8e1af6c3 100644 --- a/packages/clerk-js/src/ui/primitives/Icon.tsx +++ b/packages/clerk-js/src/ui/primitives/Icon.tsx @@ -36,7 +36,7 @@ export const Icon = (props: IconProps): JSX.Element => { ); }; diff --git a/packages/clerk-js/src/ui/primitives/Input.tsx b/packages/clerk-js/src/ui/primitives/Input.tsx index fa0b7321ddf..465b3915b03 100644 --- a/packages/clerk-js/src/ui/primitives/Input.tsx +++ b/packages/clerk-js/src/ui/primitives/Input.tsx @@ -18,9 +18,8 @@ const { applyVariants, filterProps } = createVariants((theme, props) => ({ width: props.type === 'checkbox' ? theme.sizes.$4 : '100%', aspectRatio: props.type === 'checkbox' ? '1/1' : 'unset', accentColor: theme.colors.$primary500, - ...common.textVariants(theme).smallRegular, + ...common.textVariants(theme).body, ...common.borderVariants(theme, props).normal, - ...(props.focusRing === false ? {} : common.focusRingInput(theme, props)), ...common.disabled(theme), [mqu.ios]: { fontSize: theme.fontSizes.$md, diff --git a/packages/clerk-js/src/ui/primitives/Link.tsx b/packages/clerk-js/src/ui/primitives/Link.tsx index 6cedc61abfc..b03f706603c 100644 --- a/packages/clerk-js/src/ui/primitives/Link.tsx +++ b/packages/clerk-js/src/ui/primitives/Link.tsx @@ -18,7 +18,6 @@ const { applyVariants, filterProps } = createVariants(theme => ({ }, variants: { variant: common.textVariants(theme), - size: common.fontSizeVariants(theme), colorScheme: { primary: { color: theme.colors.$primary500, @@ -38,7 +37,7 @@ const { applyVariants, filterProps } = createVariants(theme => ({ }, defaultVariants: { colorScheme: 'primary', - variant: 'smallRegular', + variant: 'body', }, })); @@ -66,7 +65,7 @@ export const Link = (props: LinkProps): JSX.Element => { href={href || ''} target={href && isExternal ? '_blank' : undefined} rel={href && isExternal ? 'noopener' : undefined} - css={applyVariants(props)} + css={applyVariants(props) as any} > {children} diff --git a/packages/clerk-js/src/ui/primitives/NotificationBadge.tsx b/packages/clerk-js/src/ui/primitives/NotificationBadge.tsx index 265e1e71dfb..d777081debd 100644 --- a/packages/clerk-js/src/ui/primitives/NotificationBadge.tsx +++ b/packages/clerk-js/src/ui/primitives/NotificationBadge.tsx @@ -25,7 +25,7 @@ const { applyVariants, filterProps } = createVariants(theme => ({ }, defaultVariants: { colorScheme: 'primary', - textVariant: 'extraSmallRegular', + textVariant: 'caption', }, })); @@ -39,7 +39,7 @@ export const NotificationBadge = (props: NotificationBadgeProps) => { center as='span' css={[ - applyVariants(props), + applyVariants(props) as any, { lineHeight: 0, }, diff --git a/packages/clerk-js/src/ui/primitives/Spinner.tsx b/packages/clerk-js/src/ui/primitives/Spinner.tsx index df9877056f7..ca3988e9c64 100644 --- a/packages/clerk-js/src/ui/primitives/Spinner.tsx +++ b/packages/clerk-js/src/ui/primitives/Spinner.tsx @@ -61,7 +61,7 @@ export const Spinner = (props: SpinnerProps) => { return ( diff --git a/packages/clerk-js/src/ui/primitives/Table.tsx b/packages/clerk-js/src/ui/primitives/Table.tsx index e7c983a218a..cb333fd1dc3 100644 --- a/packages/clerk-js/src/ui/primitives/Table.tsx +++ b/packages/clerk-js/src/ui/primitives/Table.tsx @@ -59,7 +59,7 @@ export const Table = React.forwardRef((props, ); diff --git a/packages/clerk-js/src/ui/primitives/Td.tsx b/packages/clerk-js/src/ui/primitives/Td.tsx index 89d08ee9cf1..51357ef6d36 100644 --- a/packages/clerk-js/src/ui/primitives/Td.tsx +++ b/packages/clerk-js/src/ui/primitives/Td.tsx @@ -21,7 +21,7 @@ export const Td = React.forwardRef((props, ref) = ); diff --git a/packages/clerk-js/src/ui/primitives/Text.tsx b/packages/clerk-js/src/ui/primitives/Text.tsx index 95ac5ba5981..a098b6470e6 100644 --- a/packages/clerk-js/src/ui/primitives/Text.tsx +++ b/packages/clerk-js/src/ui/primitives/Text.tsx @@ -17,7 +17,6 @@ const { applyVariants, filterProps } = createVariants(theme => { }, variants: { variant: common.textVariants(theme), - size: common.fontSizeVariants(theme), colorScheme: { primary: { color: theme.colors.$colorText }, onPrimaryBg: { color: theme.colors.$colorTextOnPrimaryBackground }, @@ -35,7 +34,8 @@ const { applyVariants, filterProps } = createVariants(theme => { }, }, defaultVariants: { - variant: 'regularRegular', + variant: 'body', + colorScheme: 'inherit', }, }; }); @@ -45,12 +45,12 @@ export type TextProps = PrimitiveProps<'p'> & { isDisabled?: boolean } & StyleVa as?: 'p' | 'div' | 'label' | 'code' | 'span' | 'li' | 'a'; }; -export const Text = React.forwardRef((props, ref): JSX.Element => { +export const Text = React.forwardRef((props, ref) => { const { as: As = 'p', ...rest } = props; return ( ); diff --git a/packages/clerk-js/src/ui/primitives/Th.tsx b/packages/clerk-js/src/ui/primitives/Th.tsx index ffac4ba00ac..bb912f7a348 100644 --- a/packages/clerk-js/src/ui/primitives/Th.tsx +++ b/packages/clerk-js/src/ui/primitives/Th.tsx @@ -26,7 +26,7 @@ export const Th = React.forwardRef((props, ref) = ); diff --git a/packages/clerk-js/src/ui/router/Route.tsx b/packages/clerk-js/src/ui/router/Route.tsx index 4facb5a0980..c68ab28c4e4 100644 --- a/packages/clerk-js/src/ui/router/Route.tsx +++ b/packages/clerk-js/src/ui/router/Route.tsx @@ -18,7 +18,6 @@ interface UnguardedRouteProps { flowStart?: boolean; canActivate?: never; } - type GuardedRouteProps = { path?: string; index?: boolean; diff --git a/packages/clerk-js/src/ui/styledSystem/common.ts b/packages/clerk-js/src/ui/styledSystem/common.ts index 8471c70641d..072c84cff55 100644 --- a/packages/clerk-js/src/ui/styledSystem/common.ts +++ b/packages/clerk-js/src/ui/styledSystem/common.ts @@ -4,139 +4,115 @@ const textVariants = (t: InternalTheme) => { const base = { WebkitFontSmoothing: t.options.$fontSmoothing, fontFamily: 'inherit', + letterSpacing: t.letterSpacings.$normal, }; - const smallRegular = { + const h1 = { ...base, - fontWeight: t.fontWeights.$normal, - fontSize: t.fontSizes.$xs, - lineHeight: t.lineHeights.$shorter, - } as const; - - const smallMedium = { - ...smallRegular, fontWeight: t.fontWeights.$medium, - lineHeight: t.lineHeights.$short, + fontSize: t.fontSizes.$xl, + lineHeight: t.lineHeights.$large, } as const; - const smallBold = { - ...smallMedium, + const h2 = { + ...base, fontWeight: t.fontWeights.$bold, + fontSize: t.fontSizes.$lg, + lineHeight: t.lineHeights.$medium, } as const; - const extraSmallRegular = { + const h3 = { ...base, - fontWeight: t.fontWeights.$normal, - fontSize: t.fontSizes.$2xs, - letterSpacing: t.letterSpacings.$normal, - lineHeight: t.lineHeights.$none, + fontWeight: t.fontWeights.$bold, + fontSize: t.fontSizes.$md, + lineHeight: t.lineHeights.$small, } as const; - const extraSmallMedium = { + const subtitle = { ...base, fontWeight: t.fontWeights.$medium, - fontSize: t.fontSizes.$2xs, - letterSpacing: t.letterSpacings.$normal, - lineHeight: t.lineHeights.$shortest, + fontSize: t.fontSizes.$md, + lineHeight: t.lineHeights.$small, } as const; - const regularRegular = { + const body = { ...base, fontWeight: t.fontWeights.$normal, - fontSize: t.fontSizes.$sm, - lineHeight: t.lineHeights.$shorter, + fontSize: t.fontSizes.$md, + lineHeight: t.lineHeights.$small, } as const; - const regularMedium = { - ...regularRegular, + const caption = { + ...base, fontWeight: t.fontWeights.$medium, + fontSize: t.fontSizes.$xs, + lineHeight: t.lineHeights.$none, } as const; - const largeBold = { + const buttonLarge = { ...base, - fontWeight: t.fontWeights.$bold, - fontSize: t.fontSizes.$md, - lineHeight: t.lineHeights.$taller, - } as const; - - const largeMedium = { - ...largeBold, fontWeight: t.fontWeights.$medium, - }; - - const xlargeMedium = { - ...largeBold, - fontSize: t.fontSizes.$xl, - } as const; - - const xxlargeMedium = { - ...xlargeMedium, - fontSize: t.fontSizes.$2xl, - } as const; - - const buttonExtraSmallBold = { - ...extraSmallRegular, - fontWeight: t.fontWeights.$bold, - textTransform: 'uppercase', + fontSize: t.fontSizes.$md, + lineHeight: t.lineHeights.$small, fontFamily: t.fonts.$buttons, } as const; - const buttonSmallRegular = { - ...smallRegular, - fontFamily: t.fonts.$buttons, - }; - - const buttonRegularRegular = { - ...regularRegular, - fontFamily: t.fonts.$buttons, + const buttonSmall = { + ...base, + fontWeight: t.fontWeights.$medium, + fontSize: t.fontSizes.$sm, lineHeight: t.lineHeights.$none, - }; - - const buttonRegularMedium = { - ...regularMedium, fontFamily: t.fonts.$buttons, - lineHeight: t.lineHeights.$none, - }; - - const headingRegularRegular = { - ...regularRegular, - fontSize: t.fontSizes.$md, } as const; return { - headingRegularRegular, - buttonExtraSmallBold, - buttonSmallRegular, - buttonRegularRegular, - buttonRegularMedium, - extraSmallRegular, - extraSmallMedium, - smallRegular, - smallMedium, - smallBold, - regularRegular, - regularMedium, - largeMedium, - largeBold, - xlargeMedium, - xxlargeMedium, - } as const; -}; - -const fontSizeVariants = (t: InternalTheme) => { - return { - xss: { fontSize: t.fontSizes.$2xs }, - xs: { fontSize: t.fontSizes.$xs }, - sm: { fontSize: t.fontSizes.$sm }, + h1, + h2, + h3, + subtitle, + body, + caption, + buttonLarge, + buttonSmall, } as const; }; const borderVariants = (t: InternalTheme, props?: any) => { + const defaultBoxShadow = t.shadows.$input + .replace('{{color1}}', t.colors.$blackAlpha200) + .replace('{{color2}}', t.colors.$blackAlpha300); + const hoverBoxShadow = t.shadows.$inputHover + .replace('{{color1}}', t.colors.$blackAlpha300) + .replace('{{color2}}', t.colors.$blackAlpha400); + const hoverStyles = { + '&:hover': { + WebkitTapHighlightColor: 'transparent', + boxShadow: [defaultBoxShadow, hoverBoxShadow].toString(), + }, + }; + const focusStyles = + props?.focusRing === false + ? {} + : { + '&:focus': { + WebkitTapHighlightColor: 'transparent', + boxShadow: [ + defaultBoxShadow, + hoverBoxShadow, + t.shadows.$focusRing.replace('{{color}}', props?.hasError ? t.colors.$danger300 : t.colors.$primary300), + ].toString(), + }, + }; return { normal: { - borderRadius: t.radii.$md, - border: t.borders.$normal, - ...borderColor(t, props), + borderRadius: t.radii.$lg, + border: 'none', + boxShadow: defaultBoxShadow, + transitionProperty: t.transitionProperty.$common, + transitionTimingFunction: t.transitionTiming.$common, + transitionDuration: t.transitionDuration.$focusRing, + ...hoverStyles, + ...focusStyles, }, } as const; }; @@ -164,9 +140,9 @@ const focusRingInput = (t: InternalTheme, props?: any) => { return { '&:focus': { WebkitTapHighlightColor: 'transparent', - boxShadow: t.shadows.$focusRingInput.replace( + boxShadow: t.shadows.$focusRing.replace( '{{color}}', - props?.hasError ? t.colors.$danger200 : t.colors.$primary200, + props?.hasError ? t.colors.$danger400 : t.colors.$primary400, ), transitionProperty: t.transitionProperty.$common, transitionTimingFunction: t.transitionTiming.$common, @@ -175,6 +151,10 @@ const focusRingInput = (t: InternalTheme, props?: any) => { } as const; }; +const buttonShadow = (t: InternalTheme) => { + return { boxShadow: t.shadows.$buttonShadow.replace('{{color}}', t.colors.$primary800) }; +}; + const disabled = (t: InternalTheme) => { return { '&:disabled,&[data-disabled]': { @@ -214,10 +194,10 @@ const maxHeightScroller = (t: InternalTheme) => export const common = { textVariants, - fontSizeVariants, borderVariants, focusRing, focusRingInput, + buttonShadow, disabled, borderColor, centeredFlex, diff --git a/packages/clerk-js/src/ui/utils/colors.ts b/packages/clerk-js/src/ui/utils/colors.ts index 808afd0a85a..c5423c911d7 100644 --- a/packages/clerk-js/src/ui/utils/colors.ts +++ b/packages/clerk-js/src/ui/utils/colors.ts @@ -201,7 +201,7 @@ const hwbTupleToRgbTuple = (hwb: ColorTuple): ColorTuple => { b = n; break; } - // eslint-enable max-statements-per-line, no-multi-spaces + /* eslint-enable max-statements-per-line,no-multi-spaces */ return [r * 255, g * 255, b * 255, a]; }; @@ -227,7 +227,7 @@ const rgbaTupleToHslaColor = (rgb: ColorTuple): HslaColor => { h = 4 + (r - g) / delta; } - // @ts-expect-error - TODO: fix this, h could be undefined + // @ts-ignore h = Math.min(h * 60, 360); if (h < 0) { diff --git a/packages/clerk-js/src/ui/utils/passwordUtils.test.tsx b/packages/clerk-js/src/ui/utils/passwordUtils.test.tsx index 82c87854f1f..6c95e5ca6e5 100644 --- a/packages/clerk-js/src/ui/utils/passwordUtils.test.tsx +++ b/packages/clerk-js/src/ui/utils/passwordUtils.test.tsx @@ -1,7 +1,8 @@ -import { bindCreateFixtures, renderHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { OptionsProvider } from '../contexts'; import { useLocalizations } from '../customizables'; import { createPasswordError } from './passwordUtils'; +import { bindCreateFixtures } from './test/createFixtures'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/tsconfig.declarations.retheme.json b/packages/clerk-js/tsconfig.declarations.retheme.json deleted file mode 100644 index 5999198d541..00000000000 --- a/packages/clerk-js/tsconfig.declarations.retheme.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.declarations.json", - "compilerOptions": { - "paths": { - "~ui/*": ["./ui.retheme/*"] - } - } -} diff --git a/packages/clerk-js/webpack.config.js b/packages/clerk-js/webpack.config.js index 543d17af7f3..9bae9459580 100644 --- a/packages/clerk-js/webpack.config.js +++ b/packages/clerk-js/webpack.config.js @@ -25,8 +25,6 @@ const variantToSourceFile = { /** @returns { import('webpack').Configuration } */ const common = ({ mode }) => { - const uiRetheme = process.env.CLERK_RETHEME === '1' || process.env.CLERK_RETHEME === 'true'; - return { mode, resolve: { @@ -34,7 +32,7 @@ const common = ({ mode }) => { // @see https://webpack.js.org/configuration/resolve/#resolveextensions extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'], alias: { - '~ui': uiRetheme ? './ui.retheme' : './ui', + '~ui': './ui', }, }, plugins: [ @@ -167,22 +165,22 @@ const commonForProd = () => { }; }; -/** @type { () => (import('webpack').Configuration) } */ -const externalsForHeadless = () => { - return { - externals: { - react: 'react', - 'react-dom': 'react-dom', - }, - }; -}; +// /** @type { () => (import('webpack').Configuration) } */ +// const externalsForHeadless = () => { +// return { +// externals: { +// react: 'react', +// 'react-dom': 'react-dom', +// }, +// }; +// }; const entryForVariant = variant => { return { entry: { [variant]: variantToSourceFile[variant] } }; }; /** @type { () => (import('webpack').Configuration)[] } */ -const prodConfig = ({ mode, env }) => { +const prodConfig = ({ mode }) => { const clerkBrowser = merge(entryForVariant(variants.clerkBrowser), common({ mode }), commonForProd()); const clerkHeadless = merge( diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 01c4cd09a38..f34dd2d41df 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -246,8 +246,6 @@ export const arSA: LocalizationResource = { start: { headerTitle__account: 'الحساب', headerTitle__security: 'الأمان', - headerSubtitle__account: 'إدارة معلومات حسابك', - headerSubtitle__security: 'إدارة تفضيلات الأمان الخاصة بك', profileSection: { title: 'الملف الشخصي', }, @@ -537,8 +535,6 @@ export const arSA: LocalizationResource = { start: { headerTitle__members: 'الأعضاء', headerTitle__settings: 'الأعدادات', - headerSubtitle__members: 'عرض وإدارة أعضاء المنظمة', - headerSubtitle__settings: 'إدارة اعدادات المنظمة', }, profilePage: { title: 'الملف الشخصي للمنظمة', diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 65ca888c79b..a1bdafd3d04 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -236,8 +236,6 @@ export const csCZ: LocalizationResource = { start: { headerTitle__account: 'Účet', headerTitle__security: 'Bezpečnost', - headerSubtitle__account: 'Spravujte své údaje o účtu', - headerSubtitle__security: 'Spravujte své bezpečnostní nastavení', profileSection: { title: 'Profil', }, @@ -508,8 +506,6 @@ export const csCZ: LocalizationResource = { start: { headerTitle__members: 'Členové', headerTitle__settings: 'Nastavení', - headerSubtitle__members: 'Zobrazit a spravovat členy organizace', - headerSubtitle__settings: 'Spravovat nastavení organizace', }, profilePage: { title: 'Profil organizace', diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 36df9756d23..b54576f6a68 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -211,8 +211,6 @@ export const daDK: LocalizationResource = { start: { headerTitle__account: 'Konto', headerTitle__security: 'Sikkerhed', - headerSubtitle__account: 'Administrere dine kontooplysninger', - headerSubtitle__security: 'Administrere dine sikkerhedspræferencer', profileSection: { title: 'Profil', }, @@ -485,8 +483,6 @@ export const daDK: LocalizationResource = { start: { headerTitle__members: 'Medlemmer', headerTitle__settings: 'Indstillinger', - headerSubtitle__members: 'Se og administrer organisationsmedlemmer', - headerSubtitle__settings: 'Administrer organisationsindstillinger', }, profilePage: { title: 'Organisationsprofil', diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 2ae44a74b05..0de2eff48b7 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -212,8 +212,6 @@ export const deDE: LocalizationResource = { start: { headerTitle__account: 'Konto', headerTitle__security: 'Sicherheit', - headerSubtitle__account: 'Verwalten Sie Ihre Kontoinformationen', - headerSubtitle__security: 'Verwalten Sie Ihre Sicherheitseinstellungen', profileSection: { title: 'Profil', }, @@ -498,8 +496,6 @@ export const deDE: LocalizationResource = { start: { headerTitle__members: 'Mitglieder', headerTitle__settings: 'Einstellungen', - headerSubtitle__members: 'Anzeigen und Verwalten von Organisationsmitgliedern', - headerSubtitle__settings: 'Organisationseinstellungen verwalten', }, profilePage: { title: 'Organisationsprofil', diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 0aced0251eb..fd3efc25fca 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -239,8 +239,6 @@ export const elGR: LocalizationResource = { start: { headerTitle__account: 'Λογαριασμός', headerTitle__security: 'Ασφάλεια', - headerSubtitle__account: 'Διαχείριση πληροφοριών λογαριασμού', - headerSubtitle__security: 'Διαχείριση προτιμήσεων ασφάλειας', profileSection: { title: 'Προφίλ', }, @@ -539,8 +537,6 @@ export const elGR: LocalizationResource = { start: { headerTitle__members: 'Μέλη', headerTitle__settings: 'Ρυθμίσεις', - headerSubtitle__members: 'Προβολή και διαχείριση μελών του οργανισμού', - headerSubtitle__settings: 'Διαχείριση ρυθμίσεων οργανισμού', }, profilePage: { title: 'Προφίλ Οργανισμού', diff --git a/packages/localizations/src/en-US.retheme.ts b/packages/localizations/src/en-US.retheme.ts deleted file mode 100644 index 3397216d14a..00000000000 --- a/packages/localizations/src/en-US.retheme.ts +++ /dev/null @@ -1,795 +0,0 @@ -import type { LocalizationResource } from '@clerk/types'; - -const commonTexts = { - signIn: { - phoneCode: { - title: 'Check your phone', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to your phone number', - resendButton: "Didn't receive a code? Resend", - }, - }, -} as const; - -export const enUS: LocalizationResource = { - locale: 'en-US', - socialButtonsBlockButton: 'Continue with {{provider|titleize}}', - dividerText: 'or', - formFieldLabel__emailAddress: 'Email address', - formFieldLabel__emailAddresses: 'Email addresses', - formFieldLabel__phoneNumber: 'Phone number', - formFieldLabel__username: 'Username', - formFieldLabel__emailAddress_username: 'Email address or username', - formFieldLabel__password: 'Password', - formFieldLabel__currentPassword: 'Current password', - formFieldLabel__newPassword: 'New password', - formFieldLabel__confirmPassword: 'Confirm password', - formFieldLabel__signOutOfOtherSessions: 'Sign out of all other devices', - formFieldLabel__automaticInvitations: 'Enable automatic invitations for this domain', - formFieldLabel__firstName: 'First name', - formFieldLabel__lastName: 'Last name', - formFieldLabel__backupCode: 'Backup code', - formFieldLabel__organizationName: 'Organization name', - formFieldLabel__organizationSlug: 'Slug URL', - formFieldLabel__organizationDomain: 'Domain', - formFieldLabel__organizationDomainEmailAddress: 'Verification email address', - formFieldLabel__organizationDomainEmailAddressDescription: - 'Enter an email address under this domain to receive a code and verify this domain.', - formFieldLabel__organizationDomainDeletePending: 'Delete pending invitations and suggestions', - formFieldLabel__confirmDeletion: 'Confirmation', - formFieldLabel__role: 'Role', - formFieldInputPlaceholder__emailAddress: '', - formFieldInputPlaceholder__emailAddresses: - 'Enter or paste one or more email addresses, separated by spaces or commas', - formFieldInputPlaceholder__phoneNumber: '', - formFieldInputPlaceholder__username: '', - formFieldInputPlaceholder__emailAddress_username: '', - formFieldInputPlaceholder__password: '', - formFieldInputPlaceholder__firstName: '', - formFieldInputPlaceholder__lastName: '', - formFieldInputPlaceholder__backupCode: '', - formFieldInputPlaceholder__organizationName: '', - formFieldInputPlaceholder__organizationSlug: '', - formFieldInputPlaceholder__organizationDomain: '', - formFieldInputPlaceholder__organizationDomainEmailAddress: '', - formFieldInputPlaceholder__confirmDeletionUserAccount: 'Delete account', - formFieldError__notMatchingPasswords: `Passwords don't match.`, - formFieldError__matchingPasswords: 'Passwords match.', - formFieldError__verificationLinkExpired: 'The verification link expired. Please request a new link.', - formFieldAction__forgotPassword: 'Forgot password?', - formFieldHintText__optional: 'Optional', - formFieldHintText__slug: 'A slug is a human-readable ID that must be unique. It’s often used in URLs.', - formButtonPrimary: 'Continue', - signInEnterPasswordTitle: 'Enter your password', - backButton: 'Back', - footerActionLink__useAnotherMethod: 'Use another method', - badge__primary: 'Primary', - badge__thisDevice: 'This device', - badge__userDevice: 'User device', - badge__otherImpersonatorDevice: 'Other impersonator device', - badge__default: 'Default', - badge__unverified: 'Unverified', - badge__requiresAction: 'Requires action', - badge__you: 'You', - footerPageLink__help: 'Help', - footerPageLink__privacy: 'Privacy', - footerPageLink__terms: 'Terms', - paginationButton__previous: 'Previous', - paginationButton__next: 'Next', - paginationRowText__displaying: 'Displaying', - paginationRowText__of: 'of', - membershipRole__admin: 'Admin', - membershipRole__basicMember: 'Member', - membershipRole__guestMember: 'Guest', - signUp: { - start: { - title: 'Create your account', - subtitle: 'to continue to {{applicationName}}', - actionText: 'Have an account?', - actionLink: 'Sign in', - }, - emailLink: { - title: 'Verify your email', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification link', - formSubtitle: 'Use the verification link sent to your email address', - resendButton: "Didn't receive a link? Resend", - verified: { - title: 'Successfully signed up', - }, - loading: { - title: 'Signing up...', - }, - verifiedSwitchTab: { - title: 'Successfully verified email', - subtitle: 'Return to the newly opened tab to continue', - subtitleNewTab: 'Return to previous tab to continue', - }, - }, - emailCode: { - title: 'Verify your email', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to your email address', - resendButton: "Didn't receive a code? Resend", - }, - phoneCode: { - title: 'Verify your phone', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to your phone number', - resendButton: "Didn't receive a code? Resend", - }, - continue: { - title: 'Fill in missing fields', - subtitle: 'to continue to {{applicationName}}', - actionText: 'Have an account?', - actionLink: 'Sign in', - }, - }, - signIn: { - start: { - title: 'Sign in', - subtitle: 'to continue to {{applicationName}}', - actionText: 'No account?', - actionLink: 'Sign up', - actionLink__use_email: 'Use email', - actionLink__use_phone: 'Use phone', - actionLink__use_username: 'Use username', - actionLink__use_email_username: 'Use email or username', - }, - password: { - title: 'Enter your password', - subtitle: 'to continue to {{applicationName}}', - actionLink: 'Use another method', - }, - forgotPasswordAlternativeMethods: { - title: 'Forgot Password?', - label__alternativeMethods: 'Or, sign in with another method.', - blockButton__resetPassword: 'Reset your password', - }, - forgotPassword: { - title_email: 'Check your email', - title_phone: 'Check your phone', - subtitle: 'to reset your password', - formTitle: 'Reset password code', - formSubtitle_email: 'Enter the code sent to your email address', - formSubtitle_phone: 'Enter the code sent to your phone number', - resendButton: "Didn't receive a code? Resend", - }, - resetPassword: { - title: 'Reset Password', - formButtonPrimary: 'Reset Password', - successMessage: 'Your password was successfully changed. Signing you in, please wait a moment.', - requiredMessage: - 'An account already exists with an unverified email address. Please reset your password for security.', - }, - resetPasswordMfa: { - detailsLabel: 'We need to verify your identity before resetting your password.', - }, - emailCode: { - title: 'Check your email', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to your email address', - resendButton: "Didn't receive a code? Resend", - }, - emailLink: { - title: 'Check your email', - subtitle: 'to continue to {{applicationName}}', - formTitle: 'Verification link', - formSubtitle: 'Use the verification link sent to your email', - resendButton: "Didn't receive a link? Resend", - unusedTab: { - title: 'You may close this tab', - }, - verified: { - title: 'Successfully signed in', - subtitle: 'You will be redirected soon', - }, - verifiedSwitchTab: { - subtitle: 'Return to original tab to continue', - titleNewTab: 'Signed in on other tab', - subtitleNewTab: 'Return to the newly opened tab to continue', - }, - loading: { - title: 'Signing in...', - subtitle: 'You will be redirected soon', - }, - failed: { - title: 'This verification link is invalid', - subtitle: 'Return to the original tab to continue.', - }, - expired: { - title: 'This verification link has expired', - subtitle: 'Return to the original tab to continue.', - }, - }, - phoneCode: { ...commonTexts.signIn.phoneCode }, - phoneCodeMfa: { ...commonTexts.signIn.phoneCode, subtitle: '' }, - totpMfa: { - title: 'Two-step verification', - subtitle: '', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code generated by your authenticator app', - }, - backupCodeMfa: { - title: 'Enter a backup code', - subtitle: 'to continue to {{applicationName}}', - formTitle: '', - formSubtitle: '', - }, - alternativeMethods: { - title: 'Use another method', - actionLink: 'Get help', - blockButton__emailLink: 'Email link to {{identifier}}', - blockButton__emailCode: 'Email code to {{identifier}}', - blockButton__phoneCode: 'Send SMS code to {{identifier}}', - blockButton__password: 'Sign in with your password', - blockButton__totp: 'Use your authenticator app', - blockButton__backupCode: 'Use a backup code', - getHelp: { - title: 'Get help', - content: `If you’re experiencing difficulty signing into your account, email us and we will work with you to restore access as soon as possible.`, - blockButton__emailSupport: 'Email support', - }, - }, - noAvailableMethods: { - title: 'Cannot sign in', - subtitle: 'An error occurred', - message: "Cannot proceed with sign in. There's no available authentication factor.", - }, - }, - userProfile: { - mobileButton__menu: 'Menu', - formButtonPrimary__continue: 'Continue', - formButtonPrimary__finish: 'Finish', - formButtonReset: 'Cancel', - navbar: { - title: 'Account', - description: 'Manage your account info.', - }, - start: { - headerTitle__account: 'Profile details', - headerTitle__security: 'Security', - profileSection: { - title: 'Profile', - primaryButton: 'Edit Profile', - }, - usernameSection: { - title: 'Username', - primaryButton__changeUsername: 'Change username', - primaryButton__setUsername: 'Set username', - }, - emailAddressesSection: { - title: 'Email addresses', - primaryButton: 'Add an email address', - detailsTitle__primary: 'Primary email address', - detailsSubtitle__primary: 'This email address is the primary email address', - detailsAction__primary: 'Complete verification', - detailsTitle__nonPrimary: 'Set as primary email address', - detailsSubtitle__nonPrimary: - 'Set this email address as the primary to receive communications regarding your account.', - detailsAction__nonPrimary: 'Set as primary', - detailsTitle__unverified: 'Verify email address', - detailsSubtitle__unverified: 'Complete verification to access all features with this email address', - detailsAction__unverified: 'Verify email address', - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Delete this email address and remove it from your account', - destructiveAction: 'Remove email address', - }, - phoneNumbersSection: { - title: 'Phone numbers', - primaryButton: 'Add a phone number', - detailsTitle__primary: 'Primary phone number', - detailsSubtitle__primary: 'This phone number is the primary phone number', - detailsAction__primary: 'Complete verification', - detailsTitle__nonPrimary: 'Set as primary phone number', - detailsSubtitle__nonPrimary: - 'Set this phone number as the primary to receive communications regarding your account.', - detailsAction__nonPrimary: 'Set as primary', - detailsTitle__unverified: 'Verify phone number', - detailsSubtitle__unverified: 'Complete verification to access all features with this phone number', - detailsAction__unverified: 'Verify phone number', - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Delete this phone number and remove it from your account', - destructiveAction: 'Remove phone number', - }, - connectedAccountsSection: { - title: 'Connected accounts', - primaryButton: 'Connect account', - title__connectionFailed: 'Retry failed connection', - title__reauthorize: 'Reauthorization required', - subtitle__reauthorize: - 'The required scopes have been updated, and you may be experiencing limited functionality. Please re-authorize this application to avoid any issues', - actionLabel__connectionFailed: 'Try again', - actionLabel__reauthorize: 'Authorize now', - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Remove this connected account from your account', - destructiveActionAccordionSubtitle: 'Remove connected account', - }, - enterpriseAccountsSection: { - title: 'Enterprise accounts', - }, - passwordSection: { - title: 'Password', - primaryButton__changePassword: 'Change password', - primaryButton__setPassword: 'Set password', - }, - mfaSection: { - title: 'Two-step verification', - primaryButton: 'Add two-step verification', - phoneCode: { - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Remove this phone number from the two-step verification methods', - destructiveActionLabel: 'Remove phone number', - title__default: 'Default factor', - title__setDefault: 'Set as Default factor', - subtitle__default: 'This factor will be used as the default two-step verification method when signing in.', - subtitle__setDefault: - 'Set this factor as the default factor to use it as the default two-step verification method when signing in.', - actionLabel__setDefault: 'Set as default', - }, - backupCodes: { - headerTitle: 'Backup codes', - title__regenerate: 'Regenerate backup codes', - subtitle__regenerate: - 'Get a fresh set of secure backup codes. Prior backup codes will be deleted and cannot be used.', - actionLabel__regenerate: 'Regenerate codes', - }, - totp: { - headerTitle: 'Authenticator application', - title: 'Default factor', - subtitle: 'This factor will be used as the default two-step verification method when signing in.', - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Remove authenticator application from the two-step verification methods', - destructiveActionLabel: 'Remove authenticator application', - }, - }, - activeDevicesSection: { - title: 'Active devices', - primaryButton: 'Active devices', - detailsTitle: 'Current device', - detailsSubtitle: 'This is the device you are currently using', - destructiveActionTitle: 'Sign out', - destructiveActionSubtitle: 'Sign out from your account on this device', - destructiveAction: 'Sign out of device', - }, - web3WalletsSection: { - title: 'Web3 wallets', - primaryButton: 'Web3 wallets', - destructiveActionTitle: 'Remove', - destructiveActionSubtitle: 'Remove this web3 wallet from your account', - destructiveAction: 'Remove wallet', - }, - dangerSection: { - title: 'Danger', - deleteAccountButton: 'Delete Account', - deleteAccountTitle: 'Delete Account', - deleteAccountDescription: 'Delete your account and all its associated data', - }, - }, - profilePage: { - title: 'Update profile', - imageFormTitle: 'Profile image', - imageFormSubtitle: 'Upload image', - imageFormDestructiveActionSubtitle: 'Remove image', - fileDropAreaTitle: 'Drag file here, or...', - fileDropAreaAction: 'Select file', - fileDropAreaHint: 'Upload a JPG, PNG, GIF, or WEBP image smaller than 10 MB', - readonly: 'Your profile information has been provided by the enterprise connection and cannot be edited.', - successMessage: 'Your profile has been updated.', - }, - usernamePage: { - title: 'Update username', - successMessage: 'Your username has been updated.', - }, - emailAddressPage: { - title: 'Add email address', - emailCode: { - formHint: 'An email containing a verification code will be sent to this email address.', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to {{identifier}}', - resendButton: "Didn't receive a code? Resend", - successMessage: 'The email {{identifier}} has been added to your account.', - }, - emailLink: { - formHint: 'An email containing a verification link will be sent to this email address.', - formTitle: 'Verification link', - formSubtitle: 'Click on the verification link in the email sent to {{identifier}}', - resendButton: "Didn't receive a link? Resend", - successMessage: 'The email {{identifier}} has been added to your account.', - }, - removeResource: { - title: 'Remove email address', - messageLine1: '{{identifier}} will be removed from this account.', - messageLine2: 'You will no longer be able to sign in using this email address.', - successMessage: '{{emailAddress}} has been removed from your account.', - }, - }, - phoneNumberPage: { - title: 'Add phone number', - successMessage: '{{identifier}} has been added to your account.', - infoText: 'A text message containing a verification link will be sent to this phone number.', - infoText__secondary: 'Message and data rates may apply.', - removeResource: { - title: 'Remove phone number', - messageLine1: '{{identifier}} will be removed from this account.', - messageLine2: 'You will no longer be able to sign in using this phone number.', - successMessage: '{{phoneNumber}} has been removed from your account.', - }, - }, - connectedAccountPage: { - title: 'Add connected account', - formHint: 'Select a provider to connect your account.', - formHint__noAccounts: 'There are no available external account providers.', - socialButtonsBlockButton: 'Connect {{provider|titleize}} account', - successMessage: 'The provider has been added to your account', - removeResource: { - title: 'Remove connected account', - messageLine1: '{{identifier}} will be removed from this account.', - messageLine2: - 'You will no longer be able to use this connected account and any dependent features will no longer work.', - successMessage: '{{connectedAccount}} has been removed from your account.', - }, - }, - web3WalletPage: { - title: 'Add web3 wallet', - subtitle__availableWallets: 'Select a web3 wallet to connect to your account.', - subtitle__unavailableWallets: 'There are no available web3 wallets.', - successMessage: 'The wallet has been added to your account.', - removeResource: { - title: 'Remove web3 wallet', - messageLine1: '{{identifier}} will be removed from this account.', - messageLine2: 'You will no longer be able to sign in using this web3 wallet.', - successMessage: '{{web3Wallet}} has been removed from your account.', - }, - }, - passwordPage: { - title: 'Set password', - changePasswordTitle: 'Change password', - readonly: 'Your password can currently not be edited because you can sign in only via the enterprise connection.', - successMessage: 'Your password has been set.', - changePasswordSuccessMessage: 'Your password has been updated.', - sessionsSignedOutSuccessMessage: 'All other devices have been signed out.', - }, - mfaPage: { - title: 'Add two-step verification', - formHint: 'Select a method to add.', - }, - mfaTOTPPage: { - title: 'Add authenticator application', - verifyTitle: 'Verification code', - verifySubtitle: 'Enter verification code generated by your authenticator', - successMessage: - 'Two-step verification is now enabled. When signing in, you will need to enter a verification code from this authenticator as an additional step.', - authenticatorApp: { - infoText__ableToScan: - 'Set up a new sign-in method in your authenticator app and scan the following QR code to link it to your account.', - infoText__unableToScan: 'Set up a new sign-in method in your authenticator and enter the Key provided below.', - inputLabel__unableToScan1: - 'Make sure Time-based or One-time passwords is enabled, then finish linking your account.', - inputLabel__unableToScan2: - 'Alternatively, if your authenticator supports TOTP URIs, you can also copy the full URI.', - buttonAbleToScan__nonPrimary: 'Scan QR code instead', - buttonUnableToScan__nonPrimary: 'Can’t scan QR code?', - }, - removeResource: { - title: 'Remove two-step verification', - messageLine1: 'Verification codes from this authenticator will no longer be required when signing in.', - messageLine2: 'Your account may not be as secure. Are you sure you want to continue?', - successMessage: 'Two-step verification via authenticator application has been removed.', - }, - }, - mfaPhoneCodePage: { - title: 'Add SMS code verification', - primaryButton__addPhoneNumber: 'Add a phone number', - subtitle__availablePhoneNumbers: 'Select a phone number to register for SMS code two-step verification.', - subtitle__unavailablePhoneNumbers: - 'There are no available phone numbers to register for SMS code two-step verification.', - successMessage: - 'SMS code two-step verification is now enabled for this phone number. When signing in, you will need to enter a verification code sent to this phone number as an additional step.', - removeResource: { - title: 'Remove two-step verification', - messageLine1: '{{identifier}} will be no longer receiving verification codes when signing in.', - messageLine2: 'Your account may not be as secure. Are you sure you want to continue?', - successMessage: 'SMS code two-step verification has been removed for {{mfaPhoneCode}}', - }, - }, - backupCodePage: { - title: 'Add backup code verification', - title__codelist: 'Backup codes', - subtitle__codelist: 'Store them securely and keep them secret.', - infoText1: 'Backup codes will be enabled for this account.', - infoText2: - 'Keep the backup codes secret and store them securely. You may regenerate backup codes if you suspect they have been compromised.', - successSubtitle: - 'You can use one of these to sign in to your account, if you lose access to your authentication device.', - successMessage: - 'Backup codes are now enabled. You can use one of these to sign in to your account, if you lose access to your authentication device. Each code can only be used once.', - actionLabel__copy: 'Copy all', - actionLabel__copied: 'Copied!', - actionLabel__download: 'Download .txt', - actionLabel__print: 'Print', - }, - deletePage: { - title: 'Delete account', - messageLine1: 'Are you sure you want to delete your account?', - messageLine2: 'This action is permanent and irreversible.', - actionDescription: 'Type Delete account below to continue.', - confirm: 'Delete account', - }, - }, - userButton: { - action__manageAccount: 'Manage account', - action__signOut: 'Sign out', - action__signOutAll: 'Sign out of all accounts', - action__addAccount: 'Add account', - }, - organizationSwitcher: { - personalWorkspace: 'Personal account', - notSelected: 'No organization selected', - action__createOrganization: 'Create Organization', - action__manageOrganization: 'Manage', - action__invitationAccept: 'Join', - action__suggestionsAccept: 'Request to join', - suggestionsAcceptedLabel: 'Pending approval', - }, - impersonationFab: { - title: 'Signed in as {{identifier}}', - action__signOut: 'Sign out', - }, - organizationProfile: { - badge__unverified: 'Unverified', - badge__automaticInvitation: 'Automatic invitations', - badge__automaticSuggestion: 'Automatic suggestions', - badge__manualInvitation: 'No automatic enrollment', - navbar: { - title: 'Organization', - description: 'Manage your organization.', - }, - start: { - headerTitle__members: 'Members', - headerTitle__settings: 'Settings', - }, - profilePage: { - title: 'Organization Profile', - subtitle: 'Manage organization profile', - successMessage: 'The organization has been updated.', - dangerSection: { - title: 'Danger', - leaveOrganization: { - title: 'Leave organization', - messageLine1: - 'Are you sure you want to leave this organization? You will lose access to this organization and its applications.', - messageLine2: 'This action is permanent and irreversible.', - successMessage: 'You have left the organization.', - actionDescription: 'Type {{organizationName}} below to continue.', - }, - deleteOrganization: { - title: 'Delete organization', - messageLine1: 'Are you sure you want to delete this organization?', - messageLine2: 'This action is permanent and irreversible.', - actionDescription: 'Type {{organizationName}} below to continue.', - successMessage: 'You have deleted the organization.', - }, - }, - domainSection: { - title: 'Verified domains', - subtitle: - 'Allow users to join the organization automatically or request to join based on a verified email domain.', - primaryButton: 'Add domain', - unverifiedDomain_menuAction__verify: 'Verify domain', - unverifiedDomain_menuAction__remove: 'Delete domain', - }, - }, - createDomainPage: { - title: 'Add domain', - subtitle: - 'Add the domain to verify. Users with email addresses at this domain can join the organization automatically or request to join.', - }, - verifyDomainPage: { - title: 'Verify domain', - subtitle: 'The domain {{domainName}} needs to be verified via email.', - subtitleVerificationCodeScreen: 'A verification code was sent to {{emailAddress}}. Enter the code to continue.', - formTitle: 'Verification code', - formSubtitle: 'Enter the verification code sent to your email address', - resendButton: "Didn't receive a code? Resend", - }, - verifiedDomainPage: { - subtitle: 'The domain {{domain}} is now verified. Continue by selecting enrollment mode.', - start: { - headerTitle__enrollment: 'Enrollment options', - headerTitle__danger: 'Danger', - }, - enrollmentTab: { - subtitle: 'Choose how users from this domain can join the organization.', - manualInvitationOption__label: 'No automatic enrollment', - manualInvitationOption__description: 'Users can only be invited manually to the organization.', - automaticInvitationOption__label: 'Automatic invitations', - automaticInvitationOption__description: - 'Users are automatically invited to join the organization when they sign-up and can join anytime.', - automaticSuggestionOption__label: 'Automatic suggestions', - automaticSuggestionOption__description: - 'Users receive a suggestion to request to join, but must be approved by an admin before they are able to join the organization.', - formButton__save: 'Save', - calloutInfoLabel: 'Changing the enrollment mode will only affect new users.', - calloutInvitationCountLabel: 'Pending invitations sent to users: {{count}}', - calloutSuggestionCountLabel: 'Pending suggestions sent to users: {{count}}', - }, - dangerTab: { - removeDomainTitle: 'Remove domain', - removeDomainSubtitle: 'Remove this domain from your verified domains', - removeDomainActionLabel__remove: 'Remove domain', - calloutInfoLabel: 'Removing this domain will affect invited users.', - }, - }, - invitePage: { - title: 'Invite members', - subtitle: 'Invite new members to this organization', - successMessage: 'Invitations successfully sent', - detailsTitle__inviteFailed: - 'The invitations could not be sent. There are already pending invitations for the following email addresses: {{email_addresses}}.', - formButtonPrimary__continue: 'Send invitations', - }, - removeDomainPage: { - title: 'Remove domain', - messageLine1: 'The email domain {{domain}} will be removed.', - messageLine2: 'Users won’t be able to join the organization automatically after this.', - successMessage: '{{domain}} has been removed.', - }, - membersPage: { - detailsTitle__emptyRow: 'No members to display', - action__invite: 'Invite', - start: { - headerTitle__members: 'Members', - headerTitle__invitations: 'Invitations', - headerTitle__requests: 'Requests', - }, - activeMembersTab: { - tableHeader__user: 'User', - tableHeader__joined: 'Joined', - tableHeader__role: 'Role', - tableHeader__actions: '', - menuAction__remove: 'Remove member', - }, - invitedMembersTab: { - tableHeader__invited: 'Invited', - menuAction__revoke: 'Revoke invitation', - }, - invitationsTab: { - table__emptyRow: 'No invitations to display', - manualInvitations: { - headerTitle: 'Individual invitations', - headerSubtitle: 'Manually invite members and manage existing invitations.', - }, - autoInvitations: { - headerTitle: 'Automatic invitations', - headerSubtitle: - 'Invite users by connecting an email domain with your organization. Anyone who signs up with a matching email domain will be able to join the organization anytime.', - primaryButton: 'Manage verified domains', - }, - }, - requestsTab: { - tableHeader__requested: 'Requested access', - menuAction__approve: 'Approve', - menuAction__reject: 'Reject', - table__emptyRow: 'No requests to display', - requests: { - headerTitle: 'Requests', - headerSubtitle: 'Browse and manage users who requested to join the organization.', - }, - autoSuggestions: { - headerTitle: 'Automatic suggestions', - headerSubtitle: - 'Users who sign up with a matching email domain, will be able to see a suggestion to request to join your organization.', - primaryButton: 'Manage verified domains', - }, - }, - }, - }, - createOrganization: { - title: 'Create Organization', - formButtonSubmit: 'Create organization', - invitePage: { - formButtonReset: 'Skip', - }, - }, - organizationList: { - createOrganization: 'Create Organization', - title: 'Select an account', - titleWithoutPersonal: 'Select an organization', - subtitle: 'to continue to {{applicationName}}', - action__invitationAccept: 'Join', - invitationAcceptedLabel: 'Joined', - action__suggestionsAccept: 'Request to join', - suggestionsAcceptedLabel: 'Pending approval', - action__createOrganization: 'Create organization', - }, - unstable__errors: { - identification_deletion_failed: 'You cannot delete your last identification.', - phone_number_exists: 'This phone number is taken. Please try another.', - form_identifier_not_found: '', - captcha_unavailable: - 'Sign up unsuccessful due to failed bot validation. Please refresh the page to try again or reach out to support for more assistance.', - captcha_invalid: - 'Sign up unsuccessful due to failed security validations. Please refresh the page to try again or reach out to support for more assistance.', - form_password_pwned: - 'This password has been found as part of a breach and can not be used, please try another password instead.', - form_username_invalid_length: '', - form_username_invalid_character: '', - form_param_format_invalid: '', - form_param_format_invalid__phone_number: 'Phone number must be in a valid international format', - form_param_format_invalid__email_address: 'Email address must be a valid email address.', - form_password_length_too_short: '', - form_param_nil: '', - form_code_incorrect: '', - form_password_incorrect: '', - not_allowed_access: '', - form_identifier_exists: '', - form_password_validation_failed: 'Incorrect Password', - form_password_not_strong_enough: 'Your password is not strong enough.', - form_password_size_in_bytes_exceeded: - 'Your password has exceeded the maximum number of bytes allowed, please shorten it or remove some special characters.', - passwordComplexity: { - sentencePrefix: 'Your password must contain', - minimumLength: '{{length}} or more characters', - maximumLength: 'less than {{length}} characters', - requireNumbers: 'a number', - requireLowercase: 'a lowercase letter', - requireUppercase: 'an uppercase letter', - requireSpecialCharacter: 'a special character', - }, - zxcvbn: { - notEnough: 'Your password is not strong enough.', - couldBeStronger: 'Your password works, but could be stronger. Try adding more characters.', - goodPassword: 'Your password meets all the necessary requirements.', - warnings: { - straightRow: 'Straight rows of keys on your keyboard are easy to guess.', - keyPattern: 'Short keyboard patterns are easy to guess.', - simpleRepeat: 'Repeated characters like "aaa" are easy to guess.', - extendedRepeat: 'Repeated character patterns like "abcabcabc" are easy to guess.', - sequences: 'Common character sequences like "abc" are easy to guess.', - recentYears: 'Recent years are easy to guess.', - dates: 'Dates are easy to guess.', - topTen: 'This is a heavily used password.', - topHundred: 'This is a frequently used password.', - common: 'This is a commonly used password.', - similarToCommon: 'This is similar to a commonly used password.', - wordByItself: 'Single words are easy to guess.', - namesByThemselves: 'Single names or surnames are easy to guess.', - commonNames: 'Common names and surnames are easy to guess.', - userInputs: 'There should not be any personal or page related data.', - pwned: 'Your password was exposed by a data breach on the Internet.', - }, - suggestions: { - l33t: "Avoid predictable letter substitutions like '@' for 'a'.", - reverseWords: 'Avoid reversed spellings of common words.', - allUppercase: 'Capitalize some, but not all letters.', - capitalization: 'Capitalize more than the first letter.', - dates: 'Avoid dates and years that are associated with you.', - recentYears: 'Avoid recent years.', - associatedYears: 'Avoid years that are associated with you.', - sequences: 'Avoid common character sequences.', - repeated: 'Avoid repeated words and characters.', - longerKeyboardPattern: 'Use longer keyboard patterns and change typing direction multiple times.', - anotherWord: 'Add more words that are less common.', - useWords: 'Use multiple words, but avoid common phrases.', - noNeed: 'You can create strong passwords without using symbols, numbers, or uppercase letters.', - pwned: 'If you use this password elsewhere, you should change it.', - }, - }, - form_param_max_length_exceeded__name: 'Name should not exceed 256 characters.', - form_param_max_length_exceeded__first_name: 'First name should not exceed 256 characters.', - form_param_max_length_exceeded__last_name: 'Last name should not exceed 256 characters.', - }, - dates: { - previous6Days: "Last {{ date | weekday('en-US','long') }} at {{ date | timeString('en-US') }}", - lastDay: "Yesterday at {{ date | timeString('en-US') }}", - sameDay: "Today at {{ date | timeString('en-US') }}", - nextDay: "Tomorrow at {{ date | timeString('en-US') }}", - next6Days: "{{ date | weekday('en-US','long') }} at {{ date | timeString('en-US') }}", - numeric: "{{ date | numeric('en-US') }}", - }, -} as const; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 38679e5c980..3397216d14a 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -246,13 +246,16 @@ export const enUS: LocalizationResource = { formButtonPrimary__continue: 'Continue', formButtonPrimary__finish: 'Finish', formButtonReset: 'Cancel', + navbar: { + title: 'Account', + description: 'Manage your account info.', + }, start: { - headerTitle__account: 'Account', + headerTitle__account: 'Profile details', headerTitle__security: 'Security', - headerSubtitle__account: 'Manage your account information', - headerSubtitle__security: 'Manage your security preferences', profileSection: { title: 'Profile', + primaryButton: 'Edit Profile', }, usernameSection: { title: 'Username', @@ -528,7 +531,7 @@ export const enUS: LocalizationResource = { personalWorkspace: 'Personal account', notSelected: 'No organization selected', action__createOrganization: 'Create Organization', - action__manageOrganization: 'Manage Organization', + action__manageOrganization: 'Manage', action__invitationAccept: 'Join', action__suggestionsAccept: 'Request to join', suggestionsAcceptedLabel: 'Pending approval', @@ -542,11 +545,13 @@ export const enUS: LocalizationResource = { badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', + navbar: { + title: 'Organization', + description: 'Manage your organization.', + }, start: { headerTitle__members: 'Members', headerTitle__settings: 'Settings', - headerSubtitle__members: 'View and manage organization members', - headerSubtitle__settings: 'Manage organization settings', }, profilePage: { title: 'Organization Profile', diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 72170cddbf6..00c07a3b64a 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -205,8 +205,6 @@ export const esES: LocalizationResource = { start: { headerTitle__account: 'Cuenta', headerTitle__security: 'Seguridad', - headerSubtitle__account: 'Administrar la información de su cuenta', - headerSubtitle__security: 'Administra tus preferencias de seguridad', profileSection: { title: 'Perfil', }, @@ -481,8 +479,6 @@ export const esES: LocalizationResource = { start: { headerTitle__members: 'Miembros', headerTitle__settings: 'Configuración', - headerSubtitle__members: 'Ver y administrar los miembros de la organización', - headerSubtitle__settings: 'Administrar la configuración de la organización', }, profilePage: { title: 'Perfil de la organización', diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index a4ba6167bb1..0c20649c531 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -238,8 +238,6 @@ export const frFR: LocalizationResource = { start: { headerTitle__account: 'Compte', headerTitle__security: 'Sécurité', - headerSubtitle__account: 'Gérer les informations de votre compte', - headerSubtitle__security: 'Gérer vos préférences de sécurité', profileSection: { title: 'Profil', }, @@ -529,8 +527,6 @@ export const frFR: LocalizationResource = { start: { headerTitle__members: 'Membres', headerTitle__settings: 'Réglages', - headerSubtitle__members: "Afficher et gérer les membres de l'organisation", - headerSubtitle__settings: "Gérer les paramètres de l'organisation", }, profilePage: { title: 'Profil de l’organisation', diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 3641280fd1c..9c24fd0289e 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -235,8 +235,6 @@ export const heIL: LocalizationResource = { start: { headerTitle__account: 'חשבון', headerTitle__security: 'אבטחה', - headerSubtitle__account: 'נהל את מידע החשבון שלך', - headerSubtitle__security: 'נהל את העדפות האבטחה שלך', profileSection: { title: 'פרופיל', }, @@ -505,8 +503,6 @@ export const heIL: LocalizationResource = { start: { headerTitle__members: 'חברים', headerTitle__settings: 'הגדרות', - headerSubtitle__members: 'הצג ונהל את חברי הארגון', - headerSubtitle__settings: 'נהל הגדרות ארגון', }, profilePage: { title: 'פרופיל ארגון', diff --git a/packages/localizations/src/index.retheme.ts b/packages/localizations/src/index.retheme.ts deleted file mode 100644 index 2a2f339df5f..00000000000 --- a/packages/localizations/src/index.retheme.ts +++ /dev/null @@ -1 +0,0 @@ -export { enUS } from './en-US.retheme'; diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 57033557fed..98ffb9834d4 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -205,8 +205,6 @@ export const itIT: LocalizationResource = { start: { headerTitle__account: 'Account', headerTitle__security: 'Sicurezza', - headerSubtitle__account: 'Gestisci le informazioni del tuo account', - headerSubtitle__security: 'Gestisci le tue preferenze di sicurezza', profileSection: { title: 'Profilo', }, @@ -476,8 +474,6 @@ export const itIT: LocalizationResource = { start: { headerTitle__members: 'Membri', headerTitle__settings: 'Impostazioni', - headerSubtitle__members: "Consulta e gestisci i membri dell'organizzazione", - headerSubtitle__settings: "Gestisci le impostazioni dell'organizzazione", }, profilePage: { title: "Profilo dell'organizzazione", diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index c2d1720763f..9e37bcf2721 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -236,8 +236,6 @@ export const jaJP: LocalizationResource = { start: { headerTitle__account: 'アカウント', headerTitle__security: 'セキュリティ', - headerSubtitle__account: 'アカウント情報の管理', - headerSubtitle__security: 'セキュリティ設定の管理', profileSection: { title: 'プロフィール', }, @@ -504,8 +502,6 @@ export const jaJP: LocalizationResource = { start: { headerTitle__members: 'メンバー', headerTitle__settings: '設定', - headerSubtitle__members: '組織のメンバーを表示および管理する', - headerSubtitle__settings: '組織の設定を管理する', }, profilePage: { title: '組織プロフィール', diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 0348d3d4b4d..ba63bb964cc 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -235,8 +235,6 @@ export const koKR: LocalizationResource = { start: { headerTitle__account: '계정', headerTitle__security: '보안', - headerSubtitle__account: '계정 정보 관리', - headerSubtitle__security: '보안 환경설정 관리', profileSection: { title: '프로필', }, @@ -513,8 +511,6 @@ export const koKR: LocalizationResource = { start: { headerTitle__members: '멤버', headerTitle__settings: '설정', - headerSubtitle__members: '조직 구성원 보기 및 관리', - headerSubtitle__settings: '조직 설정 관리', }, profilePage: { title: '조직 프로필', diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index c3850742d0b..576460489c6 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -235,8 +235,6 @@ export const nbNO: LocalizationResource = { start: { headerTitle__account: 'Konto', headerTitle__security: 'Sikkerhet', - headerSubtitle__account: 'Administrer kontoinformasjonen din', - headerSubtitle__security: 'Administrer sikkerhetsinnstillingene dine', profileSection: { title: 'Profil', }, @@ -526,8 +524,6 @@ export const nbNO: LocalizationResource = { start: { headerTitle__members: 'Medlemmer', headerTitle__settings: 'Innstillinger', - headerSubtitle__members: 'Vis og administrer organisasjonsmedlemmer', - headerSubtitle__settings: 'Administrer organisasjonsinnstillinger', }, profilePage: { title: 'Organisasjonsprofil', diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index fe68877d170..7945956a977 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -203,8 +203,6 @@ export const nlNL: LocalizationResource = { start: { headerTitle__account: 'Account', headerTitle__security: 'Beveiliging', - headerSubtitle__account: 'Beheer uw accountinformatie', - headerSubtitle__security: 'Beheer uw veiligheidsvoorkeuren', profileSection: { title: 'Profiel', }, @@ -470,8 +468,6 @@ export const nlNL: LocalizationResource = { start: { headerTitle__members: 'Leden', headerTitle__settings: 'Instellingen', - headerSubtitle__members: 'Organisatieleden bekijken en beheren', - headerSubtitle__settings: 'Organisatie-instellingen beheren', }, profilePage: { title: 'Organisatieprofiel', diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 202327f1d18..40bea2f9f4c 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -211,8 +211,6 @@ export const plPL: LocalizationResource = { start: { headerTitle__account: 'Konto', headerTitle__security: 'Bezpieczeństwo', - headerSubtitle__account: 'Zarządzaj swoimi informacjami o koncie', - headerSubtitle__security: 'Zarządzaj swoimi preferencjami dotyczącymi bezpieczeństwa', profileSection: { title: 'Profil', }, @@ -498,8 +496,6 @@ export const plPL: LocalizationResource = { start: { headerTitle__members: 'Członkowie', headerTitle__settings: 'Ustawienia', - headerSubtitle__members: 'Wyświetl i zarządzaj członkami organizacji', - headerSubtitle__settings: 'Zarządzaj ustawieniami organizacji', }, profilePage: { title: 'Profil organizacji', diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 7f0794a2d8c..d4b7909fdd5 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -247,8 +247,6 @@ export const ptBR: LocalizationResource = { start: { headerTitle__account: 'Conta', headerTitle__security: 'Segurança', - headerSubtitle__account: 'Gerencie suas informações de conta', - headerSubtitle__security: 'Gerencie suas preferencias de segurança', profileSection: { title: 'Perfil', }, @@ -546,8 +544,6 @@ export const ptBR: LocalizationResource = { start: { headerTitle__members: 'Membros', headerTitle__settings: 'Configurações', - headerSubtitle__members: 'Veja e gerencie os membros da organização', - headerSubtitle__settings: 'Gerencie as configurações da organização', }, profilePage: { title: 'Perfil da organização', diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 2518b43302f..5a41a647126 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -242,8 +242,6 @@ export const ptBR: LocalizationResource = { start: { headerTitle__account: 'Conta', headerTitle__security: 'Segurança', - headerSubtitle__account: 'Configurar as informações da conta', - headerSubtitle__security: 'Configurar as preferências de segurança', profileSection: { title: 'Perfil', }, @@ -539,8 +537,6 @@ export const ptBR: LocalizationResource = { start: { headerTitle__members: 'Membros', headerTitle__settings: 'Configurações', - headerSubtitle__members: 'Veja e configure os membros da organização', - headerSubtitle__settings: 'Configure as configurações da organização', }, profilePage: { title: 'Perfil da organização', diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index 9d4d197f328..7fcd742d9f8 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -250,8 +250,6 @@ export const roRO: LocalizationResource = { start: { headerTitle__account: 'Cont', headerTitle__security: 'Securitate', - headerSubtitle__account: 'Gestionați informațiile contului dvs', - headerSubtitle__security: 'Gestionați-vă preferințele de securitate', profileSection: { title: 'Profil', }, @@ -554,8 +552,6 @@ export const roRO: LocalizationResource = { start: { headerTitle__members: 'Membri', headerTitle__settings: 'Setări', - headerSubtitle__members: 'Vizualizați și gestionați membrii organizației', - headerSubtitle__settings: 'Gestionați setările organizației', }, profilePage: { title: 'Profilul organizației', diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index af2eef6f501..9bdc3728635 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -233,8 +233,6 @@ export const ruRU: LocalizationResource = { start: { headerTitle__account: 'Аккаунт', headerTitle__security: 'Безопасность', - headerSubtitle__account: 'Управление информацией об аккаунте', - headerSubtitle__security: 'Управление настройками безопасности', profileSection: { title: 'Профиль', }, @@ -509,8 +507,6 @@ export const ruRU: LocalizationResource = { start: { headerTitle__members: 'Участники', headerTitle__settings: 'Настройки', - headerSubtitle__members: 'Просмотр и управление участниками организации', - headerSubtitle__settings: 'Управление настройками организации', }, profilePage: { title: 'Профиль организации', diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 64ccc416265..74b6fcd1965 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -236,8 +236,6 @@ export const skSK: LocalizationResource = { start: { headerTitle__account: 'Účet', headerTitle__security: 'Bezpečnosť', - headerSubtitle__account: 'Spravujte svoje údaje o účte', - headerSubtitle__security: 'Spravujte svoje bezpečnostné nastavenia', profileSection: { title: 'Profil', }, @@ -509,8 +507,6 @@ export const skSK: LocalizationResource = { start: { headerTitle__members: 'Členovia', headerTitle__settings: 'Nastavenia', - headerSubtitle__members: 'Zobraziť a spravovať členov organizácie', - headerSubtitle__settings: 'Spravovať nastavenia organizácie', }, profilePage: { title: 'Profil organizácie', diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index d6df2444643..f66d26147d6 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -206,8 +206,6 @@ export const svSE: LocalizationResource = { start: { headerTitle__account: 'Konto', headerTitle__security: 'Säkerhet', - headerSubtitle__account: 'Hantera din kontoinformation', - headerSubtitle__security: 'Hantera dina säkerhetsinställningar', profileSection: { title: 'Profil', }, @@ -485,8 +483,6 @@ export const svSE: LocalizationResource = { start: { headerTitle__members: 'Medlemmar', headerTitle__settings: 'Inställningar', - headerSubtitle__members: 'Visa och hantera organisationsmedlemmar', - headerSubtitle__settings: 'Hantera organisationsinställningar', }, profilePage: { title: 'Organisationsprofil', diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index e04c2738292..1451a1545ac 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -235,8 +235,6 @@ export const trTR: LocalizationResource = { start: { headerTitle__account: 'Hesap', headerTitle__security: 'Güvenlik', - headerSubtitle__account: 'Hesap bilgilerini yönet', - headerSubtitle__security: 'Güvenlik ayarlarını yönet', profileSection: { title: 'Profil', }, @@ -511,8 +509,6 @@ export const trTR: LocalizationResource = { start: { headerTitle__members: 'Üyeler', headerTitle__settings: 'Ayarlar', - headerSubtitle__members: 'Organizasyon üyelerini yönet', - headerSubtitle__settings: 'Organizasyon ayarlarını yönet', }, profilePage: { title: 'Organizasyon profili', diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 07ba57c37e3..046a1ff47d9 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -236,8 +236,6 @@ export const ukUA: LocalizationResource = { start: { headerTitle__account: 'Обліковий запис', headerTitle__security: 'Безпека', - headerSubtitle__account: 'Керування інформацією про обліковий запис', - headerSubtitle__security: 'Керування налаштуваннями безпеки', profileSection: { title: 'Профіль', }, @@ -522,8 +520,6 @@ export const ukUA: LocalizationResource = { start: { headerTitle__members: 'Учасники', headerTitle__settings: 'Налаштування', - headerSubtitle__members: 'Перегляд і управління учасниками організації', - headerSubtitle__settings: 'Управління налаштуваннями організації', }, profilePage: { title: 'Профіль організації', diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 98c0c18b186..2b93d96e245 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -235,8 +235,6 @@ export const viVN: LocalizationResource = { start: { headerTitle__account: 'Tài khoản', headerTitle__security: 'Bảo mật', - headerSubtitle__account: 'Quản lý thông tin tài khoản của bạn', - headerSubtitle__security: 'Quản lý các tùy chọn bảo mật', profileSection: { title: 'Hồ sơ', }, @@ -524,8 +522,6 @@ export const viVN: LocalizationResource = { start: { headerTitle__members: 'Thành viên', headerTitle__settings: 'Cài đặt', - headerSubtitle__members: 'Xem và quản lý thành viên tổ chức', - headerSubtitle__settings: 'Quản lý cài đặt tổ chức', }, profilePage: { title: 'Hồ sơ Tổ chức', diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 922ecd8fbdb..9c865b8f646 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -234,8 +234,6 @@ export const zhCN: LocalizationResource = { start: { headerTitle__account: '账户', headerTitle__security: '安全', - headerSubtitle__account: '管理您的账户信息', - headerSubtitle__security: '管理您的安全设置', profileSection: { title: '个人资料', }, @@ -496,8 +494,6 @@ export const zhCN: LocalizationResource = { start: { headerTitle__members: '成员', headerTitle__settings: '设置', - headerSubtitle__members: '查看并管理组织成员', - headerSubtitle__settings: '管理组织设置', }, profilePage: { title: '组织简介', diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 19e090c8481..fc995046a44 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -235,8 +235,6 @@ export const zhTW: LocalizationResource = { start: { headerTitle__account: '帳戶', headerTitle__security: '安全', - headerSubtitle__account: '管理您的帳戶資訊', - headerSubtitle__security: '管理您的安全設定', profileSection: { title: '個人資料', }, @@ -510,8 +508,6 @@ export const zhTW: LocalizationResource = { start: { headerTitle__members: '成員', headerTitle__settings: '設置', - headerSubtitle__members: '查看並管理組織成員', - headerSubtitle__settings: '管理組織設置', }, profilePage: { title: '組織簡介', diff --git a/packages/localizations/tsup.config.ts b/packages/localizations/tsup.config.ts index 020096d7747..d5862ed9816 100644 --- a/packages/localizations/tsup.config.ts +++ b/packages/localizations/tsup.config.ts @@ -1,68 +1,36 @@ import { defineConfig } from 'tsup'; export default defineConfig(_overrideOptions => { - const uiRetheme = process.env.CLERK_RETHEME === '1' || process.env.CLERK_RETHEME === 'true'; - return { - entry: uiRetheme - ? { - index: 'src/index.retheme.ts', - 'en-US': 'src/en-US.retheme.ts', - 'ar-SA': 'src/en-US.retheme.ts', - 'cs-CZ': 'src/en-US.retheme.ts', - 'da-DK': 'src/en-US.retheme.ts', - 'de-DE': 'src/en-US.retheme.ts', - 'el-GR': 'src/en-US.retheme.ts', - 'es-ES': 'src/en-US.retheme.ts', - 'fr-FR': 'src/en-US.retheme.ts', - 'he-IL': 'src/en-US.retheme.ts', - 'it-IT': 'src/en-US.retheme.ts', - 'ja-JP': 'src/en-US.retheme.ts', - 'ko-KR': 'src/en-US.retheme.ts', - 'nb-NO': 'src/en-US.retheme.ts', - 'nl-NL': 'src/en-US.retheme.ts', - 'pl-PL': 'src/en-US.retheme.ts', - 'pt-BR': 'src/en-US.retheme.ts', - 'pt-PT': 'src/en-US.retheme.ts', - 'ro-RO': 'src/en-US.retheme.ts', - 'ru-RU': 'src/en-US.retheme.ts', - 'sk-SK': 'src/en-US.retheme.ts', - 'sv-SE': 'src/en-US.retheme.ts', - 'tr-TR': 'src/en-US.retheme.ts', - 'uk-UA': 'src/en-US.retheme.ts', - 'vi-VN': 'src/en-US.retheme.ts', - 'zh-CN': 'src/en-US.retheme.ts', - 'zh-TW': 'src/en-US.retheme.ts', - } - : { - index: 'src/index.ts', - 'en-US': 'src/en-US.ts', - 'ar-SA': 'src/ar-SA.ts', - 'cs-CZ': 'src/cs-CZ.ts', - 'da-DK': 'src/da-DK.ts', - 'de-DE': 'src/de-DE.ts', - 'el-GR': 'src/el-GR.ts', - 'es-ES': 'src/es-ES.ts', - 'fr-FR': 'src/fr-FR.ts', - 'he-IL': 'src/he-IL.ts', - 'it-IT': 'src/it-IT.ts', - 'ja-JP': 'src/ja-JP.ts', - 'ko-KR': 'src/ko-KR.ts', - 'nb-NO': 'src/nb-NO.ts', - 'nl-NL': 'src/nl-NL.ts', - 'pl-PL': 'src/pl-PL.ts', - 'pt-BR': 'src/pt-BR.ts', - 'pt-PT': 'src/pt-PT.ts', - 'ro-RO': 'src/ro-RO.ts', - 'ru-RU': 'src/ru-RU.ts', - 'sk-SK': 'src/sk-SK.ts', - 'sv-SE': 'src/sv-SE.ts', - 'tr-TR': 'src/tr-TR.ts', - 'uk-UA': 'src/uk-UA.ts', - 'vi-VN': 'src/vi-VN.ts', - 'zh-CN': 'src/zh-CN.ts', - 'zh-TW': 'src/zh-TW.ts', - }, + entry: { + index: 'src/index.ts', + 'en-US': 'src/en-US.ts', + 'ar-SA': 'src/ar-SA.ts', + 'cs-CZ': 'src/cs-CZ.ts', + 'da-DK': 'src/da-DK.ts', + 'de-DE': 'src/de-DE.ts', + 'el-GR': 'src/el-GR.ts', + 'es-ES': 'src/es-ES.ts', + 'fr-FR': 'src/fr-FR.ts', + 'he-IL': 'src/he-IL.ts', + 'it-IT': 'src/it-IT.ts', + 'ja-JP': 'src/ja-JP.ts', + 'ko-KR': 'src/ko-KR.ts', + 'nb-NO': 'src/nb-NO.ts', + 'nl-NL': 'src/nl-NL.ts', + 'pl-PL': 'src/pl-PL.ts', + 'pt-BR': 'src/pt-BR.ts', + 'pt-PT': 'src/pt-PT.ts', + 'ro-RO': 'src/ro-RO.ts', + 'ru-RU': 'src/ru-RU.ts', + 'sk-SK': 'src/sk-SK.ts', + 'sv-SE': 'src/sv-SE.ts', + 'tr-TR': 'src/tr-TR.ts', + 'uk-UA': 'src/uk-UA.ts', + 'vi-VN': 'src/vi-VN.ts', + 'zh-CN': 'src/zh-CN.ts', + 'zh-TW': 'src/zh-TW.ts', + }, format: ['cjs', 'esm'], bundle: true, clean: true, diff --git a/packages/types/src/appearance.retheme.ts b/packages/types/src/appearance.retheme.ts deleted file mode 100644 index bb7b9ea8a64..00000000000 --- a/packages/types/src/appearance.retheme.ts +++ /dev/null @@ -1,657 +0,0 @@ -import type * as CSS from 'csstype'; - -import type { - AlertId, - FieldId, - FooterActionId, - MenuId, - OrganizationPreviewId, - ProfilePageId, - ProfileSectionId, - SelectId, - UserPreviewId, -} from './elementIds'; -import type { OAuthProvider } from './oauth'; -import type { SamlIdpSlug } from './saml'; -import type { BuiltInColors, TransparentColor } from './theme'; -import type { Web3Provider } from './web3'; - -type CSSProperties = CSS.PropertiesFallback; -type CSSPropertiesWithMultiValues = { [K in keyof CSSProperties]: CSSProperties[K] }; -type CSSPseudos = { [K in CSS.Pseudos as `&${K}`]?: CSSObject }; - -interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos {} - -type UserDefinedStyle = string | CSSObject; - -type Shade = '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; -export type ColorScale = Record; -export type AlphaColorScale = Record<'20' | Shade, T>; - -export type ColorScaleWithRequiredBase = Partial> & { '500': T }; - -export type CssColorOrScale = string | ColorScaleWithRequiredBase; -export type CssColorOrAlphaScale = string | AlphaColorScale; -type CssColor = string | TransparentColor | BuiltInColors; -type CssLengthUnit = string; -type FontSmoothing = 'auto' | 'antialiased' | 'never'; - -type FontWeightNamedValue = CSS.Properties['fontWeight']; -type FontWeightNumericValue = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; - -type FontWeightScale = { - normal?: FontWeightNamedValue | FontWeightNumericValue; - medium?: FontWeightNamedValue | FontWeightNumericValue; - bold?: FontWeightNamedValue | FontWeightNumericValue; -}; - -type WebSafeFont = - | 'Arial' - | 'Brush Script MT' - | 'Courier New' - | 'Garamond' - | 'Georgia' - | 'Helvetica' - | 'Tahoma' - | 'Times New Roman' - | 'Trebuchet MS' - | 'Verdana'; - -export type FontFamily = string | WebSafeFont; - -type LoadingState = 'loading'; -type ErrorState = 'error'; -type OpenState = 'open'; -type ActiveState = 'active'; -export type ElementState = LoadingState | ErrorState | OpenState | ActiveState; -type ControlState = ErrorState; - -/** - * A type that describes the states and the ids that we will combine - * in order to create all theming combinations - * If jsx exists, the element can also receive a typed function that returns a JSX.Element - */ -type ConfigOptions = { states: ElementState; ids: string; jsx: any }; -type WithOptions = { ids: Ids; states: States; jsx: Jsx }; - -/** - * Create a type union of all state + id combinations - */ -export type StateSelectors = S extends never - ? never - : `${E}__${S}`; - -/** - * Create a type union consisting of the base element with all valid ids appended - */ -export type IdSelectors = Id extends never - ? never - : `${E}__${Id}`; - -/** - * Create a type union consisting of all base, base+state, base+id, base+id+state combinations - */ -type ElementPartsKeys = - | StateSelectors - | IdSelectors - | StateSelectors, Opts['states']>; - -/** - * Create an object type mapping base elements and part combinations (base, base+state, base+id, base+id+state) - * to the value they can accept (usually a style rule, a string class or jsx) - */ -type Selectors = - | Partial> - | Partial, UserDefinedStyle>>; - -/** - * Convert a kebab-cased key from ElementsConfig into a camelCased Elements key - */ -export type ElementObjectKey = K extends `${infer Parent}-${infer Rest}` - ? `${Parent}${Capitalize}` - : K; - -/** - * A map that describes the possible combinations we need to generate - * for each unique base element - * Kebab-case is used to differentiate between the container and child elements - */ -export type ElementsConfig = { - rootBox: WithOptions; - card: WithOptions; - cardContent: WithOptions; - cardFooter: WithOptions; - cardFooterItem: WithOptions; - - logoBox: WithOptions; - logoImage: WithOptions; - // outerLogo: WithOptions; - // 'outerLogo-image': WithOptions; - - header: WithOptions; - headerTitle: WithOptions; - headerSubtitle: WithOptions; - - backRow: WithOptions; - backLink: WithOptions; - - main: WithOptions; - - footer: WithOptions; - footerAction: WithOptions; - footerActionText: WithOptions; - footerActionLink: WithOptions; - footerPages: WithOptions; - footerPagesLink: WithOptions<'help' | 'terms' | 'privacy'>; - - socialButtons: WithOptions; - socialButtonsIconButton: WithOptions; - socialButtonsBlockButton: WithOptions; - socialButtonsBlockButtonText: WithOptions; - socialButtonsBlockButtonArrow: WithOptions; - socialButtonsProviderIcon: WithOptions; - - enterpriseButtonsProviderIcon: WithOptions; - - alternativeMethods: WithOptions; - alternativeMethodsBlockButton: WithOptions; - alternativeMethodsBlockButtonText: WithOptions; - alternativeMethodsBlockButtonArrow: WithOptions; - - otpCodeBox: WithOptions; - otpCodeHeader: WithOptions; - otpCodeHeaderTitle: WithOptions; - otpCodeHeaderSubtitle: WithOptions; - otpCodeField: WithOptions; - otpCodeFieldInputs: WithOptions; - otpCodeFieldInput: WithOptions; - otpCodeFieldErrorText: WithOptions; - - dividerRow: WithOptions; - dividerText: WithOptions; - dividerLine: WithOptions; - - formHeader: WithOptions; - formHeaderTitle: WithOptions; - formHeaderSubtitle: WithOptions; - formResendCodeLink: WithOptions; - - verificationLinkStatusBox: WithOptions; - verificationLinkStatusIconBox: WithOptions; - verificationLinkStatusIcon: WithOptions; - verificationLinkStatusText: WithOptions; - - form: WithOptions; - formFieldRow: WithOptions; - formField: WithOptions; - formFieldLabelRow: WithOptions; - formFieldLabel: WithOptions; - formFieldRadioGroup: WithOptions; - formFieldRadioGroupItem: WithOptions; - formFieldRadioInput: WithOptions; - formFieldRadioLabel: WithOptions; - formFieldRadioLabelTitle: WithOptions; - formFieldRadioLabelDescription: WithOptions; - formFieldAction: WithOptions; - formFieldInput: WithOptions; - formFieldErrorText: WithOptions; - formFieldWarningText: WithOptions; - formFieldSuccessText: WithOptions; - formFieldInfoText: WithOptions; - formFieldDirectionsText: WithOptions; - formFieldHintText: WithOptions; - formButtonRow: WithOptions; - formButtonPrimary: WithOptions; - formButtonReset: WithOptions; - formFieldInputGroup: WithOptions; - formFieldInputShowPasswordButton: WithOptions; - formFieldInputShowPasswordIcon: WithOptions; - formFieldInputCopyToClipboardButton: WithOptions; - formFieldInputCopyToClipboardIcon: WithOptions; - - phoneInputBox: WithOptions; - formInputGroup: WithOptions; - - avatarBox: WithOptions; - avatarImage: WithOptions; - avatarImageActions: WithOptions; - avatarImageActionsUpload: WithOptions; - avatarImageActionsRemove: WithOptions; - - // TODO: We can remove "Popover" from these: - userButtonBox: WithOptions; - userButtonOuterIdentifier: WithOptions; - userButtonTrigger: WithOptions; - userButtonAvatarBox: WithOptions; - userButtonAvatarImage: WithOptions; - userButtonPopoverRootBox: WithOptions; - userButtonPopoverCard: WithOptions; - userButtonPopoverMain: WithOptions; - userButtonPopoverUserPreview: WithOptions; - userButtonPopoverActions: WithOptions<'singleSession' | 'multiSession'>; - userButtonPopoverActionButton: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; - userButtonPopoverActionButtonIconBox: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; - userButtonPopoverActionButtonIcon: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; - userButtonPopoverFooter: WithOptions; - userButtonPopoverFooterPages: WithOptions; - userButtonPopoverFooterPagesLink: WithOptions<'terms' | 'privacy'>; - - organizationSwitcherTrigger: WithOptions; - organizationSwitcherTriggerIcon: WithOptions; - organizationSwitcherPopoverRootBox: WithOptions; - organizationSwitcherPopoverCard: WithOptions; - organizationSwitcherPopoverMain: WithOptions; - organizationSwitcherPopoverActions: WithOptions; - organizationSwitcherPopoverInvitationActions: WithOptions; - organizationSwitcherPopoverActionButton: WithOptions< - 'manageOrganization' | 'createOrganization' | 'switchOrganization', - never, - never - >; - organizationSwitcherPreviewButton: WithOptions; - organizationSwitcherInvitationAcceptButton: WithOptions; - organizationSwitcherInvitationRejectButton: WithOptions; - organizationSwitcherPopoverActionButtonIconBox: WithOptions< - 'manageOrganization' | 'createOrganization', - never, - never - >; - organizationSwitcherPopoverActionButtonIcon: WithOptions<'manageOrganization' | 'createOrganization'>; - organizationSwitcherPopoverActionButtonText: WithOptions<'manageOrganization' | 'createOrganization'>; - organizationSwitcherPopoverFooter: WithOptions; - organizationSwitcherPopoverFooterPages: WithOptions; - organizationSwitcherPopoverFooterPagesLink: WithOptions<'terms' | 'privacy'>; - - organizationListPreviewItems: WithOptions; - organizationListPreviewItem: WithOptions; - organizationListPreviewButton: WithOptions; - organizationListPreviewItemActionButton: WithOptions; - - // TODO: Test this idea. Instead of userButtonUserPreview, have a userPreview__userButton instead - // Same for other repeated selectors, eg avatar - userPreview: WithOptions; - userPreviewAvatarContainer: WithOptions; - userPreviewAvatarBox: WithOptions; - userPreviewAvatarImage: WithOptions; - userPreviewAvatarIcon: WithOptions; - userPreviewTextContainer: WithOptions; - userPreviewMainIdentifier: WithOptions; - userPreviewSecondaryIdentifier: WithOptions; - - organizationPreview: WithOptions; - organizationPreviewAvatarContainer: WithOptions; - organizationPreviewAvatarBox: WithOptions; - organizationPreviewAvatarImage: WithOptions; - organizationPreviewTextContainer: WithOptions; - organizationPreviewMainIdentifier: WithOptions; - organizationPreviewSecondaryIdentifier: WithOptions; - - membersPageInviteButton: WithOptions; - organizationProfilePage: WithOptions; - - identityPreview: WithOptions; - identityPreviewAvatarBox: WithOptions; - identityPreviewAvatarImage: WithOptions; - identityPreviewText: WithOptions; - identityPreviewEditButton: WithOptions; - identityPreviewEditButtonIcon: WithOptions; - - alert: WithOptions; - alertIcon: WithOptions; - alertText: WithOptions; - alertTextContainer: WithOptions; - - tagInputContainer: WithOptions; - tagPillIcon: WithOptions; - tagPillContainer: WithOptions; - - tabPanel: WithOptions; - tabButton: WithOptions; - tabListContainer: WithOptions; - - tableHead: WithOptions; - - paginationButton: WithOptions; - paginationRowText: WithOptions<'allRowsCount' | 'rowsCount' | 'displaying'>; - - selectButton: WithOptions; - selectSearchInput: WithOptions; - selectButtonIcon: WithOptions; - selectOptionsContainer: WithOptions; - selectOption: WithOptions; - - menuButton: WithOptions; - menuList: WithOptions; - menuItem: WithOptions; - - loader: WithOptions; - loaderIcon: WithOptions; - - modalBackdrop: WithOptions; - modalContent: WithOptions; - modalCloseButton: WithOptions; - - profileSection: WithOptions; - profileSectionTitle: WithOptions; - profileSectionTitleText: WithOptions; - profileSectionSubtitle: WithOptions; - profileSectionSubtitleText: WithOptions; - profileSectionContent: WithOptions; - profileSectionPrimaryButton: WithOptions; - profilePage: WithOptions; - - // TODO: review - formattedPhoneNumber: WithOptions; - formattedPhoneNumberFlag: WithOptions; - formattedPhoneNumberText: WithOptions; - - breadcrumbs: WithOptions; - breadcrumbsItems: WithOptions; - breadcrumbsItemBox: WithOptions<'currentPage'>; - breadcrumbsItem: WithOptions<'currentPage'>; - breadcrumbsItemIcon: WithOptions<'currentPage'>; - breadcrumbsItemDivider: WithOptions; - - scrollBox: WithOptions; - - navbar: WithOptions; - navbarButtons: WithOptions; - navbarButton: WithOptions; - navbarButtonIcon: WithOptions; - navbarMobileMenuRow: WithOptions; - navbarMobileMenuButton: WithOptions; - navbarMobileMenuButtonIcon: WithOptions; - - pageScrollBox: WithOptions; - page: WithOptions; - pageHeader: WithOptions; - - activeDevice: WithOptions<'current'>; - activeDeviceListItem: WithOptions<'current'>; - activeDeviceIcon: WithOptions<'mobile' | 'desktop'>; - - impersonationFab: WithOptions; - impersonationFabIcon: WithOptions; - impersonationFabIconContainer: WithOptions; - impersonationFabTitle: WithOptions; - impersonationFabActionLink: WithOptions; - - fileDropAreaOuterBox: WithOptions; - fileDropAreaBox: WithOptions; - fileDropAreaIconBox: WithOptions; - fileDropAreaIcon: WithOptions; - fileDropAreaHint: WithOptions; - fileDropAreaButtonPrimary: WithOptions; - fileDropAreaFooterHint: WithOptions; - - invitationsSentIconBox: WithOptions; - invitationsSentIcon: WithOptions; - - accordionTriggerButton: WithOptions; - accordionContent: WithOptions; - - qrCodeRow: WithOptions; - qrCodeContainer: WithOptions; - - // default descriptors - badge: WithOptions<'primary' | 'actionRequired'>; - notificationBadge: WithOptions; - button: WithOptions; - providerIcon: WithOptions; - spinner: WithOptions; -}; - -export type Elements = { - [k in keyof ElementsConfig]: Selectors & string, ElementsConfig[k]>; -}[keyof ElementsConfig]; - -export type Variables = { - /** - * The primary color used throughout the components. Set this to your brand color. - */ - colorPrimary?: CssColorOrScale; - /** - * The color used to indicate errors or destructive actions. Set this to your brand's danger color. - */ - colorDanger?: CssColorOrScale; - /** - * The color used to indicate an action that completed successfully or a positive result. - */ - colorSuccess?: CssColorOrScale; - /** - * The color used for potentially destructive actions or when the user's attention is required. - */ - colorWarning?: CssColorOrScale; - /** - * The color that will be used for all to generate the alpha shades the components use. To achieve sufficient contrast, - * light themes should be using dark shades (`black`), while dark themes should be using light (`white`) shades. This option applies to borders, - * backgrounds for hovered elements, hovered dropdown options etc. - * @default 'black' - */ - colorAlphaShade?: CssColorOrAlphaScale; - /** - * The default text color. - * @default black - */ - colorText?: CssColor; - /** - * The color of text appearing on top of an element that with a background color of {@link Variables.colorPrimary}, - * eg: solid primary buttons. - * @default white - */ - colorTextOnPrimaryBackground?: CssColor; - /** - * The text color for elements of lower importance, eg: a subtitle text. - * @default A lighter shade of {@link Variables.colorText} - */ - colorTextSecondary?: CssColor; - /** - * The background color for the card container. - */ - colorBackground?: CssColor; - /** - * The default text color inside input elements. To customise the input background color instead, use {@link Variables.colorInputBackground}. - * @default The value of {@link Variables.colorText} - */ - colorInputText?: CssColor; - /** - * The background color for all input elements. - */ - colorInputBackground?: CssColor; - /** - * The default font that will be used in all components. - * This can be the name of a custom font loaded by your code or the name of a web-safe font ((@link WebSafeFont}) - * If a specific fontFamily is not provided, the components will inherit the font of the parent element. - * @default inherit - * @example - * { fontFamily: 'Montserrat' } - */ - fontFamily?: FontFamily; - /** - * The default font that will be used in all buttons. See {@link Variables.fontFamily} for details. - * If not provided, {@link Variables.fontFamily} will be used instead. - * @default inherit - */ - fontFamilyButtons?: FontFamily; - /** - * The value will be used as the base `md` to calculate all the other scale values (`2xs`, `xs`, `sm`, `lg` and `xl`). - * By default, this value is relative to the root fontSize of the html element. - * @default 1rem; - */ - fontSize?: CssLengthUnit; - /** - * What text anti-aliasing strategy the components will use by default. You can set it to `auto`, `antialiased` or `never` - * @default auto; - */ - fontSmoothing?: FontSmoothing; - /** - * The font weight the components will use. By default, the components will use the 400, 500 and 600 weights for normal, medium and bold - * text respectively. You can override the default weights by passing a {@FontWeightScale} object - * @default { normal: 400, medium: 500, bold: 600 }; - */ - fontWeight?: FontWeightScale; - /** - * The size that will be used as the `md` base borderRadius value. This is used as the base to calculate the `lg`, `xl`, `2xl` - * our components use. As a general rule, the bigger an element is, the larger its borderRadius is going to be. - * eg: the Card element uses '2xl' - * @default 0.375rem - */ - borderRadius?: CssLengthUnit; - /** - * The base spacing unit that all margins, paddings and gaps between the elements are derived from. - * @default 1rem - */ - spacingUnit?: CssLengthUnit; - /** - * The color of the avatar shimmer - * @default rgba(255, 255, 255, 0.36) - */ - colorShimmer?: CssColor; - /** - * The shadow that appears on the avatar when hovered - * @default rgba(0, 0, 0, 0.36) - */ - shadowShimmer?: CssColor; -}; - -export type BaseThemeTaggedType = { __type: 'prebuilt_appearance' }; -export type BaseTheme = BaseThemeTaggedType; - -export type Theme = { - /** - * A theme used as the base theme for the components. - * For further customisation, you can use the {@link Theme.layout}, {@link Theme.variables} and {@link Theme.elements} props. - * @example - * import { dark } from "@clerk/themes"; - * appearance={{ baseTheme: dark }} - */ - baseTheme?: BaseTheme; - /** - * Configuration options that affect the layout of the components, allowing - * customizations that hard to implement with just CSS. - * Eg: placing the logo outside the card element - */ - layout?: Layout; - /** - * General theme overrides. This styles will be merged with our base theme. - * Can override global styles like colors, fonts etc. - * Eg: `colorPrimary: 'blue'` - */ - variables?: Variables; - /** - * Fine-grained theme overrides. Useful when you want to style - * specific elements or elements that under a specific state. - * Eg: `formButtonPrimary__loading: { backgroundColor: 'gray' }` - */ - elements?: Elements; -}; - -export type Layout = { - /** - * Controls whether the logo will be rendered inside or outside the component card. - * To customise the logo further, you can use {@link Appearance.elements} - * @default inside - */ - logoPlacement?: 'inside' | 'outside' | 'none'; - /** - * The URL of your custom logo the components will display. - * By default, the components will use the logo you've set in the Clerk Dashboard. - * This option is helpful when you need to display different logos for different themes, - * eg: white logo on dark themes, black logo on light themes - * To customise the logo further, you can use {@link Appearance.elements} - * @default undefined - */ - logoImageUrl?: string; - /** - * Controls where the browser will redirect to after the user clicks the application logo, - * usually found in the SignIn and SignUp components. - * If a URL is provided, it will be used as the `href` of the link. - * If a value is not passed in, the components will use the Home URL as set in the Clerk dashboard - * @default undefined - */ - logoLinkUrl?: string; - /** - * Controls the variant that will be used for the social buttons. - * By default, the components will use block buttons if you have less than - * 3 social providers enabled, otherwise icon buttons will be used. - * To customise the social buttons further, you can use {@link Appearance.elements} - * @default auto - */ - socialButtonsVariant?: 'auto' | 'iconButton' | 'blockButton'; - /** - * Controls whether the social buttons will be rendered above or below the card form. - * To customise the social button container further, you can use {@link Appearance.elements} - * @default 'top' - */ - socialButtonsPlacement?: 'top' | 'bottom'; - /** - * Controls whether the SignIn or SignUp forms will include optional fields. - * You can make a field required or optional through the {@link https://dashboard.clerk.com|Clerk dashboard}. - * @default true - */ - showOptionalFields?: boolean; - /** - * This options enables the "Terms" link which is, by default, displayed on the bottom-right corner of the - * prebuilt components. Clicking the link will open the passed URL in a new tab - */ - termsPageUrl?: string; - /** - * This options enables the "Help" link which is, by default, displayed on the bottom-right corner of the - * prebuilt components. Clicking the link will open the passed URL in a new tab - */ - helpPageUrl?: string; - /** - * This options enables the "Privacy" link which is, by default, displayed on the bottom-right corner of the - * prebuilt components. Clicking the link will open the passed URL in a new tab - */ - privacyPageUrl?: string; - /** - * This option enables the shimmer animation for the avatars of and - * @default true - */ - shimmer?: boolean; -}; - -export type SignInTheme = Theme; -export type SignUpTheme = Theme; -export type UserButtonTheme = Theme; -export type UserProfileTheme = Theme; -export type OrganizationSwitcherTheme = Theme; -export type OrganizationListTheme = Theme; -export type OrganizationProfileTheme = Theme; -export type CreateOrganizationTheme = Theme; - -export type Appearance = T & { - /** - * Theme overrides that only apply to the `` component - */ - signIn?: T; - /** - * Theme overrides that only apply to the `` component - */ - signUp?: T; - /** - * Theme overrides that only apply to the `` component - */ - userButton?: T; - /** - * Theme overrides that only apply to the `` component - */ - userProfile?: T; - /** - * Theme overrides that only apply to the `` component - */ - organizationSwitcher?: T; - /** - * Theme overrides that only apply to the `` component - */ - organizationList?: T; - /** - * Theme overrides that only apply to the `` component - */ - organizationProfile?: T; - /** - * Theme overrides that only apply to the `` component - */ - createOrganization?: T; -}; diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index ff9b71f031f..bb7b9ea8a64 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -119,6 +119,9 @@ export type ElementObjectKey = K extends `${infer Parent}-${in export type ElementsConfig = { rootBox: WithOptions; card: WithOptions; + cardContent: WithOptions; + cardFooter: WithOptions; + cardFooterItem: WithOptions; logoBox: WithOptions; logoImage: WithOptions; @@ -128,9 +131,9 @@ export type ElementsConfig = { header: WithOptions; headerTitle: WithOptions; headerSubtitle: WithOptions; - headerBackRow: WithOptions; - headerBackLink: WithOptions; - headerBackIcon: WithOptions; + + backRow: WithOptions; + backLink: WithOptions; main: WithOptions; @@ -225,11 +228,10 @@ export type ElementsConfig = { userButtonPopoverCard: WithOptions; userButtonPopoverMain: WithOptions; userButtonPopoverUserPreview: WithOptions; - userButtonPopoverActions: WithOptions; - userButtonPopoverActionButton: WithOptions<'manageAccount' | 'signOut'>; - userButtonPopoverActionButtonIconBox: WithOptions<'manageAccount' | 'signOut'>; - userButtonPopoverActionButtonIcon: WithOptions<'manageAccount' | 'signOut'>; - userButtonPopoverActionButtonText: WithOptions<'manageAccount' | 'signOut'>; + userButtonPopoverActions: WithOptions<'singleSession' | 'multiSession'>; + userButtonPopoverActionButton: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; + userButtonPopoverActionButtonIconBox: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; + userButtonPopoverActionButtonIcon: WithOptions<'manageAccount' | 'addAccount' | 'signOut' | 'signOutAll'>; userButtonPopoverFooter: WithOptions; userButtonPopoverFooterPages: WithOptions; userButtonPopoverFooterPagesLink: WithOptions<'terms' | 'privacy'>; @@ -271,6 +273,7 @@ export type ElementsConfig = { userPreviewAvatarContainer: WithOptions; userPreviewAvatarBox: WithOptions; userPreviewAvatarImage: WithOptions; + userPreviewAvatarIcon: WithOptions; userPreviewTextContainer: WithOptions; userPreviewMainIdentifier: WithOptions; userPreviewSecondaryIdentifier: WithOptions; @@ -395,6 +398,7 @@ export type ElementsConfig = { notificationBadge: WithOptions; button: WithOptions; providerIcon: WithOptions; + spinner: WithOptions; }; export type Elements = { diff --git a/packages/types/src/clerk.retheme.ts b/packages/types/src/clerk.retheme.ts deleted file mode 100644 index e58ce059064..00000000000 --- a/packages/types/src/clerk.retheme.ts +++ /dev/null @@ -1,964 +0,0 @@ -import type { - Appearance, - CreateOrganizationTheme, - OrganizationListTheme, - OrganizationProfileTheme, - OrganizationSwitcherTheme, - SignInTheme, - SignUpTheme, - UserButtonTheme, - UserProfileTheme, -} from './appearance.retheme'; -import type { ClientResource } from './client'; -import type { CustomPage } from './customPages'; -import type { DisplayThemeJSON } from './json'; -import type { LocalizationResource } from './localization'; -import type { OAuthProvider, OAuthScope } from './oauth'; -import type { OrganizationResource } from './organization'; -import type { MembershipRole } from './organizationMembership'; -import type { ActiveSessionResource } from './session'; -import type { UserResource } from './user'; -import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils'; - -export type InstanceType = 'production' | 'development'; - -export type SDKMetadata = { - name: string; - version: string; -}; - -export type ListenerCallback = (emission: Resources) => void; -export type UnsubscribeCallback = () => void; -export type BeforeEmitCallback = (session?: ActiveSessionResource | null) => void | Promise; - -export type SignOutCallback = () => void | Promise; - -export type SignOutOptions = { - /** - * Specify a specific session to sign out. Useful for - * multi-session applications. - */ - sessionId?: string; -}; - -export interface SignOut { - (options?: SignOutOptions): Promise; - - (signOutCallback?: SignOutCallback, options?: SignOutOptions): Promise; -} - -/** - * Main Clerk SDK object. - */ -export interface Clerk { - /** - * Clerk SDK version number. - */ - version: string | undefined; - - /** - * If present, contains information about the SDK that the host application is using. - * For example, if Clerk is loaded through `@clerk/nextjs`, this would be `{ name: '@clerk/nextjs', version: '1.0.0' }` - */ - sdkMetadata: SDKMetadata | undefined; - - /** - * If true the bootstrapping of Clerk.load() has completed successfully. - */ - loaded: boolean; - - frontendApi: string; - - /** Clerk Publishable Key string. */ - publishableKey: string; - - /** Clerk Proxy url string. */ - proxyUrl: string | undefined; - - /** Clerk Satellite Frontend API string. */ - domain: string; - - /** Clerk Flag for satellite apps. */ - isSatellite: boolean; - - /** Clerk Instance type is defined from the Publishable key */ - instanceType: InstanceType | undefined; - - /** Clerk flag for loading Clerk in a standard browser setup */ - isStandardBrowser: boolean | undefined; - - /** Client handling most Clerk operations. */ - client: ClientResource | undefined; - - /** Active Session. */ - session: ActiveSessionResource | null | undefined; - - /** Active Organization */ - organization: OrganizationResource | null | undefined; - - /** Current User. */ - user: UserResource | null | undefined; - - /** - * Signs out the current user on single-session instances, or all users on multi-session instances - * @param signOutCallback - Optional A callback that runs after sign out completes. - * @param options - Optional Configuration options, see {@link SignOutOptions} - * @returns A promise that resolves when the sign out process completes. - */ - signOut: SignOut; - - /** - * Opens the Clerk SignIn component in a modal. - * @param props Optional sign in configuration parameters. - */ - openSignIn: (props?: SignInProps) => void; - - /** - * Closes the Clerk SignIn modal. - */ - closeSignIn: () => void; - - /** - * Opens the Clerk SignUp component in a modal. - * @param props Optional props that will be passed to the SignUp component. - */ - openSignUp: (props?: SignUpProps) => void; - - /** - * Closes the Clerk SignUp modal. - */ - closeSignUp: () => void; - - /** - * Opens the Clerk UserProfile modal. - * @param props Optional props that will be passed to the UserProfile component. - */ - openUserProfile: (props?: UserProfileProps) => void; - - /** - * Closes the Clerk UserProfile modal. - */ - closeUserProfile: () => void; - - /** - * Opens the Clerk OrganizationProfile modal. - * @param props Optional props that will be passed to the OrganizationProfile component. - */ - openOrganizationProfile: (props?: OrganizationProfileProps) => void; - - /** - * Closes the Clerk OrganizationProfile modal. - */ - closeOrganizationProfile: () => void; - - /** - * Opens the Clerk CreateOrganization modal. - * @param props Optional props that will be passed to the CreateOrganization component. - */ - openCreateOrganization: (props?: CreateOrganizationProps) => void; - - /** - * Closes the Clerk CreateOrganization modal. - */ - closeCreateOrganization: () => void; - - /** - * Mounts a sign in flow component at the target element. - * @param targetNode Target node to mount the SignIn component. - * @param signInProps sign in configuration parameters. - */ - mountSignIn: (targetNode: HTMLDivElement, signInProps?: SignInProps) => void; - - /** - * Unmount a sign in flow component from the target element. - * If there is no component mounted at the target node, results in a noop. - * - * @param targetNode Target node to unmount the SignIn component from. - */ - unmountSignIn: (targetNode: HTMLDivElement) => void; - - /** - * Mounts a sign up flow component at the target element. - * - * @param targetNode Target node to mount the SignUp component. - * @param signUpProps sign up configuration parameters. - */ - mountSignUp: (targetNode: HTMLDivElement, signUpProps?: SignUpProps) => void; - - /** - * Unmount a sign up flow component from the target element. - * If there is no component mounted at the target node, results in a noop. - * - * @param targetNode Target node to unmount the SignUp component from. - */ - unmountSignUp: (targetNode: HTMLDivElement) => void; - - /** - * Mount a user button component at the target element. - * - * @param targetNode Target node to mount the UserButton component. - * @param userButtonProps User button configuration parameters. - */ - mountUserButton: (targetNode: HTMLDivElement, userButtonProps?: UserButtonProps) => void; - - /** - * Unmount a user button component at the target element. - * If there is no component mounted at the target node, results in a noop. - * - * @param targetNode Target node to unmount the UserButton component from. - */ - unmountUserButton: (targetNode: HTMLDivElement) => void; - - /** - * Mount a user profile component at the target element. - * - * @param targetNode Target to mount the UserProfile component. - * @param userProfileProps User profile configuration parameters. - */ - mountUserProfile: (targetNode: HTMLDivElement, userProfileProps?: UserProfileProps) => void; - - /** - * Unmount a user profile component at the target element. - * If there is no component mounted at the target node, results in a noop. - * - * @param targetNode Target node to unmount the UserProfile component from. - */ - unmountUserProfile: (targetNode: HTMLDivElement) => void; - - /** - * Mount an organization profile component at the target element. - * @param targetNode Target to mount the OrganizationProfile component. - * @param props Configuration parameters. - */ - mountOrganizationProfile: (targetNode: HTMLDivElement, props?: OrganizationProfileProps) => void; - - /** - * Unmount the organization profile component from the target node. - * @param targetNode Target node to unmount the OrganizationProfile component from. - */ - unmountOrganizationProfile: (targetNode: HTMLDivElement) => void; - - /** - * Mount a CreateOrganization component at the target element. - * @param targetNode Target to mount the CreateOrganization component. - * @param props Configuration parameters. - */ - mountCreateOrganization: (targetNode: HTMLDivElement, props?: CreateOrganizationProps) => void; - - /** - * Unmount the CreateOrganization component from the target node. - * @param targetNode Target node to unmount the CreateOrganization component from. - */ - unmountCreateOrganization: (targetNode: HTMLDivElement) => void; - - /** - * Mount an organization switcher component at the target element. - * @param targetNode Target to mount the OrganizationSwitcher component. - * @param props Configuration parameters. - */ - mountOrganizationSwitcher: (targetNode: HTMLDivElement, props?: OrganizationSwitcherProps) => void; - - /** - * Unmount the organization profile component from the target node.* - * @param targetNode Target node to unmount the OrganizationSwitcher component from. - */ - unmountOrganizationSwitcher: (targetNode: HTMLDivElement) => void; - - /** - * Mount an organization list component at the target element. - * @param targetNode Target to mount the OrganizationList component. - * @param props Configuration parameters. - */ - mountOrganizationList: (targetNode: HTMLDivElement, props?: OrganizationListProps) => void; - - /** - * Unmount the organization list component from the target node.* - * @param targetNode Target node to unmount the OrganizationList component from. - */ - unmountOrganizationList: (targetNode: HTMLDivElement) => void; - - /** - * Register a listener that triggers a callback each time important Clerk resources are changed. - * Allows to hook up at different steps in the sign up, sign in processes. - * - * Some important checkpoints: - * When there is an active session, user === session.user. - * When there is no active session, user and session will both be null. - * When a session is loading, user and session will be undefined. - * - * @param callback Callback function receiving the most updated Clerk resources after a change. - * @returns - Unsubscribe callback - */ - addListener: (callback: ListenerCallback) => UnsubscribeCallback; - - /** - * Set the active session and organization explicitly. - * - * If the session param is `null`, the active session is deleted. - * In a similar fashion, if the organization param is `null`, the current organization is removed as active. - */ - setActive: SetActive; - - /** - * Function used to commit a navigation after certain steps in the Clerk processes. - */ - navigate: CustomNavigation; - - /** - * Decorates the provided url with the auth token for development instances. - * - * @param {string} to - * @param opts A {@link BuildUrlWithAuthParams} object - */ - buildUrlWithAuth(to: string, opts?: BuildUrlWithAuthParams): string; - - /** - * Returns the configured url where is mounted or a custom sign-in page is rendered. - * - * @param opts A {@link RedirectOptions} object - */ - buildSignInUrl(opts?: RedirectOptions): string; - - /** - * Returns the configured url where is mounted or a custom sign-up page is rendered. - * - * @param opts A {@link RedirectOptions} object - */ - buildSignUpUrl(opts?: RedirectOptions): string; - - /** - * Returns the url where is mounted or a custom user-profile page is rendered. - */ - buildUserProfileUrl(): string; - - /** - * Returns the configured url where is mounted or a custom create-organization page is rendered. - */ - buildCreateOrganizationUrl(): string; - - /** - * Returns the configured url where is mounted or a custom organization-profile page is rendered. - */ - buildOrganizationProfileUrl(): string; - - /** - * Returns the configured home URL of the instance. - */ - buildHomeUrl(): string; - - /** - * - * Redirects to the provided url after decorating it with the auth token for development instances. - * - * @param {string} to - */ - redirectWithAuth(to: string): Promise; - - /** - * Redirects to the configured URL where is mounted. - * - * @param opts A {@link RedirectOptions} object - */ - redirectToSignIn(opts?: SignInRedirectOptions): Promise; - - /** - * Redirects to the configured URL where is mounted. - * - * @param opts A {@link RedirectOptions} object - */ - redirectToSignUp(opts?: SignUpRedirectOptions): Promise; - - /** - * Redirects to the configured URL where is mounted. - */ - redirectToUserProfile: () => Promise; - - /** - * Redirects to the configured URL where is mounted. - */ - redirectToOrganizationProfile: () => Promise; - - /** - * Redirects to the configured URL where is mounted. - */ - redirectToCreateOrganization: () => Promise; - - /** - * Redirects to the configured home URL of the instance. - */ - redirectToHome: () => Promise; - - /** - * Completes an OAuth or SAML redirection flow started by - * {@link Clerk.client.signIn.authenticateWithRedirect} or {@link Clerk.client.signUp.authenticateWithRedirect} - */ - handleRedirectCallback: ( - params: HandleOAuthCallbackParams | HandleSamlCallbackParams, - customNavigate?: (to: string) => Promise, - ) => Promise; - - /** - * Completes a Email Link flow started by {@link Clerk.client.signIn.createEmailLinkFlow} or {@link Clerk.client.signUp.createEmailLinkFlow} - */ - handleEmailLinkVerification: ( - params: HandleEmailLinkVerificationParams, - customNavigate?: (to: string) => Promise, - ) => Promise; - - /** - * Authenticates user using their Metamask browser extension - */ - authenticateWithMetamask: (params?: AuthenticateWithMetamaskParams) => Promise; - - /** - * Creates an organization, adding the current user as admin. - */ - createOrganization: (params: CreateOrganizationParams) => Promise; - - /** - * Retrieves a single organization by id. - */ - getOrganization: (organizationId: string) => Promise; - - /** - * Handles a 401 response from Frontend API by refreshing the client and session object accordingly - */ - handleUnauthenticated: () => Promise; -} - -export type HandleOAuthCallbackParams = { - /** - * Full URL or path to navigate after successful sign up. - */ - afterSignUpUrl?: string | null; - - /** - * Full URL or path to navigate after successful sign in. - */ - afterSignInUrl?: string | null; - - /** - * Full URL or path to navigate after successful sign in - * or sign up. - * - * The same as setting afterSignInUrl and afterSignUpUrl - * to the same value. - */ - redirectUrl?: string | null; - - /** - * Full URL or path to navigate during sign in, - * if identifier verification is required. - */ - firstFactorUrl?: string; - - /** - * Full URL or path to navigate during sign in, - * if 2FA is enabled. - */ - secondFactorUrl?: string; - - /** - * Full URL or path to navigate during sign in, - * if the user is required to reset their password. - */ - resetPasswordUrl?: string; - - /** - * Full URL or path to navigate after an incomplete sign up. - */ - continueSignUpUrl?: string | null; - - /** - * Full URL or path to navigate after requesting email verification. - */ - verifyEmailAddressUrl?: string | null; - - /** - * Full URL or path to navigate after requesting phone verification. - */ - verifyPhoneNumberUrl?: string | null; -}; - -export type HandleSamlCallbackParams = HandleOAuthCallbackParams; - -export type BuildUrlWithAuthParams = { - /** - * Controls if dev browser JWT is added as a query param - */ - useQueryParam?: boolean | null; -}; - -export type CustomNavigation = (to: string, options?: NavigateOptions) => Promise | void; - -export type ClerkThemeOptions = DeepSnakeToCamel>; - -/** - * Navigation options used to replace or push history changes. - * Both `routerPush` & `routerReplace` OR none options should be passed. - */ -type ClerkOptionsNavigationFn = - | { - routerPush?: never; - routerReplace?: never; - } - | { - routerPush: (to: string) => Promise | unknown; - routerReplace: (to: string) => Promise | unknown; - }; - -type ClerkOptionsNavigation = ClerkOptionsNavigationFn & { - routerDebug?: boolean; -}; - -export type ClerkOptions = ClerkOptionsNavigation & { - appearance?: Appearance; - localization?: LocalizationResource; - /** - * Navigation - */ - routerPush?: (to: string) => Promise | unknown; - routerReplace?: (to: string) => Promise | unknown; - routerDebug?: boolean; - polling?: boolean; - selectInitialSession?: (client: ClientResource) => ActiveSessionResource | null; - /** Controls if ClerkJS will load with the standard browser setup using Clerk cookies */ - standardBrowser?: boolean; - /** Optional support email for display in authentication screens */ - supportEmail?: string; - touchSession?: boolean; - signInUrl?: string; - signUpUrl?: string; - afterSignInUrl?: string; - afterSignUpUrl?: string; - allowedRedirectOrigins?: Array; - isSatellite?: boolean | ((url: URL) => boolean); - - /** - * Telemetry options - */ - telemetry?: - | false - | { - disabled?: boolean; - debug?: boolean; - }; - - sdkMetadata?: SDKMetadata; -}; - -export interface NavigateOptions { - replace?: boolean; -} - -export interface Resources { - client: ClientResource; - session?: ActiveSessionResource | null; - user?: UserResource | null; - organization?: OrganizationResource | null; -} - -export type RoutingStrategy = 'path' | 'hash' | 'virtual'; - -export type WithoutRouting = Omit; - -export type SignInInitialValues = { - emailAddress?: string; - phoneNumber?: string; - username?: string; -}; - -export type SignUpInitialValues = { - emailAddress?: string; - phoneNumber?: string; - firstName?: string; - lastName?: string; - username?: string; -}; - -export type RedirectOptions = { - /** - * Full URL or path to navigate after successful sign in. - */ - afterSignInUrl?: string | null; - - /** - * Full URL or path to navigate after successful sign up. - * Sets the afterSignUpUrl if the "Sign up" link is clicked. - */ - afterSignUpUrl?: string | null; - - /** - * Full URL or path to navigate after successful sign in, - * or sign up. - * - * The same as setting afterSignInUrl and afterSignUpUrl - * to the same value. - */ - redirectUrl?: string | null; -}; - -export type SignInRedirectOptions = RedirectOptions & { - /** - * Initial values that are used to prefill the sign in form. - */ - initialValues?: SignInInitialValues; -}; - -export type SignUpRedirectOptions = RedirectOptions & { - /** - * Initial values that are used to prefill the sign up form. - */ - initialValues?: SignUpInitialValues; -}; - -export type SetActiveParams = { - /** - * The session resource or session id (string version) to be set as active. - * If `null`, the current session is deleted. - */ - session?: ActiveSessionResource | string | null; - - /** - * The organization resource or organization id (string version) to be set as active in the current session. - * If `null`, the currently active organization is removed as active. - */ - organization?: OrganizationResource | string | null; - - /** - * Callback run just before the active session and/or organization is set to the passed object. - * Can be used to hook up for pre-navigation actions. - */ - beforeEmit?: BeforeEmitCallback; -}; - -export type SetActive = (params: SetActiveParams) => Promise; - -export type RoutingOptions = - | { path: string | undefined; routing?: Extract } - | { path?: never; routing?: Extract }; - -export type SignInProps = RoutingOptions & { - /** - * Full URL or path to for the sign up process. - * Used to fill the "Sign up" link in the SignUp component. - */ - signUpUrl?: string; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: SignInTheme; - /** - * Initial values that are used to prefill the sign in form. - */ - initialValues?: SignInInitialValues; -} & RedirectOptions; - -export type SignInModalProps = WithoutRouting; - -export type SignUpProps = RoutingOptions & { - /** - * Full URL or path to for the sign in process. - * Used to fill the "Sign in" link in the SignUp component. - */ - signInUrl?: string; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: SignUpTheme; - - /** - * Additional arbitrary metadata to be stored alongside the User object - */ - unsafeMetadata?: SignUpUnsafeMetadata; - /** - * Initial values that are used to prefill the sign up form. - */ - initialValues?: SignUpInitialValues; -} & RedirectOptions; - -export type SignUpModalProps = WithoutRouting; - -export type UserProfileProps = RoutingOptions & { - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: UserProfileTheme; - /* - * Specify additional scopes per OAuth provider that your users would like to provide if not already approved. - * e.g. - */ - additionalOAuthScopes?: Partial>; - /* - * Provide custom pages and links to be rendered inside the UserProfile. - */ - customPages?: CustomPage[]; -}; - -export type UserProfileModalProps = WithoutRouting; - -export type OrganizationProfileProps = RoutingOptions & { - /** - * Full URL or path to navigate to after the user leaves the currently active organization. - * @default undefined - */ - afterLeaveOrganizationUrl?: string; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: OrganizationProfileTheme; - /* - * Provide custom pages and links to be rendered inside the OrganizationProfile. - */ - customPages?: CustomPage[]; -}; - -export type OrganizationProfileModalProps = WithoutRouting; - -export type CreateOrganizationProps = RoutingOptions & { - /** - * Full URL or path to navigate after creating a new organization. - * @default undefined - */ - afterCreateOrganizationUrl?: - | ((organization: OrganizationResource) => string) - | LooseExtractedParams>; - /** - * Hides the screen for sending invitations after an organization is created. - * @default undefined When left undefined Clerk will automatically hide the screen if - * the number of max allowed members is equal to 1 - */ - skipInvitationScreen?: boolean; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: CreateOrganizationTheme; -}; - -export type CreateOrganizationModalProps = WithoutRouting; - -type UserProfileMode = 'modal' | 'navigation'; -type UserButtonProfileMode = - | { - userProfileUrl?: never; - userProfileMode?: Extract; - } - | { - userProfileUrl: string; - userProfileMode?: Extract; - }; - -export type UserButtonProps = UserButtonProfileMode & { - /** - * Controls if the username is displayed next to the trigger button - */ - showName?: boolean; - /** - * Controls the default state of the UserButton - */ - defaultOpen?: boolean; - /** - * Full URL or path to navigate after sign out is complete - */ - afterSignOutUrl?: string; - /** - * Full URL or path to navigate after signing out the current user is complete. - * This option applies to multi-session applications. - */ - afterMultiSessionSingleSignOutUrl?: string; - /** - * Full URL or path to navigate on "Add another account" action. - * Multi-session mode only. - */ - signInUrl?: string; - /** - * Full URL or path to navigate after successful account change. - * Multi-session mode only. - */ - afterSwitchSessionUrl?: string; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: UserButtonTheme; - - /* - * Specify options for the underlying component. - * e.g. - */ - userProfileProps?: Pick; -}; - -type PrimitiveKeys = { - [K in keyof T]: T[K] extends string | boolean | number | null ? K : never; -}[keyof T]; - -type LooseExtractedParams = Autocomplete<`:${T}`>; - -type OrganizationProfileMode = - | { organizationProfileUrl: string; organizationProfileMode?: 'navigation' } - | { organizationProfileUrl?: never; organizationProfileMode?: 'modal' }; - -type CreateOrganizationMode = - | { createOrganizationUrl: string; createOrganizationMode?: 'navigation' } - | { createOrganizationUrl?: never; createOrganizationMode?: 'modal' }; - -export type OrganizationSwitcherProps = CreateOrganizationMode & - OrganizationProfileMode & { - /** - * Controls the default state of the OrganizationSwitcher - */ - defaultOpen?: boolean; - /** - * By default, users can switch between organization and their personal account. - * This option controls whether OrganizationSwitcher will include the user's personal account - * in the organization list. Setting this to `false` will hide the personal account entry, - * and users will only be able to switch between organizations. - * @default true - */ - hidePersonal?: boolean; - /** - * Full URL or path to navigate after a successful organization switch. - * @default undefined - * @deprecated use `afterSelectOrganizationUrl` or `afterSelectPersonalUrl` - */ - afterSwitchOrganizationUrl?: string; - /** - * Full URL or path to navigate after creating a new organization. - * @default undefined - */ - afterCreateOrganizationUrl?: - | ((organization: OrganizationResource) => string) - | LooseExtractedParams>; - /** - * Full URL or path to navigate after a successful organization selection. - * Accepts a function that returns URL or path - * @default undefined` - */ - afterSelectOrganizationUrl?: - | ((organization: OrganizationResource) => string) - | LooseExtractedParams>; - - /** - * Full URL or path to navigate after a successful selection of personal workspace. - * Accepts a function that returns URL or path - * @default undefined - */ - afterSelectPersonalUrl?: ((user: UserResource) => string) | LooseExtractedParams>; - /** - * Full URL or path to navigate to after the user leaves the currently active organization. - * @default undefined - */ - afterLeaveOrganizationUrl?: string; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: OrganizationSwitcherTheme; - - /* - * Specify options for the underlying component. - * e.g. - */ - organizationProfileProps?: Pick; - }; - -export type OrganizationListProps = { - /** - * Full URL or path to navigate after creating a new organization. - * @default undefined - */ - afterCreateOrganizationUrl?: - | ((organization: OrganizationResource) => string) - | LooseExtractedParams>; - /** - * Full URL or path to navigate after a successful organization selection. - * Accepts a function that returns URL or path - * @default undefined` - */ - afterSelectOrganizationUrl?: - | ((organization: OrganizationResource) => string) - | LooseExtractedParams>; - /** - * Customisation options to fully match the Clerk components to your own brand. - * These options serve as overrides and will be merged with the global `appearance` - * prop of ClerkProvided (if one is provided) - */ - appearance?: OrganizationListTheme; - /** - * Hides the screen for sending invitations after an organization is created. - * @default undefined When left undefined Clerk will automatically hide the screen if - * the number of max allowed members is equal to 1 - */ - skipInvitationScreen?: boolean; - /** - * By default, users can switch between organization and their personal account. - * This option controls whether OrganizationList will include the user's personal account - * in the organization list. Setting this to `false` will hide the personal account entry, - * and users will only be able to switch between organizations. - * @default true - */ - hidePersonal?: boolean; - /** - * Full URL or path to navigate after a successful selection of personal workspace. - * Accepts a function that returns URL or path - * @default undefined` - */ - afterSelectPersonalUrl?: ((user: UserResource) => string) | LooseExtractedParams>; -}; - -export interface HandleEmailLinkVerificationParams { - /** - * Full URL or path to navigate after successful magic link verification - * on completed sign up or sign in on the same device. - */ - redirectUrlComplete?: string; - /** - * Full URL or path to navigate after successful magic link verification - * on the same device, but not completed sign in or sign up. - */ - redirectUrl?: string; - /** - * Callback function to be executed after successful magic link - * verification on another device. - */ - onVerifiedOnOtherDevice?: () => void; -} - -export type CreateOrganizationInvitationParams = { - emailAddress: string; - role: MembershipRole; -}; - -export type CreateBulkOrganizationInvitationParams = { - emailAddresses: string[]; - role: MembershipRole; -}; - -export interface CreateOrganizationParams { - name: string; - slug?: string; -} - -export interface AuthenticateWithMetamaskParams { - customNavigate?: (to: string) => Promise; - redirectUrl?: string; - signUpContinueUrl?: string; - unsafeMetadata?: SignUpUnsafeMetadata; -} - -export interface LoadedClerk extends Clerk { - client: ClientResource; -} diff --git a/packages/types/src/index.retheme.ts b/packages/types/src/index.retheme.ts deleted file mode 100644 index 4090e47dca6..00000000000 --- a/packages/types/src/index.retheme.ts +++ /dev/null @@ -1,56 +0,0 @@ -export * from './api'; -export * from './appearance.retheme'; -export * from './elementIds'; -export * from './attributes'; -export * from './authConfig'; -export * from './backupCode'; -export * from './clerk.retheme'; -export * from './client'; -export * from './deletedObject'; -export * from './displayConfig'; -export * from './emailAddress'; -export * from './environment'; -export * from './externalAccount'; -export * from './factors'; -export * from './identificationLink'; -export * from './identifiers'; -export * from './image'; -export * from './json'; -export * from './jwt'; -export * from './key'; -export * from './localization.retheme'; -export * from './jwtv2'; -export * from './multiDomain'; -export * from './oauth'; -export * from './organization'; -export * from './organizationDomain'; -export * from './organizationInvitation'; -export * from './organizationMembership'; -export * from './organizationMembershipRequest'; -export * from './organizationSettings'; -export * from './organizationSuggestion'; -export * from './passwords'; -export * from './permission'; -export * from './phoneNumber'; -export * from './redirects'; -export * from './resource'; -export * from './role'; -export * from './saml'; -export * from './samlAccount'; -export * from './session'; -export * from './signIn'; -export * from './signUp'; -export * from './ssr'; -export * from './strategies'; -export * from './theme'; -export * from './token'; -export * from './totp'; -export * from './user'; -export * from './userOrganizationInvitation'; -export * from './userSettings'; -export * from './utils'; -export * from './verification'; -export * from './web3'; -export * from './web3Wallet'; -export * from './customPages'; -export * from './pagination'; diff --git a/packages/types/src/localization.retheme.ts b/packages/types/src/localization.retheme.ts deleted file mode 100644 index 96b986a8fe1..00000000000 --- a/packages/types/src/localization.retheme.ts +++ /dev/null @@ -1,787 +0,0 @@ -import type { FieldId } from './elementIds'; -import type { CamelToSnake, DeepPartial } from './utils'; - -export type LocalizationValue = string; - -/** - * A type containing all the possible localization keys the prebuilt Clerk components support. - * Users aiming to customise a few strings can also peak at the `data-localization-key` attribute by inspecting - * the DOM and updating the corresponding key. - * Users aiming to completely localize the components by providing a complete translation can use - * the default english resource object from {@link https://github.com/clerk/javascript Clerk's open source repo} - * as a starting point. - */ -export type LocalizationResource = DeepPartial<_LocalizationResource>; - -type _LocalizationResource = { - locale: string; - /** - * @experimental - * Add role keys and their localized value - * e.g. roles:{ 'org:teacher': 'Teacher'} - */ - roles: { - [r: string]: LocalizationValue; - }; - socialButtonsBlockButton: LocalizationValue; - dividerText: LocalizationValue; - formFieldLabel__emailAddress: LocalizationValue; - formFieldLabel__emailAddresses: LocalizationValue; - formFieldLabel__phoneNumber: LocalizationValue; - formFieldLabel__username: LocalizationValue; - formFieldLabel__emailAddress_username: LocalizationValue; - formFieldLabel__password: LocalizationValue; - formFieldLabel__currentPassword: LocalizationValue; - formFieldLabel__newPassword: LocalizationValue; - formFieldLabel__confirmPassword: LocalizationValue; - formFieldLabel__signOutOfOtherSessions: LocalizationValue; - formFieldLabel__automaticInvitations: LocalizationValue; - formFieldLabel__firstName: LocalizationValue; - formFieldLabel__lastName: LocalizationValue; - formFieldLabel__backupCode: LocalizationValue; - formFieldLabel__organizationName: LocalizationValue; - formFieldLabel__organizationSlug: LocalizationValue; - formFieldLabel__organizationDomain: LocalizationValue; - formFieldLabel__organizationDomainEmailAddress: LocalizationValue; - formFieldLabel__organizationDomainEmailAddressDescription: LocalizationValue; - formFieldLabel__organizationDomainDeletePending: LocalizationValue; - formFieldLabel__confirmDeletion: LocalizationValue; - formFieldLabel__role: LocalizationValue; - formFieldInputPlaceholder__emailAddress: LocalizationValue; - formFieldInputPlaceholder__emailAddresses: LocalizationValue; - formFieldInputPlaceholder__phoneNumber: LocalizationValue; - formFieldInputPlaceholder__username: LocalizationValue; - formFieldInputPlaceholder__emailAddress_username: LocalizationValue; - formFieldInputPlaceholder__password: LocalizationValue; - formFieldInputPlaceholder__firstName: LocalizationValue; - formFieldInputPlaceholder__lastName: LocalizationValue; - formFieldInputPlaceholder__backupCode: LocalizationValue; - formFieldInputPlaceholder__organizationName: LocalizationValue; - formFieldInputPlaceholder__organizationSlug: LocalizationValue; - formFieldInputPlaceholder__organizationDomain: LocalizationValue; - formFieldInputPlaceholder__organizationDomainEmailAddress: LocalizationValue; - formFieldInputPlaceholder__confirmDeletionUserAccount: LocalizationValue; - formFieldError__notMatchingPasswords: LocalizationValue; - formFieldError__matchingPasswords: LocalizationValue; - formFieldError__verificationLinkExpired: LocalizationValue; - formFieldAction__forgotPassword: LocalizationValue; - formFieldHintText__optional: LocalizationValue; - formFieldHintText__slug: LocalizationValue; - formButtonPrimary: LocalizationValue; - signInEnterPasswordTitle: LocalizationValue; - backButton: LocalizationValue; - footerActionLink__useAnotherMethod: LocalizationValue; - badge__primary: LocalizationValue; - badge__thisDevice: LocalizationValue; - badge__userDevice: LocalizationValue; - badge__otherImpersonatorDevice: LocalizationValue; - badge__default: LocalizationValue; - badge__unverified: LocalizationValue; - badge__requiresAction: LocalizationValue; - badge__you: LocalizationValue; - footerPageLink__help: LocalizationValue; - footerPageLink__privacy: LocalizationValue; - footerPageLink__terms: LocalizationValue; - paginationButton__previous: LocalizationValue; - paginationButton__next: LocalizationValue; - paginationRowText__displaying: LocalizationValue; - paginationRowText__of: LocalizationValue; - membershipRole__admin: LocalizationValue; - membershipRole__basicMember: LocalizationValue; - membershipRole__guestMember: LocalizationValue; - signUp: { - start: { - title: LocalizationValue; - subtitle: LocalizationValue; - actionText: LocalizationValue; - actionLink: LocalizationValue; - }; - emailLink: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - verified: { - title: LocalizationValue; - }; - loading: { - title: LocalizationValue; - }; - verifiedSwitchTab: { - title: LocalizationValue; - subtitle: LocalizationValue; - subtitleNewTab: LocalizationValue; - }; - }; - emailCode: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - phoneCode: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - continue: { - title: LocalizationValue; - subtitle: LocalizationValue; - actionText: LocalizationValue; - actionLink: LocalizationValue; - }; - }; - signIn: { - start: { - title: LocalizationValue; - subtitle: LocalizationValue; - actionText: LocalizationValue; - actionLink: LocalizationValue; - actionLink__use_email: LocalizationValue; - actionLink__use_phone: LocalizationValue; - actionLink__use_username: LocalizationValue; - actionLink__use_email_username: LocalizationValue; - }; - password: { - title: LocalizationValue; - subtitle: LocalizationValue; - actionLink: LocalizationValue; - }; - forgotPasswordAlternativeMethods: { - title: LocalizationValue; - label__alternativeMethods: LocalizationValue; - blockButton__resetPassword: LocalizationValue; - }; - forgotPassword: { - title_email: LocalizationValue; - title_phone: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle_email: LocalizationValue; - formSubtitle_phone: LocalizationValue; - resendButton: LocalizationValue; - }; - resetPassword: { - title: LocalizationValue; - formButtonPrimary: LocalizationValue; - successMessage: LocalizationValue; - requiredMessage: LocalizationValue; - }; - resetPasswordMfa: { - detailsLabel: LocalizationValue; - }; - emailCode: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - emailLink: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - unusedTab: { - title: LocalizationValue; - }; - verified: { - title: LocalizationValue; - subtitle: LocalizationValue; - }; - verifiedSwitchTab: { - subtitle: LocalizationValue; - titleNewTab: LocalizationValue; - subtitleNewTab: LocalizationValue; - }; - loading: { - title: LocalizationValue; - subtitle: LocalizationValue; - }; - failed: { - title: LocalizationValue; - subtitle: LocalizationValue; - }; - expired: { - title: LocalizationValue; - subtitle: LocalizationValue; - }; - }; - phoneCode: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - phoneCodeMfa: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - totpMfa: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - }; - backupCodeMfa: { - title: LocalizationValue; - subtitle: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - }; - alternativeMethods: { - title: LocalizationValue; - actionLink: LocalizationValue; - blockButton__emailLink: LocalizationValue; - blockButton__emailCode: LocalizationValue; - blockButton__phoneCode: LocalizationValue; - blockButton__password: LocalizationValue; - blockButton__totp: LocalizationValue; - blockButton__backupCode: LocalizationValue; - getHelp: { - title: LocalizationValue; - content: LocalizationValue; - blockButton__emailSupport: LocalizationValue; - }; - }; - noAvailableMethods: { - title: LocalizationValue; - subtitle: LocalizationValue; - message: LocalizationValue; - }; - }; - userProfile: { - mobileButton__menu: LocalizationValue; - formButtonPrimary__continue: LocalizationValue; - formButtonPrimary__finish: LocalizationValue; - formButtonReset: LocalizationValue; - navbar: { - title: LocalizationValue; - description: LocalizationValue; - }; - start: { - headerTitle__account: LocalizationValue; - headerTitle__security: LocalizationValue; - profileSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - }; - usernameSection: { - title: LocalizationValue; - primaryButton__changeUsername: LocalizationValue; - primaryButton__setUsername: LocalizationValue; - }; - emailAddressesSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - detailsTitle__primary: LocalizationValue; - detailsSubtitle__primary: LocalizationValue; - detailsAction__primary: LocalizationValue; - detailsTitle__nonPrimary: LocalizationValue; - detailsSubtitle__nonPrimary: LocalizationValue; - detailsAction__nonPrimary: LocalizationValue; - detailsTitle__unverified: LocalizationValue; - detailsSubtitle__unverified: LocalizationValue; - detailsAction__unverified: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveAction: LocalizationValue; - }; - phoneNumbersSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - detailsTitle__primary: LocalizationValue; - detailsSubtitle__primary: LocalizationValue; - detailsAction__primary: LocalizationValue; - detailsTitle__nonPrimary: LocalizationValue; - detailsSubtitle__nonPrimary: LocalizationValue; - detailsAction__nonPrimary: LocalizationValue; - detailsTitle__unverified: LocalizationValue; - detailsSubtitle__unverified: LocalizationValue; - detailsAction__unverified: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveAction: LocalizationValue; - }; - connectedAccountsSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - title__connectionFailed: LocalizationValue; - title__reauthorize: LocalizationValue; - subtitle__reauthorize: LocalizationValue; - actionLabel__connectionFailed: LocalizationValue; - actionLabel__reauthorize: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveActionAccordionSubtitle: LocalizationValue; - }; - enterpriseAccountsSection: { - title: LocalizationValue; - }; - passwordSection: { - title: LocalizationValue; - primaryButton__changePassword: LocalizationValue; - primaryButton__setPassword: LocalizationValue; - }; - mfaSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - phoneCode: { - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveActionLabel: LocalizationValue; - title__default: LocalizationValue; - title__setDefault: LocalizationValue; - subtitle__default: LocalizationValue; - subtitle__setDefault: LocalizationValue; - actionLabel__setDefault: LocalizationValue; - }; - backupCodes: { - headerTitle: LocalizationValue; - title__regenerate: LocalizationValue; - subtitle__regenerate: LocalizationValue; - actionLabel__regenerate: LocalizationValue; - }; - totp: { - headerTitle: LocalizationValue; - title: LocalizationValue; - subtitle: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveActionLabel: LocalizationValue; - }; - }; - activeDevicesSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - detailsTitle: LocalizationValue; - detailsSubtitle: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveAction: LocalizationValue; - }; - web3WalletsSection: { - title: LocalizationValue; - primaryButton: LocalizationValue; - destructiveActionTitle: LocalizationValue; - destructiveActionSubtitle: LocalizationValue; - destructiveAction: LocalizationValue; - }; - dangerSection: { - title: LocalizationValue; - deleteAccountButton: LocalizationValue; - deleteAccountTitle: LocalizationValue; - deleteAccountDescription: LocalizationValue; - }; - }; - profilePage: { - title: LocalizationValue; - imageFormTitle: LocalizationValue; - imageFormSubtitle: LocalizationValue; - imageFormDestructiveActionSubtitle: LocalizationValue; - fileDropAreaTitle: LocalizationValue; - fileDropAreaAction: LocalizationValue; - fileDropAreaHint: LocalizationValue; - readonly: LocalizationValue; - successMessage: LocalizationValue; - }; - usernamePage: { - title: LocalizationValue; - successMessage: LocalizationValue; - }; - emailAddressPage: { - title: LocalizationValue; - emailCode: { - formHint: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - successMessage: LocalizationValue; - }; - emailLink: { - formHint: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - successMessage: LocalizationValue; - }; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - phoneNumberPage: { - title: LocalizationValue; - successMessage: LocalizationValue; - infoText: LocalizationValue; - infoText__secondary: LocalizationValue; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - connectedAccountPage: { - title: LocalizationValue; - formHint: LocalizationValue; - formHint__noAccounts: LocalizationValue; - socialButtonsBlockButton: LocalizationValue; - successMessage: LocalizationValue; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - web3WalletPage: { - title: LocalizationValue; - subtitle__availableWallets: LocalizationValue; - subtitle__unavailableWallets: LocalizationValue; - successMessage: LocalizationValue; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - passwordPage: { - title: LocalizationValue; - readonly: LocalizationValue; - successMessage: LocalizationValue; - changePasswordTitle: LocalizationValue; - changePasswordSuccessMessage: LocalizationValue; - sessionsSignedOutSuccessMessage: LocalizationValue; - }; - mfaPage: { - title: LocalizationValue; - formHint: LocalizationValue; - }; - mfaTOTPPage: { - title: LocalizationValue; - verifyTitle: LocalizationValue; - verifySubtitle: LocalizationValue; - successMessage: LocalizationValue; - authenticatorApp: { - infoText__ableToScan: LocalizationValue; - infoText__unableToScan: LocalizationValue; - inputLabel__unableToScan1: LocalizationValue; - inputLabel__unableToScan2: LocalizationValue; - buttonAbleToScan__nonPrimary: LocalizationValue; - buttonUnableToScan__nonPrimary: LocalizationValue; - }; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - mfaPhoneCodePage: { - title: LocalizationValue; - primaryButton__addPhoneNumber: LocalizationValue; - subtitle__availablePhoneNumbers: LocalizationValue; - subtitle__unavailablePhoneNumbers: LocalizationValue; - successMessage: LocalizationValue; - removeResource: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - backupCodePage: { - title: LocalizationValue; - title__codelist: LocalizationValue; - subtitle__codelist: LocalizationValue; - infoText1: LocalizationValue; - infoText2: LocalizationValue; - successSubtitle: LocalizationValue; - successMessage: LocalizationValue; - actionLabel__copy: LocalizationValue; - actionLabel__copied: LocalizationValue; - actionLabel__download: LocalizationValue; - actionLabel__print: LocalizationValue; - }; - deletePage: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - actionDescription: LocalizationValue; - confirm: LocalizationValue; - }; - }; - userButton: { - action__manageAccount: LocalizationValue; - action__signOut: LocalizationValue; - action__signOutAll: LocalizationValue; - action__addAccount: LocalizationValue; - }; - organizationSwitcher: { - personalWorkspace: LocalizationValue; - notSelected: LocalizationValue; - action__createOrganization: LocalizationValue; - action__manageOrganization: LocalizationValue; - action__invitationAccept: LocalizationValue; - action__suggestionsAccept: LocalizationValue; - suggestionsAcceptedLabel: LocalizationValue; - }; - impersonationFab: { - title: LocalizationValue; - action__signOut: LocalizationValue; - }; - organizationProfile: { - badge__unverified: LocalizationValue; - badge__automaticInvitation: LocalizationValue; - badge__automaticSuggestion: LocalizationValue; - badge__manualInvitation: LocalizationValue; - navbar: { - title: LocalizationValue; - description: LocalizationValue; - }; - start: { - headerTitle__members: LocalizationValue; - headerTitle__settings: LocalizationValue; - }; - profilePage: { - title: LocalizationValue; - subtitle: LocalizationValue; - successMessage: LocalizationValue; - dangerSection: { - title: LocalizationValue; - leaveOrganization: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - actionDescription: LocalizationValue; - successMessage: LocalizationValue; - }; - deleteOrganization: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - actionDescription: LocalizationValue; - successMessage: LocalizationValue; - }; - }; - domainSection: { - title: LocalizationValue; - subtitle: LocalizationValue; - primaryButton: LocalizationValue; - unverifiedDomain_menuAction__verify: LocalizationValue; - unverifiedDomain_menuAction__remove: LocalizationValue; - }; - }; - createDomainPage: { - title: LocalizationValue; - subtitle: LocalizationValue; - }; - verifyDomainPage: { - title: LocalizationValue; - subtitle: LocalizationValue; - subtitleVerificationCodeScreen: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - resendButton: LocalizationValue; - }; - verifiedDomainPage: { - subtitle: LocalizationValue; - start: { - headerTitle__enrollment: LocalizationValue; - headerTitle__danger: LocalizationValue; - }; - enrollmentTab: { - subtitle: LocalizationValue; - manualInvitationOption__label: LocalizationValue; - manualInvitationOption__description: LocalizationValue; - automaticInvitationOption__label: LocalizationValue; - automaticInvitationOption__description: LocalizationValue; - automaticSuggestionOption__label: LocalizationValue; - automaticSuggestionOption__description: LocalizationValue; - formButton__save: LocalizationValue; - calloutInfoLabel: LocalizationValue; - calloutInvitationCountLabel: LocalizationValue; - calloutSuggestionCountLabel: LocalizationValue; - }; - dangerTab: { - removeDomainTitle: LocalizationValue; - removeDomainSubtitle: LocalizationValue; - removeDomainActionLabel__remove: LocalizationValue; - calloutInfoLabel: LocalizationValue; - }; - }; - removeDomainPage: { - title: LocalizationValue; - messageLine1: LocalizationValue; - messageLine2: LocalizationValue; - successMessage: LocalizationValue; - }; - invitePage: { - title: LocalizationValue; - subtitle: LocalizationValue; - successMessage: LocalizationValue; - detailsTitle__inviteFailed: LocalizationValue; - formButtonPrimary__continue: LocalizationValue; - }; - membersPage: { - detailsTitle__emptyRow: LocalizationValue; - action__invite: LocalizationValue; - start: { - headerTitle__members: LocalizationValue; - headerTitle__invitations: LocalizationValue; - headerTitle__requests: LocalizationValue; - }; - activeMembersTab: { - tableHeader__user: LocalizationValue; - tableHeader__joined: LocalizationValue; - tableHeader__role: LocalizationValue; - tableHeader__actions: LocalizationValue; - menuAction__remove: LocalizationValue; - }; - invitedMembersTab: { - tableHeader__invited: LocalizationValue; - menuAction__revoke: LocalizationValue; - }; - invitationsTab: { - table__emptyRow: LocalizationValue; - manualInvitations: { - headerTitle: LocalizationValue; - headerSubtitle: LocalizationValue; - }; - autoInvitations: { - headerTitle: LocalizationValue; - headerSubtitle: LocalizationValue; - primaryButton: LocalizationValue; - }; - }; - requestsTab: { - tableHeader__requested: LocalizationValue; - menuAction__approve: LocalizationValue; - menuAction__reject: LocalizationValue; - table__emptyRow: LocalizationValue; - requests: { - headerTitle: LocalizationValue; - headerSubtitle: LocalizationValue; - }; - autoSuggestions: { - headerTitle: LocalizationValue; - headerSubtitle: LocalizationValue; - primaryButton: LocalizationValue; - }; - }; - }; - }; - createOrganization: { - title: LocalizationValue; - formButtonSubmit: LocalizationValue; - invitePage: { - formButtonReset: LocalizationValue; - }; - }; - organizationList: { - createOrganization: LocalizationValue; - title: LocalizationValue; - titleWithoutPersonal: LocalizationValue; - subtitle: LocalizationValue; - action__createOrganization: LocalizationValue; - action__invitationAccept: LocalizationValue; - action__suggestionsAccept: LocalizationValue; - suggestionsAcceptedLabel: LocalizationValue; - invitationAcceptedLabel: LocalizationValue; - }; - unstable__errors: UnstableErrors; - dates: { - previous6Days: LocalizationValue; - lastDay: LocalizationValue; - sameDay: LocalizationValue; - nextDay: LocalizationValue; - next6Days: LocalizationValue; - numeric: LocalizationValue; - }; -}; - -type WithParamName = T & - Partial>}`, LocalizationValue>>; -type UnstableErrors = WithParamName<{ - identification_deletion_failed: LocalizationValue; - phone_number_exists: LocalizationValue; - form_identifier_not_found: LocalizationValue; - captcha_unavailable: LocalizationValue; - captcha_invalid: LocalizationValue; - form_password_pwned: LocalizationValue; - form_username_invalid_length: LocalizationValue; - form_username_invalid_character: LocalizationValue; - form_param_format_invalid: LocalizationValue; - form_param_format_invalid__email_address: LocalizationValue; - form_password_length_too_short: LocalizationValue; - form_param_nil: LocalizationValue; - form_code_incorrect: LocalizationValue; - form_password_incorrect: LocalizationValue; - form_password_validation_failed: LocalizationValue; - not_allowed_access: LocalizationValue; - form_identifier_exists: LocalizationValue; - form_password_not_strong_enough: LocalizationValue; - form_password_size_in_bytes_exceeded: LocalizationValue; - passwordComplexity: { - sentencePrefix: LocalizationValue; - minimumLength: LocalizationValue; - maximumLength: LocalizationValue; - requireNumbers: LocalizationValue; - requireLowercase: LocalizationValue; - requireUppercase: LocalizationValue; - requireSpecialCharacter: LocalizationValue; - }; - zxcvbn: { - notEnough: LocalizationValue; - couldBeStronger: LocalizationValue; - goodPassword: LocalizationValue; - warnings: { - straightRow: LocalizationValue; - keyPattern: LocalizationValue; - simpleRepeat: LocalizationValue; - extendedRepeat: LocalizationValue; - sequences: LocalizationValue; - recentYears: LocalizationValue; - dates: LocalizationValue; - topTen: LocalizationValue; - topHundred: LocalizationValue; - common: LocalizationValue; - similarToCommon: LocalizationValue; - wordByItself: LocalizationValue; - namesByThemselves: LocalizationValue; - commonNames: LocalizationValue; - userInputs: LocalizationValue; - pwned: LocalizationValue; - }; - suggestions: { - l33t: LocalizationValue; - reverseWords: LocalizationValue; - allUppercase: LocalizationValue; - capitalization: LocalizationValue; - dates: LocalizationValue; - recentYears: LocalizationValue; - associatedYears: LocalizationValue; - sequences: LocalizationValue; - repeated: LocalizationValue; - longerKeyboardPattern: LocalizationValue; - anotherWord: LocalizationValue; - useWords: LocalizationValue; - noNeed: LocalizationValue; - pwned: LocalizationValue; - }; - }; - form_param_max_length_exceeded: LocalizationValue; -}>; diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 1e25159acb0..96b986a8fe1 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -264,13 +264,16 @@ type _LocalizationResource = { formButtonPrimary__continue: LocalizationValue; formButtonPrimary__finish: LocalizationValue; formButtonReset: LocalizationValue; + navbar: { + title: LocalizationValue; + description: LocalizationValue; + }; start: { headerTitle__account: LocalizationValue; headerTitle__security: LocalizationValue; - headerSubtitle__account: LocalizationValue; - headerSubtitle__security: LocalizationValue; profileSection: { title: LocalizationValue; + primaryButton: LocalizationValue; }; usernameSection: { title: LocalizationValue; @@ -545,11 +548,13 @@ type _LocalizationResource = { badge__automaticInvitation: LocalizationValue; badge__automaticSuggestion: LocalizationValue; badge__manualInvitation: LocalizationValue; + navbar: { + title: LocalizationValue; + description: LocalizationValue; + }; start: { headerTitle__members: LocalizationValue; headerTitle__settings: LocalizationValue; - headerSubtitle__members: LocalizationValue; - headerSubtitle__settings: LocalizationValue; }; profilePage: { title: LocalizationValue; diff --git a/packages/types/tsup.config.ts b/packages/types/tsup.config.ts index f138fb99e10..992fe31fd0f 100644 --- a/packages/types/tsup.config.ts +++ b/packages/types/tsup.config.ts @@ -1,11 +1,9 @@ import { defineConfig } from 'tsup'; export default defineConfig(() => { - const uiRetheme = process.env.CLERK_RETHEME === '1' || process.env.CLERK_RETHEME === 'true'; - return { entry: { - index: uiRetheme ? 'src/index.retheme.ts' : 'src/index.ts', + index: 'src/index.ts', }, minify: false, clean: true,