[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}`,
- })}
- >
-
-
-
- >
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserInvitationList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserInvitationList.tsx
deleted file mode 100644
index 8eddb100f8b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserInvitationList.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useClerk, useOrganizationList } from '@clerk/shared/react';
-import type { OrganizationResource, UserOrganizationInvitationResource } from '@clerk/types';
-import { useState } from 'react';
-
-import { localizationKeys } from '../../customizables';
-import { useCardState, withCardStateProvider } from '../../elements';
-import { handleError } from '../../utils';
-import { populateCacheUpdateItem } from '../OrganizationSwitcher/utils';
-import { PreviewListItem, PreviewListItemButton } from './shared';
-import { MembershipPreview } from './UserMembershipList';
-import { organizationListParams } from './utils';
-
-export const AcceptRejectInvitationButtons = (props: { onAccept: () => void }) => {
- const card = useCardState();
-
- return (
-
- );
-};
-
-export const InvitationPreview = withCardStateProvider((props: UserOrganizationInvitationResource) => {
- const card = useCardState();
- const { getOrganization } = useClerk();
- const [acceptedOrganization, setAcceptedOrganization] = useState(null);
- const { userInvitations } = useOrganizationList({
- userInvitations: organizationListParams.userInvitations,
- });
-
- const handleAccept = () => {
- return card
- .runAsync(async () => {
- const updatedItem = await props.accept();
- const organization = await getOrganization(props.publicOrganizationData.id);
- return [updatedItem, organization] as const;
- })
- .then(([updatedItem, organization]) => {
- // Update cache in case another listener depends on it
- void userInvitations?.setData?.(cachedPages => populateCacheUpdateItem(updatedItem, cachedPages));
- setAcceptedOrganization(organization);
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- if (acceptedOrganization) {
- return ;
- }
-
- return (
-
- {/* TODO: Fix this properly */}
- {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserMembershipList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserMembershipList.tsx
deleted file mode 100644
index b834713d398..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserMembershipList.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useOrganizationList, useUser } from '@clerk/shared/react';
-import type { OrganizationResource } from '@clerk/types';
-
-import { useOrganizationListContext } from '../../contexts';
-import { OrganizationPreview, PersonalWorkspacePreview, useCardState, withCardStateProvider } from '../../elements';
-import { localizationKeys } from '../../localization';
-import { OrganizationListPreviewButton, sharedMainIdentifierSx } from './shared';
-
-export const MembershipPreview = withCardStateProvider((props: { organization: OrganizationResource }) => {
- const card = useCardState();
- const { navigateAfterSelectOrganization } = useOrganizationListContext();
- const { isLoaded, setActive } = useOrganizationList();
-
- if (!isLoaded) {
- return null;
- }
- const handleOrganizationClicked = (organization: OrganizationResource) => {
- return card.runAsync(async () => {
- await setActive({
- organization,
- });
- await navigateAfterSelectOrganization(organization);
- });
- };
- return (
- handleOrganizationClicked(props.organization)}>
-
-
- );
-});
-export const PersonalAccountPreview = withCardStateProvider(() => {
- const card = useCardState();
- const { hidePersonal, navigateAfterSelectPersonal } = useOrganizationListContext();
- const { isLoaded, setActive } = useOrganizationList();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
-
- const handlePersonalClicked = () => {
- if (!isLoaded) {
- return;
- }
- return card.runAsync(async () => {
- await setActive({
- organization: null,
- });
-
- await navigateAfterSelectPersonal(user);
- });
- };
-
- if (hidePersonal) {
- return null;
- }
-
- return (
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserSuggestionList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserSuggestionList.tsx
deleted file mode 100644
index e6e65ff0dfd..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/UserSuggestionList.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { useOrganizationList } from '@clerk/shared/react';
-import type { OrganizationSuggestionResource } from '@clerk/types';
-
-import { localizationKeys, Text } from '../../customizables';
-import { useCardState, withCardStateProvider } from '../../elements';
-import { handleError } from '../../utils';
-import { populateCacheUpdateItem } from '../OrganizationSwitcher/utils';
-import { PreviewListItem, PreviewListItemButton } from './shared';
-import { organizationListParams } from './utils';
-
-export const AcceptRejectInvitationButtons = (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 (
-
- );
-};
-
-export const SuggestionPreview = withCardStateProvider((props: OrganizationSuggestionResource) => {
- return (
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/__tests__/OrganizationList.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/__tests__/OrganizationList.test.tsx
deleted file mode 100644
index 0ccee062b45..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/__tests__/OrganizationList.test.tsx
+++ /dev/null
@@ -1,324 +0,0 @@
-import { describe } from '@jest/globals';
-
-import { render, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { createFakeOrganization } from '../../CreateOrganization/__tests__/CreateOrganization.test';
-import {
- createFakeUserOrganizationInvitation,
- createFakeUserOrganizationMembership,
-} from '../../OrganizationSwitcher/__tests__/utlis';
-import { OrganizationList } from '../OrganizationList';
-
-const { createFixtures } = bindCreateFixtures('OrganizationList');
-
-describe('OrganizationList', () => {
- it('renders component with personal and no data', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
-
- const { queryByText, queryByRole } = render(, { wrapper });
-
- await waitFor(() => {
- // Header
- expect(queryByRole('heading', { name: /select an account/i })).toBeInTheDocument();
- // Subheader
- expect(queryByText('to continue to TestApp')).toBeInTheDocument();
- //
- expect(queryByText('Personal account')).toBeInTheDocument();
- // Divider
- expect(queryByText('or')).toBeInTheDocument();
- expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
- });
- });
-
- describe('Personal Account', () => {
- it('hides the personal account with data to list', async () => {
- const { wrapper, props, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
-
- fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationMembership({
- id: '1',
- organization: {
- id: '1',
- name: 'Org1',
- slug: 'org1',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- ],
- total_count: 1,
- }),
- );
-
- props.setProps({ hidePersonal: true });
- const { queryByText, queryByRole } = render(, { wrapper });
-
- await waitFor(() => {
- // Header
- expect(queryByRole('heading', { name: /select an organization/i })).toBeInTheDocument();
- // Subheader
- expect(queryByText('to continue to TestApp')).toBeInTheDocument();
- // No personal
- expect(queryByText('Personal account')).not.toBeInTheDocument();
- // Display membership
- expect(queryByText('Org1')).toBeInTheDocument();
-
- // No Divider
- expect(queryByText('or')).toBeInTheDocument();
-
- expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
- });
- });
-
- it('hides the personal account with no data to list', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1', role: 'admin' }],
- });
- });
- props.setProps({ hidePersonal: true });
- const { queryByText, queryByRole, queryByLabelText } = render(, { wrapper });
-
- await waitFor(() => {
- // Header
- expect(queryByRole('heading', { name: /Create organization/i })).toBeInTheDocument();
- // Subheader
- expect(queryByText('to continue to TestApp')).toBeInTheDocument();
- //
- expect(queryByText('Personal account')).not.toBeInTheDocument();
- // No Divider
- expect(queryByText('or')).not.toBeInTheDocument();
-
- // Form fields of CreateOrganizationForm
- expect(queryByLabelText(/organization name/i)).toBeInTheDocument();
- expect(queryByLabelText(/slug url/i)).toBeInTheDocument();
-
- expect(queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
- expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
- });
- });
-
- it('lists invitations', async () => {
- const { wrapper, props, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
-
- fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationMembership({
- id: '1',
- organization: {
- id: '1',
- name: 'Org1',
- slug: 'org1',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- ],
- total_count: 1,
- }),
- );
-
- const invitation = createFakeUserOrganizationInvitation({
- id: '1',
- emailAddress: 'one@clerk.com',
- publicOrganizationData: {
- name: 'OrgOne',
- },
- });
-
- invitation.accept = jest.fn().mockResolvedValue(
- createFakeUserOrganizationInvitation({
- id: '1',
- emailAddress: 'one@clerk.com',
- publicOrganizationData: {
- name: 'OrgOne',
- },
- status: 'accepted',
- }),
- );
-
- fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
- Promise.resolve({
- data: [invitation],
- total_count: 1,
- }),
- );
-
- fixtures.clerk.getOrganization.mockResolvedValue(
- createFakeOrganization({
- adminDeleteEnabled: false,
- id: '2',
- maxAllowedMemberships: 0,
- membersCount: 1,
- name: 'OrgOne',
- pendingInvitationsCount: 0,
- slug: '',
- }),
- );
-
- props.setProps({ hidePersonal: true });
- const { queryByText, userEvent, getByRole, queryByRole } = render(, { wrapper });
-
- await waitFor(async () => {
- // Display membership
- expect(queryByText('Org1')).toBeInTheDocument();
- // Display invitation
- expect(queryByText('OrgOne')).toBeInTheDocument();
- await userEvent.click(getByRole('button', { name: 'Join' }));
- expect(queryByRole('button', { name: 'Join' })).not.toBeInTheDocument();
- });
- });
- });
-
- describe('CreateOrganization', () => {
- it('display CreateOrganization within OrganizationList', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
-
- const { queryByLabelText, getByRole, userEvent, queryByRole } = render(, { wrapper });
-
- await waitFor(async () => {
- // Header
- expect(queryByRole('heading', { name: /select an account/i })).toBeInTheDocument();
- // Form fields of CreateOrganizationForm
- expect(queryByLabelText(/organization name/i)).not.toBeInTheDocument();
- expect(queryByLabelText(/slug url/i)).not.toBeInTheDocument();
- await userEvent.click(getByRole('button', { name: 'Create organization' }));
- // Header
- expect(queryByRole('heading', { name: /Create organization/i })).toBeInTheDocument();
- // Form fields of CreateOrganizationForm
- expect(queryByLabelText(/organization name/i)).toBeInTheDocument();
- expect(queryByLabelText(/slug url/i)).toBeInTheDocument();
-
- expect(queryByRole('button', { name: 'Cancel' })).toBeInTheDocument();
- expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
- });
- });
-
- it('display CreateOrganization and navigates to Invite Members', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
-
- const { getByLabelText, getByRole, userEvent, queryByText } = render(, { wrapper });
-
- await waitFor(async () => {
- await userEvent.click(getByRole('button', { name: /create organization/i }));
-
- 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();
- });
- });
- });
-
- describe('navigation', () => {
- it('constructs afterSelectPersonalUrl from `:id` ', async () => {
- const { wrapper, fixtures, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- id: 'test_user_id',
- email_addresses: ['test@clerk.com'],
- });
- });
-
- props.setProps({
- afterSelectPersonalUrl: '/user/:id',
- });
- const { getByText, userEvent } = render(, {
- wrapper,
- });
-
- await waitFor(async () => {
- fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
- await userEvent.click(getByText(/Personal account/i));
-
- expect(fixtures.router.navigate).toHaveBeenCalledWith(`/user/test_user_id`);
- expect(fixtures.clerk.setActive).toHaveBeenCalledWith(
- expect.objectContaining({
- organization: null,
- }),
- );
- });
- });
-
- it('constructs afterSelectOrganizationUrl from `:slug` ', async () => {
- const { wrapper, fixtures, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- id: 'test_user_id',
- email_addresses: ['test@clerk.com'],
- });
- });
-
- const membership = createFakeUserOrganizationMembership({
- id: '1',
- organization: {
- id: '1',
- name: 'Org1',
- slug: 'org1',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- });
-
- fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [membership],
- total_count: 1,
- }),
- );
-
- props.setProps({
- afterSelectOrganizationUrl: '/org/:slug',
- });
-
- const { userEvent, getByRole } = render(, {
- wrapper,
- });
-
- await waitFor(async () => {
- fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
- await userEvent.click(getByRole('button', { name: /Org1/i }));
- expect(fixtures.clerk.setActive).toHaveBeenCalledWith(
- expect.objectContaining({
- organization: membership.organization,
- }),
- );
- expect(fixtures.router.navigate).toHaveBeenCalledWith(`/org/org1`);
- });
- });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/index.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationList/index.ts
deleted file mode 100644
index 0f38a0cd702..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './OrganizationList';
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/shared.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationList/shared.tsx
deleted file mode 100644
index 60174e70aab..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/shared.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import type { UserOrganizationInvitationResource } from '@clerk/types';
-import type { PropsWithChildren } from 'react';
-import { forwardRef } from 'react';
-
-import { Box, Button, Col, descriptors, Flex, Spinner } from '../../customizables';
-import { OrganizationPreview, PreviewButton } from '../../elements';
-import { ArrowRightIcon } from '../../icons';
-import type { ThemableCssProp } from '../../styledSystem';
-import { common } from '../../styledSystem';
-
-export const PreviewListItems = (props: PropsWithChildren) => {
- return (
- ({
- maxHeight: `calc(8 * ${t.sizes.$12})`,
- overflowY: 'auto',
- ...common.unstyledScrollbar(t),
- })}
- >
- {props.children}
-
- );
-};
-
-const sharedStyles: ThemableCssProp = t => ({
- height: t.space.$12,
- padding: `${t.space.$2} ${t.space.$8}`,
-});
-
-export const sharedMainIdentifierSx: ThemableCssProp = t => ({
- fontSize: t.fontSizes.$md,
- fontWeight: t.fontWeights.$normal,
- color: t.colors.$colorText,
-});
-
-export const PreviewListItem = (
- props: PropsWithChildren<{
- organizationData: UserOrganizationInvitationResource['publicOrganizationData'];
- }>,
-) => {
- return (
- ({
- minHeight: 'unset',
- justifyContent: 'space-between',
- }),
- sharedStyles,
- ]}
- elementDescriptor={descriptors.organizationListPreviewItem}
- >
-
- {props.children}
-
- );
-};
-
-export const PreviewListSpinner = forwardRef((_, ref) => {
- return (
- ({
- width: '100%',
- height: t.space.$12,
- position: 'relative',
- })}
- >
-
-
-
-
- );
-});
-
-export const PreviewListItemButton = (props: Parameters[0]) => {
- return (
-
- );
-};
-
-export const OrganizationListPreviewButton = (props: PropsWithChildren<{ onClick: () => void | Promise }>) => {
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationList/utils.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationList/utils.ts
deleted file mode 100644
index 0fd0d468fd3..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationList/utils.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { useOrganizationList } from '@clerk/shared/react';
-
-export const organizationListParams = {
- userMemberships: {
- infinite: true,
- },
- userInvitations: {
- infinite: true,
- },
- userSuggestions: {
- infinite: true,
- status: ['pending', 'accepted'],
- },
-} satisfies Parameters[0];
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActionConfirmationPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActionConfirmationPage.tsx
deleted file mode 100644
index 88868aaf9e7..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActionConfirmationPage.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { useOrganization, useUser } from '@clerk/shared/react';
-
-import { useWizard, Wizard } from '../../common';
-import { useOrganizationProfileContext } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
-import { localizationKeys, Text } from '../../customizables';
-import {
- ContentPage,
- Form,
- FormButtonContainer,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const LeaveOrganizationPage = () => {
- const card = useCardState();
- const { navigateAfterLeaveOrganization } = useOrganizationProfileContext();
- const { organization } = useOrganization();
- const { user } = useUser();
-
- if (!organization || !user) {
- return null;
- }
-
- const leave = () => {
- return card.runAsync(user.leaveOrganization(organization.id)).then(navigateAfterLeaveOrganization);
- };
-
- return (
-
- );
-};
-
-export const DeleteOrganizationPage = () => {
- const card = useCardState();
- const { navigateAfterLeaveOrganization } = useOrganizationProfileContext();
- const { organization } = useOrganization();
-
- if (!organization) {
- return null;
- }
-
- const deleteOrg = () => {
- return card.runAsync(organization.destroy()).then(navigateAfterLeaveOrganization);
- };
-
- return (
-
- );
-};
-
-type ActionConfirmationPageProps = {
- title: LocalizationKey;
- messageLine1: LocalizationKey;
- messageLine2: LocalizationKey;
- actionDescription?: LocalizationKey;
- organizationName?: string;
- successMessage: LocalizationKey;
- submitLabel: LocalizationKey;
- onConfirmation: () => Promise;
- variant?: 'primaryDanger' | 'primary';
-};
-
-const ActionConfirmationPage = withCardStateProvider((props: ActionConfirmationPageProps) => {
- const {
- title,
- messageLine1,
- messageLine2,
- actionDescription,
- organizationName,
- successMessage,
- submitLabel,
- onConfirmation,
- variant = 'primaryDanger',
- } = props;
- const wizard = useWizard();
- const card = useCardState();
- const { navigate } = useRouter();
-
- const confirmationField = useFormControl('deleteOrganizationConfirmation', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__confirmDeletion'),
- isRequired: true,
- placeholder: organizationName,
- });
-
- const canSubmit = actionDescription ? confirmationField.value === organizationName : true;
-
- const handleSubmit = async () => {
- if (!canSubmit) {
- return;
- }
- try {
- await onConfirmation().then(() => wizard.nextStep());
- } catch (e) {
- handleError(e, [], card.setError);
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- await navigate('..');
- }}
- />
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActiveMembersList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActiveMembersList.tsx
deleted file mode 100644
index 1355fbbe8fc..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ActiveMembersList.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { useOrganization, useUser } from '@clerk/shared/react';
-import type { OrganizationMembershipResource } from '@clerk/types';
-
-import { Gate } from '../../common/Gate';
-import { Badge, localizationKeys, Td, Text } from '../../customizables';
-import { ThreeDotsMenu, useCardState, UserPreview } from '../../elements';
-import { useFetchRoles, useLocalizeCustomRoles } from '../../hooks/useFetchRoles';
-import { handleError } from '../../utils';
-import { DataTable, RoleSelect, RowContainer } from './MemberListTable';
-
-const membershipsParams = {
- memberships: {
- pageSize: 10,
- keepPreviousData: true,
- },
-};
-
-export const ActiveMembersList = () => {
- const card = useCardState();
- const { organization, memberships } = useOrganization(membershipsParams);
-
- const { options, isLoading: loadingRoles } = useFetchRoles();
-
- if (!organization) {
- return null;
- }
-
- const handleRoleChange = (membership: OrganizationMembershipResource) => (newRole: string) =>
- card.runAsync(() => membership.update({ role: newRole })).catch(err => handleError(err, [], card.setError));
-
- const handleRemove = (membership: OrganizationMembershipResource) => async () => {
- return card
- .runAsync(async () => {
- const destroyedMembership = await membership.destroy();
- await memberships?.revalidate?.();
- return destroyedMembership;
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- return (
- memberships?.fetchPage?.(n)}
- itemCount={memberships?.count || 0}
- pageCount={memberships?.pageCount || 0}
- itemsPerPage={membershipsParams.memberships.pageSize}
- isLoading={memberships?.isLoading || loadingRoles}
- emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.detailsTitle__emptyRow')}
- headers={[
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__joined'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__role'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__actions'),
- ]}
- rows={(memberships?.data || []).map(m => (
-
- ))}
- />
- );
-};
-
-// TODO: Find a way to disable Role select based on last admin by using permissions
-const MemberRow = (props: {
- membership: OrganizationMembershipResource;
- onRemove: () => unknown;
- options: Parameters[0]['roles'];
- onRoleChange: (role: string) => unknown;
-}) => {
- const { membership, onRemove, onRoleChange, options } = props;
- const { localizeCustomRole } = useLocalizeCustomRoles();
- const card = useCardState();
- const { user } = useUser();
-
- const isCurrentUser = user?.id === membership.publicUserData.userId;
- const unlocalizedRoleLabel = options?.find(a => a.value === membership.role)?.label;
-
- return (
-
-
- }
- />
- |
- {membership.createdAt.toLocaleDateString()} |
-
- ({ opacity: t.opacity.$inactive })}>
- {localizeCustomRole(membership.role) || unlocalizedRoleLabel}
-
- }
- >
-
-
- |
-
-
-
-
- |
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/AddDomainPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/AddDomainPage.tsx
deleted file mode 100644
index 076d450893c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/AddDomainPage.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import React from 'react';
-
-import { useEnvironment } from '../../contexts';
-import { localizationKeys } from '../../customizables';
-import { ContentPage, Form, FormButtons, useCardState, withCardStateProvider } from '../../elements';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const AddDomainPage = withCardStateProvider(() => {
- const { organizationSettings } = useEnvironment();
- const title = localizationKeys('organizationProfile.createDomainPage.title');
- const subtitle = localizationKeys('organizationProfile.createDomainPage.subtitle');
- const breadcrumbTitle = localizationKeys('organizationProfile.profilePage.domainSection.title');
- const card = useCardState();
- const { organization } = useOrganization();
- const { navigate } = useRouter();
-
- const nameField = useFormControl('name', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__organizationDomain'),
- placeholder: localizationKeys('formFieldInputPlaceholder__organizationDomain'),
- });
-
- if (!organization || !organizationSettings) {
- return null;
- }
-
- const canSubmit = nameField.value.trim() !== '';
-
- const onSubmit = (e: React.FormEvent) => {
- nameField.setError(undefined);
- e.preventDefault();
- return organization
- .createDomain(nameField.value)
- .then(res => {
- if (res.verification && res.verification.status === 'verified') {
- return navigate(`../domain/${res.id}?mode=select`);
- }
- return navigate(`../domain/${res.id}/verify`);
- })
- .catch(err => {
- handleError(err, [nameField], card.setError);
- });
- };
-
- return (
-
-
-
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/BillingWidget.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/BillingWidget.tsx
deleted file mode 100644
index aaee065c329..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/BillingWidget.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { runIfFunctionOrReturn } from '../../../utils';
-import { AlertIcon, Flex, Link, Text } from '../../customizables';
-import { useRouter } from '../../router';
-
-export const BillingWidget = ({
- __unstable_manageBillingUrl,
- __unstable_manageBillingMembersLimit,
-}: {
- __unstable_manageBillingUrl: string | ((args: any) => string);
- __unstable_manageBillingMembersLimit: string | ((args: any) => string);
-}) => {
- const router = useRouter();
-
- return (
- ({ background: theme.colors.$blackAlpha50, padding: theme.space.$4, borderRadius: theme.radii.$md })}
- >
- ({ marginTop: t.space.$1 })}
- />
-
- This organization is limited to {runIfFunctionOrReturn(__unstable_manageBillingMembersLimit)} members.
-
- ({
- alignSelf: 'flex-start',
- color: t.colors.$primary500,
- fontWeight: t.fontWeights.$normal,
- })}
- onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
- >
- Upgrade for unlimited members
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/DomainList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/DomainList.tsx
deleted file mode 100644
index a40f3f9b679..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/DomainList.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type {
- GetDomainsParams,
- OrganizationDomainResource,
- OrganizationDomainVerificationStatus,
- OrganizationEnrollmentMode,
-} from '@clerk/types';
-import React, { useMemo } from 'react';
-
-import { stripOrigin, toURL, trimLeadingSlash } from '../../../utils';
-import { useGate, withGate } from '../../common';
-import type { LocalizationKey } from '../../customizables';
-import { Box, Col, descriptors, localizationKeys, Spinner } from '../../customizables';
-import { ArrowBlockButton, BlockWithTrailingComponent, ThreeDotsMenu } from '../../elements';
-import { useInView } from '../../hooks';
-import { useRouter } from '../../router';
-import { EnrollmentBadge } from './EnrollmentBadge';
-
-type DomainListProps = GetDomainsParams & {
- verificationStatus?: OrganizationDomainVerificationStatus;
- enrollmentMode?: OrganizationEnrollmentMode;
- /**
- * Enables internal links to navigate to the correct page
- * based on when this component is used
- */
- redirectSubPath: 'organization-settings/domain' | 'domain';
- fallback?: React.ReactNode;
-};
-
-const buildDomainListRelativeURL = (parentPath: string, domainId: string, mode?: 'verify' | 'remove') =>
- trimLeadingSlash(stripOrigin(toURL(`${parentPath}/${domainId}/${mode || ''}`)));
-
-const useMenuActions = (
- parentPath: string,
- domainId: string,
-): { label: LocalizationKey; onClick: () => Promise; isDestructive?: boolean }[] => {
- const { isAuthorizedUser: canManageDomain } = useGate({ permission: 'org:sys_domains:manage' });
- const { navigate } = useRouter();
-
- const menuActions = [];
-
- if (canManageDomain) {
- menuActions.push({
- label: localizationKeys('organizationProfile.profilePage.domainSection.unverifiedDomain_menuAction__verify'),
- onClick: () => navigate(buildDomainListRelativeURL(parentPath, domainId, 'verify')),
- });
-
- menuActions.push({
- label: localizationKeys('organizationProfile.profilePage.domainSection.unverifiedDomain_menuAction__remove'),
- isDestructive: true,
- onClick: () => navigate(buildDomainListRelativeURL(parentPath, domainId, 'remove')),
- });
- }
-
- return menuActions;
-};
-
-const DomainListDotMenu = ({
- redirectSubPath,
- domainId,
-}: Pick & {
- domainId: OrganizationDomainResource['id'];
-}) => {
- const actions = useMenuActions(redirectSubPath, domainId);
- return ;
-};
-
-export const DomainList = withGate(
- (props: DomainListProps) => {
- const { verificationStatus, enrollmentMode, redirectSubPath, fallback, ...rest } = props;
- const { organization, domains } = useOrganization({
- domains: {
- infinite: true,
- ...rest,
- },
- });
-
- const { isAuthorizedUser: canManageDomain } = useGate({ permission: 'org:sys_domains:manage' });
- const { ref } = useInView({
- threshold: 0,
- onChange: inView => {
- if (inView) {
- void domains?.fetchNext?.();
- }
- },
- });
- const { navigate } = useRouter();
-
- const domainList = useMemo(() => {
- if (!domains?.data) {
- return [];
- }
-
- return domains.data.filter(d => {
- let matchesStatus = true;
- let matchesMode = true;
- if (verificationStatus) {
- matchesStatus = !!d.verification && d.verification.status === verificationStatus;
- }
- if (enrollmentMode) {
- matchesMode = d.enrollmentMode === enrollmentMode;
- }
-
- return matchesStatus && matchesMode;
- });
- }, [domains?.data]);
-
- if (!organization) {
- return null;
- }
-
- // TODO: Split this to smaller components
- return (
-
- {domainList.length === 0 && !domains?.isLoading && fallback}
- {domainList.map(d => {
- if (!(d.verification && d.verification.status === 'verified') || !canManageDomain) {
- return (
- ({
- '&:hover': {
- backgroundColor: t.colors.$blackAlpha50,
- },
- padding: `${t.space.$none} ${t.space.$4}`,
- minHeight: t.sizes.$10,
- })}
- badge={}
- trailingComponent={
- canManageDomain ? (
-
- ) : undefined
- }
- >
- {d.name}
-
- );
- }
-
- return (
- : undefined}
- sx={t => ({
- padding: `${t.space.$3} ${t.space.$4}`,
- minHeight: t.sizes.$10,
- })}
- onClick={() => navigate(buildDomainListRelativeURL(redirectSubPath, d.id))}
- >
- {d.name}
-
- );
- })}
- {(domains?.hasNextPage || domains?.isFetching) && (
- ({
- width: '100%',
- height: t.space.$10,
- position: 'relative',
- }),
- ]}
- >
-
-
-
-
- )}
-
- );
- },
- {
- permission: 'org:sys_domains:read',
- },
-);
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/EnrollmentBadge.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/EnrollmentBadge.tsx
deleted file mode 100644
index 1c2d3db6fef..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/EnrollmentBadge.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { OrganizationDomainResource } from '@clerk/types';
-
-import type { LocalizationKey } from '../../customizables';
-import { Badge, localizationKeys } from '../../customizables';
-
-const badgeLabelsMap: Record = {
- manual_invitation: localizationKeys('organizationProfile.badge__manualInvitation'),
- automatic_invitation: localizationKeys('organizationProfile.badge__automaticInvitation'),
- automatic_suggestion: localizationKeys('organizationProfile.badge__automaticSuggestion'),
-};
-
-export const EnrollmentBadge = (props: { organizationDomain: OrganizationDomainResource | null }) => {
- const { organizationDomain } = props;
- if (!organizationDomain) {
- return null;
- }
-
- if (!(organizationDomain.verification && organizationDomain.verification.status === 'verified')) {
- return (
-
- );
- }
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersForm.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersForm.tsx
deleted file mode 100644
index 275317b146c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersForm.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { isClerkAPIResponseError } from '@clerk/shared/error';
-import { useOrganization } from '@clerk/shared/react';
-import type { ClerkAPIError, MembershipRole } from '@clerk/types';
-import type { FormEvent } from 'react';
-import { useState } from 'react';
-
-import { Flex, Text } from '../../customizables';
-import { Form, FormButtonContainer, TagInput, useCardState } from '../../elements';
-import { useFetchRoles } from '../../hooks/useFetchRoles';
-import type { LocalizationKey } from '../../localization';
-import { localizationKeys, useLocalizations } from '../../localization';
-import { useRouter } from '../../router';
-import { createListFormat, handleError, useFormControl } from '../../utils';
-import { RoleSelect } from './MemberListTable';
-
-const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
-
-type InviteMembersFormProps = {
- onSuccess: () => void;
- onReset?: () => void;
- primaryButtonLabel?: LocalizationKey;
- resetButtonLabel?: LocalizationKey;
-};
-
-export const InviteMembersForm = (props: InviteMembersFormProps) => {
- const { navigate } = useRouter();
- const { onSuccess, onReset = () => navigate('..'), resetButtonLabel } = props;
- const { organization, invitations } = useOrganization({
- invitations: {
- pageSize: 10,
- keepPreviousData: true,
- },
- });
- const card = useCardState();
- const { t, locale } = useLocalizations();
- const [isValidUnsubmittedEmail, setIsValidUnsubmittedEmail] = useState(false);
-
- const validateUnsubmittedEmail = (value: string) => setIsValidUnsubmittedEmail(isEmail(value));
-
- const emailAddressField = useFormControl('emailAddress', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__emailAddresses'),
- });
-
- const roleField = useFormControl('role', '', {
- label: localizationKeys('formFieldLabel__role'),
- });
-
- if (!organization) {
- return null;
- }
-
- const {
- props: {
- setError,
- setWarning,
- setSuccess,
- setInfo,
- isFocused,
- validatePassword,
- setHasPassedComplexity,
- hasPassedComplexity,
- feedback,
- feedbackType,
- clearFeedback,
- ...restEmailAddressProps
- },
- } = emailAddressField;
-
- const canSubmit = (!!emailAddressField.value.length || isValidUnsubmittedEmail) && !!roleField.value;
-
- const onSubmit = (e: FormEvent) => {
- e.preventDefault();
-
- const submittedData = new FormData(e.currentTarget);
- return organization
- .inviteMembers({
- emailAddresses: emailAddressField.value.split(','),
- role: submittedData.get('role') as MembershipRole,
- })
- .then(async () => {
- await invitations?.revalidate?.();
- return onSuccess();
- })
- .catch(err => {
- if (isClerkAPIResponseError(err)) {
- removeInvalidEmails(err.errors[0]);
- }
-
- if (isClerkAPIResponseError(err) && err.errors?.[0]?.code === 'duplicate_record') {
- const unlocalizedEmailsList = err.errors[0].meta?.emailAddresses || [];
- card.setError(
- t(
- localizationKeys('organizationProfile.invitePage.detailsTitle__inviteFailed', {
- // Create a localized list of email addresses
- email_addresses: createListFormat(unlocalizedEmailsList, locale),
- }),
- ),
- );
- } else {
- handleError(err, [], card.setError);
- }
- });
- };
-
- const removeInvalidEmails = (err: ClerkAPIError) => {
- const invalidEmails = new Set([...(err.meta?.emailAddresses ?? []), ...(err.meta?.identifiers ?? [])]);
- const emails = emailAddressField.value.split(',');
- emailAddressField.setValue(emails.filter(e => !invalidEmails.has(e)).join(','));
- };
-
- return (
-
-
-
-
-
- ({ fontSize: t.fontSizes.$xs })}
- />
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const AsyncRoleSelect = (field: ReturnType>) => {
- const { options, isLoading } = useFetchRoles();
-
- return (
-
-
-
- field.setValue(value)}
- triggerSx={t => ({ width: t.sizes.$48, justifyContent: 'space-between', display: 'flex' })}
- optionListSx={t => ({ minWidth: t.sizes.$48 })}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersPage.tsx
deleted file mode 100644
index 7e42fd45430..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InviteMembersPage.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-
-import { runIfFunctionOrReturn } from '../../../utils';
-import { useWizard, Wizard } from '../../common';
-import { useOrganizationProfileContext } from '../../contexts';
-import { descriptors, Flex, localizationKeys, Text } from '../../customizables';
-import { ContentPage, IconCircle, SuccessPage, useCardState, withCardStateProvider } from '../../elements';
-import { Email } from '../../icons';
-import { BillingWidget } from './BillingWidget';
-import { InviteMembersForm } from './InviteMembersForm';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const InviteMembersPage = withCardStateProvider(() => {
- const title = localizationKeys('organizationProfile.invitePage.title');
- const subtitle = localizationKeys('organizationProfile.invitePage.subtitle');
- const card = useCardState();
- const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
- const { organization } = useOrganization();
- //@ts-expect-error
- const { __unstable_manageBillingUrl, __unstable_manageBillingMembersLimit } = useOrganizationProfileContext();
-
- if (!organization) {
- return null;
- }
-
- const reachedOrganizationMemberLimit =
- !!__unstable_manageBillingMembersLimit &&
- runIfFunctionOrReturn(__unstable_manageBillingMembersLimit) <=
- organization.pendingInvitationsCount + organization.membersCount;
-
- return (
-
-
- {reachedOrganizationMemberLimit && __unstable_manageBillingUrl && (
-
- )}
-
-
- }
- Breadcrumbs={OrganizationProfileBreadcrumbs}
- />
-
- );
-});
-
-export const InvitationsSentMessage = () => {
- return (
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InvitedMembersList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InvitedMembersList.tsx
deleted file mode 100644
index 07f82f03d43..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/InvitedMembersList.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type { OrganizationInvitationResource } from '@clerk/types';
-
-import { localizationKeys, Td, Text } from '../../customizables';
-import { ThreeDotsMenu, useCardState, UserPreview } from '../../elements';
-import { useFetchRoles, useLocalizeCustomRoles } from '../../hooks/useFetchRoles';
-import { handleError } from '../../utils';
-import { DataTable, RowContainer } from './MemberListTable';
-
-const invitationsParams = {
- invitations: {
- pageSize: 10,
- keepPreviousData: true,
- },
-};
-
-export const InvitedMembersList = () => {
- const card = useCardState();
- const { organization, invitations } = useOrganization(invitationsParams);
-
- const { options, isLoading: loadingRoles } = useFetchRoles();
-
- if (!organization) {
- return null;
- }
-
- const revoke = (invitation: OrganizationInvitationResource) => async () => {
- return card
- .runAsync(async () => {
- await invitation.revoke();
- await invitations?.revalidate?.();
- return invitation;
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- return (
- null)}
- itemCount={invitations?.count || 0}
- pageCount={invitations?.pageCount || 0}
- itemsPerPage={invitationsParams.invitations.pageSize}
- isLoading={invitations?.isLoading || loadingRoles}
- emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.invitationsTab.table__emptyRow')}
- headers={[
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'),
- localizationKeys('organizationProfile.membersPage.invitedMembersTab.tableHeader__invited'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__role'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__actions'),
- ]}
- rows={(invitations?.data || []).map(i => (
-
- ))}
- />
- );
-};
-
-const InvitationRow = (props: {
- invitation: OrganizationInvitationResource;
- options: ReturnType['options'];
- onRevoke: () => unknown;
-}) => {
- const { invitation, onRevoke, options } = props;
- const { localizeCustomRole } = useLocalizeCustomRoles();
-
- const unlocalizedRoleLabel = options?.find(a => a.value === invitation.role)?.label;
-
- return (
-
-
-
- |
- {invitation.createdAt.toLocaleDateString()} |
-
-
- |
-
-
- |
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MemberListTable.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MemberListTable.tsx
deleted file mode 100644
index 98f33d6faac..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MemberListTable.tsx
+++ /dev/null
@@ -1,179 +0,0 @@
-import type { MembershipRole } from '@clerk/types';
-import React from 'react';
-
-import type { LocalizationKey } from '../../customizables';
-import { Col, descriptors, Flex, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables';
-import { Pagination, Select, SelectButton, SelectOptionList } from '../../elements';
-import { useLocalizeCustomRoles } from '../../hooks/useFetchRoles';
-import type { PropsOfComponent, ThemableCssProp } from '../../styledSystem';
-
-type MembersListTableProps = {
- headers: LocalizationKey[];
- rows: React.ReactNode[];
- isLoading?: boolean;
- page: number;
- onPageChange: (page: number) => void;
- itemCount: number;
- emptyStateLocalizationKey: LocalizationKey;
- pageCount: number;
- itemsPerPage: number;
-};
-
-export const DataTable = (props: MembersListTableProps) => {
- const {
- headers,
- page,
- onPageChange,
- rows,
- isLoading,
- itemCount,
- itemsPerPage,
- pageCount,
- emptyStateLocalizationKey,
- } = props;
-
- const startingRow = itemCount > 0 ? Math.max(0, (page - 1) * itemsPerPage) + 1 : 0;
- const endingRow = Math.min(page * itemsPerPage, itemCount);
-
- return (
-
-
-
-
-
- {headers.map((h, index) => (
- |
- ))}
-
-
-
- {isLoading ? (
-
-
-
- |
-
- ) : !rows.length ? (
-
- ) : (
- rows
- )}
-
-
-
- {
-
- }
-
- );
-};
-
-const EmptyRow = (props: { localizationKey: LocalizationKey }) => {
- return (
-
-
- ({
- margin: 'auto',
- display: 'block',
- width: 'fit-content',
- fontSize: t.fontSizes.$xs,
- })}
- />
- |
-
- );
-};
-
-export const RowContainer = (props: PropsOfComponent) => {
- return (
- ({ ':hover': { backgroundColor: t.colors.$blackAlpha50 } })}
- />
- );
-};
-
-export const RoleSelect = (props: {
- roles: { label: string; value: string }[] | undefined;
- value: MembershipRole;
- onChange: (params: string) => unknown;
- isDisabled?: boolean;
- triggerSx?: ThemableCssProp;
- optionListSx?: ThemableCssProp;
-}) => {
- const { value, roles, onChange, isDisabled, triggerSx, optionListSx } = props;
-
- const shouldDisplayLegacyRoles = !roles;
-
- const legacyRoles: Array<{ label: string; value: MembershipRole }> = [
- { label: 'admin', value: 'admin' },
- { label: 'basic_member', value: 'basic_member' },
- ];
-
- const legacyExcludedRoles: Array<{ label: string; value: MembershipRole }> = [
- { label: 'guest_member', value: 'guest_member' },
- ];
- const { localizeCustomRole } = useLocalizeCustomRoles();
-
- const selectedRole = [...(roles || []), ...legacyRoles, ...legacyExcludedRoles].find(r => r.value === value);
-
- const localizedOptions = (!shouldDisplayLegacyRoles ? roles : legacyRoles).map(role => ({
- value: role.value,
- label: localizeCustomRole(role.value) || role.label,
- }));
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MembershipWidget.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MembershipWidget.tsx
deleted file mode 100644
index 1a5def49cb9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/MembershipWidget.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-
-import { runIfFunctionOrReturn } from '../../../utils';
-import { useOrganizationProfileContext } from '../../contexts';
-import { Col, Flex, Link, Text } from '../../customizables';
-import { useRouter } from '../../router';
-
-export const MembershipWidget = () => {
- const { organization } = useOrganization();
- //@ts-expect-error
- const { __unstable_manageBillingUrl, __unstable_manageBillingLabel, __unstable_manageBillingMembersLimit } =
- useOrganizationProfileContext();
- const router = useRouter();
-
- if (!organization) {
- return null;
- }
-
- const totalCount = organization?.membersCount + organization?.pendingInvitationsCount;
-
- return (
- ({
- background: theme.colors.$blackAlpha50,
- padding: theme.space.$4,
- justifyContent: 'space-between',
- alignItems: 'flex-start',
- borderRadius: theme.radii.$md,
- })}
- >
-
- Members can be given access to applications.
-
- {runIfFunctionOrReturn(__unstable_manageBillingMembersLimit) > 0 && (
- ({
- alignSelf: 'flex-start',
- color: t.colors.$primary500,
- marginTop: t.space.$1,
- })}
- onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
- >
- {runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'}
-
- )}
-
-
- ({
- color: t.colors.$blackAlpha600,
- })}
- >
- {totalCount} of{' '}
- {__unstable_manageBillingMembersLimit
- ? `${runIfFunctionOrReturn(__unstable_manageBillingMembersLimit)} members`
- : 'unlimited'}
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembers.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembers.tsx
deleted file mode 100644
index 14493135fa9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembers.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-
-import { NotificationCountBadge, useGate } from '../../common';
-import { useEnvironment, useOrganizationProfileContext } from '../../contexts';
-import { Col, descriptors, Flex, localizationKeys } from '../../customizables';
-import {
- CardAlert,
- Header,
- NavbarMenuButtonRow,
- Tab,
- TabPanel,
- TabPanels,
- Tabs,
- TabsList,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { ActiveMembersList } from './ActiveMembersList';
-import { MembershipWidget } from './MembershipWidget';
-import { OrganizationMembersTabInvitations } from './OrganizationMembersTabInvitations';
-import { OrganizationMembersTabRequests } from './OrganizationMembersTabRequests';
-
-export const OrganizationMembers = withCardStateProvider(() => {
- const { organizationSettings } = useEnvironment();
- const card = useCardState();
- const { isAuthorizedUser: canManageMemberships } = useGate({ permission: 'org:sys_memberships:manage' });
- const { isAuthorizedUser: canReadMemberships } = useGate({ permission: 'org:sys_memberships:read' });
- const isDomainsEnabled = organizationSettings?.domains?.enabled;
- const { membershipRequests } = useOrganization({
- membershipRequests: isDomainsEnabled || undefined,
- });
-
- // @ts-expect-error This property is not typed. It is used by our dashboard in order to render a billing widget.
- const { __unstable_manageBillingUrl } = useOrganizationProfileContext();
-
- if (canManageMemberships === null) {
- return null;
- }
-
- return (
-
- {card.error}
-
-
-
-
-
-
-
- {canReadMemberships && (
-
- )}
- {canManageMemberships && (
-
- )}
- {canManageMemberships && isDomainsEnabled && (
-
-
-
- )}
-
-
- {canReadMemberships && (
-
-
- {canManageMemberships && __unstable_manageBillingUrl && }
-
-
-
- )}
- {canManageMemberships && (
-
-
-
- )}
- {canManageMemberships && isDomainsEnabled && (
-
-
-
- )}
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabInvitations.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabInvitations.tsx
deleted file mode 100644
index 961ed36a61f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabInvitations.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { BlockButton, Gate } from '../../common';
-import { useEnvironment, useOrganizationProfileContext } from '../../contexts';
-import { Col, descriptors, Flex, Icon, localizationKeys } from '../../customizables';
-import { Header, IconButton } from '../../elements';
-import { UserAdd } from '../../icons';
-import { useRouter } from '../../router';
-import { DomainList } from './DomainList';
-import { InvitedMembersList } from './InvitedMembersList';
-import { MembershipWidget } from './MembershipWidget';
-
-export const OrganizationMembersTabInvitations = () => {
- const { organizationSettings } = useEnvironment();
- const { navigate } = useRouter();
- //@ts-expect-error
- const { __unstable_manageBillingUrl } = useOrganizationProfileContext();
-
- const isDomainsEnabled = organizationSettings?.domains?.enabled;
-
- return (
-
- {__unstable_manageBillingUrl && }
-
- {isDomainsEnabled && (
-
-
-
-
-
-
- navigate('organization-settings/domain')}
- />
- }
- redirectSubPath={'organization-settings/domain'}
- verificationStatus={'verified'}
- enrollmentMode={'automatic_invitation'}
- />
-
-
- )}
-
-
-
-
-
-
-
-
- navigate('invite-members')}
- icon={
- ({ marginRight: t.space.$2 })}
- />
- }
- textVariant='buttonSmall'
- localizationKey={localizationKeys('organizationProfile.membersPage.action__invite')}
- />
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabRequests.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabRequests.tsx
deleted file mode 100644
index 7ed209311c2..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationMembersTabRequests.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { BlockButton, Gate } from '../../common';
-import { useEnvironment, useOrganizationProfileContext } from '../../contexts';
-import { Col, Flex, localizationKeys } from '../../customizables';
-import { Header } from '../../elements';
-import { useRouter } from '../../router';
-import { DomainList } from './DomainList';
-import { MembershipWidget } from './MembershipWidget';
-import { RequestToJoinList } from './RequestToJoinList';
-
-export const OrganizationMembersTabRequests = () => {
- const { organizationSettings } = useEnvironment();
- const { navigate } = useRouter();
- //@ts-expect-error
- const { __unstable_manageBillingUrl } = useOrganizationProfileContext();
-
- const isDomainsEnabled = organizationSettings?.domains?.enabled;
-
- return (
-
- {__unstable_manageBillingUrl && }
-
- {isDomainsEnabled && (
-
-
-
-
-
-
- navigate('organization-settings/domain')}
- />
- }
- redirectSubPath={'organization-settings/domain'}
- verificationStatus={'verified'}
- enrollmentMode={'automatic_suggestion'}
- />
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfile.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfile.tsx
deleted file mode 100644
index 8560fd9c38e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfile.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type { OrganizationProfileModalProps, OrganizationProfileProps } from '@clerk/types';
-import React from 'react';
-
-import { withOrganizationsEnabledGuard, withRedirectToHomeOrganizationGuard } from '../../common';
-import { ComponentContext, withCoreUserGuard } from '../../contexts';
-import { Flow } from '../../customizables';
-import { ProfileCard, withCardStateProvider } from '../../elements';
-import { Route, Switch } from '../../router';
-import type { OrganizationProfileCtx } from '../../types';
-import { OrganizationProfileNavbar } from './OrganizationProfileNavbar';
-import { OrganizationProfileRoutes } from './OrganizationProfileRoutes';
-
-const _OrganizationProfile = (_: OrganizationProfileProps) => {
- const { organization } = useOrganization();
-
- if (!organization) {
- return null;
- }
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
-
-const AuthenticatedRoutes = withCoreUserGuard(() => {
- const contentRef = React.useRef(null);
- return (
- ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })}
- >
-
-
-
-
- );
-});
-
-export const OrganizationProfile = withRedirectToHomeOrganizationGuard(
- withOrganizationsEnabledGuard(withCardStateProvider(_OrganizationProfile), 'OrganizationProfile', {
- mode: 'redirect',
- }),
-);
-
-export const OrganizationProfileModal = (props: OrganizationProfileModalProps): JSX.Element => {
- const organizationProfileProps: OrganizationProfileCtx = {
- ...props,
- routing: 'virtual',
- componentName: 'OrganizationProfile',
- mode: 'modal',
- };
-
- return (
-
-
- {/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx
deleted file mode 100644
index baa45033082..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { OrganizationResource } from '@clerk/types';
-
-import type { AvatarUploaderProps } from '../../elements';
-import { AvatarUploader, OrganizationAvatar } from '../../elements';
-import { localizationKeys } from '../../localization';
-
-export const OrganizationProfileAvatarUploader = (
- props: Omit & { organization: Partial },
-) => {
- const { organization, ...rest } = props;
-
- return (
- theme.sizes.$12}
- {...organization}
- />
- }
- />
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx
deleted file mode 100644
index 6f7a90db4b5..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import React from 'react';
-
-import { useGate } from '../../common';
-import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID } from '../../constants';
-import { useOrganizationProfileContext } from '../../contexts';
-import { Breadcrumbs, NavBar, NavbarContextProvider, OrganizationPreview } from '../../elements';
-import { localizationKeys } from '../../localization';
-import type { PropsOfComponent } from '../../styledSystem';
-
-export const OrganizationProfileNavbar = (
- props: React.PropsWithChildren, 'contentRef'>>,
-) => {
- const { organization } = useOrganization();
- const { pages } = useOrganizationProfileContext();
-
- const { isAuthorizedUser: allowMembersRoute } = useGate(
- has =>
- has({
- permission: 'org:sys_memberships:read',
- }) || has({ permission: 'org:sys_memberships:manage' }),
- );
-
- if (!organization) {
- return null;
- }
-
- return (
-
- ({ margin: `0 0 ${t.space.$4} ${t.space.$2}` })}
- />
- }
- routes={pages.routes.filter(
- r =>
- r.id !== ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS ||
- (r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS && allowMembersRoute),
- )}
- contentRef={props.contentRef}
- />
- {props.children}
-
- );
-};
-
-export const OrganizationProfileBreadcrumbs = (props: Pick, 'title'>) => {
- const { pages } = useOrganizationProfileContext();
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx
deleted file mode 100644
index ed257be5c41..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { Gate } from '../../common';
-import { CustomPageContentContainer } from '../../common/CustomPageContentContainer';
-import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID } from '../../constants';
-import { useOrganizationProfileContext } from '../../contexts';
-import { ProfileCardContent } from '../../elements';
-import { Route, Switch } from '../../router';
-import type { PropsOfComponent } from '../../styledSystem';
-import { DeleteOrganizationPage, LeaveOrganizationPage } from './ActionConfirmationPage';
-import { AddDomainPage } from './AddDomainPage';
-import { InviteMembersPage } from './InviteMembersPage';
-import { OrganizationMembers } from './OrganizationMembers';
-import { OrganizationSettings } from './OrganizationSettings';
-import { ProfileSettingsPage } from './ProfileSettingsPage';
-import { RemoveDomainPage } from './RemoveDomainPage';
-import { VerifiedDomainPage } from './VerifiedDomainPage';
-import { VerifyDomainPage } from './VerifyDomainPage';
-
-export const OrganizationProfileRoutes = (props: PropsOfComponent) => {
- const { pages } = useOrganizationProfileContext();
- const isMembersPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS;
- const isSettingsPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SETTINGS;
-
- const customPageRoutesWithContents = pages.contents?.map((customPage, index) => {
- const shouldFirstCustomItemBeOnRoot = !isSettingsPageRoot && !isMembersPageRoot && index === 0;
- return (
-
-
-
- );
- });
-
- return (
-
-
- {customPageRoutesWithContents}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- has({ permission: 'org:sys_memberships:read' }) || has({ permission: 'org:sys_memberships:manage' })
- }
- redirectTo={isSettingsPageRoot ? '../' : './organization-settings'}
- >
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx
deleted file mode 100644
index 603cf73d0fa..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-
-import { AddBlockButton, BlockButton, Gate, useGate } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { Col, descriptors, Flex, Icon, localizationKeys } from '../../customizables';
-import { Header, IconButton, NavbarMenuButtonRow, OrganizationPreview, ProfileSection } from '../../elements';
-import { Times } from '../../icons';
-import { useRouter } from '../../router';
-import { DomainList } from './DomainList';
-
-export const OrganizationSettings = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const OrganizationProfileSection = () => {
- const { organization } = useOrganization();
- const { navigate } = useRouter();
-
- if (!organization) {
- return null;
- }
-
- const profile = ;
-
- return (
-
- {profile}>}
- >
- navigate('profile')}>{profile}
-
-
- );
-};
-
-const OrganizationDomainsSection = () => {
- const { organizationSettings } = useEnvironment();
- const { organization } = useOrganization();
-
- const { navigate } = useRouter();
-
- if (!organizationSettings || !organization) {
- return null;
- }
-
- if (!organizationSettings.domains.enabled) {
- return null;
- }
-
- return (
-
-
-
-
- navigate('domain')}
- />
-
-
- );
-};
-
-const OrganizationDangerSection = () => {
- const { organization } = useOrganization();
- const { navigate } = useRouter();
- const { isAuthorizedUser: canDeleteOrganization } = useGate({ permission: 'org:sys_profile:delete' });
-
- if (!organization) {
- return null;
- }
-
- const adminDeleteEnabled = organization.adminDeleteEnabled;
-
- return (
- ({ marginBottom: t.space.$4 })}
- >
-
- ({ marginRight: t.space.$2 })}
- />
- }
- variant='secondaryDanger'
- textVariant='buttonSmall'
- onClick={() => navigate('leave')}
- localizationKey={localizationKeys('organizationProfile.profilePage.dangerSection.leaveOrganization.title')}
- />
- {canDeleteOrganization && adminDeleteEnabled && (
- ({ marginRight: t.space.$2 })}
- />
- }
- variant='secondaryDanger'
- textVariant='buttonSmall'
- onClick={() => navigate('delete')}
- localizationKey={localizationKeys('organizationProfile.profilePage.dangerSection.deleteOrganization.title')}
- />
- )}
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ProfileSettingsPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ProfileSettingsPage.tsx
deleted file mode 100644
index d08e68572b9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/ProfileSettingsPage.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import React from 'react';
-
-import { isDefaultImage } from '../../../utils';
-import { useWizard, Wizard } from '../../common';
-import { localizationKeys } from '../../customizables';
-import { ContentPage, Form, FormButtons, SuccessPage, useCardState, withCardStateProvider } from '../../elements';
-import { handleError, useFormControl } from '../../utils';
-import { OrganizationProfileAvatarUploader } from './OrganizationProfileAvatarUploader';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const ProfileSettingsPage = withCardStateProvider(() => {
- const title = localizationKeys('organizationProfile.profilePage.title');
- const subtitle = localizationKeys('organizationProfile.profilePage.subtitle');
- const card = useCardState();
- const [avatarChanged, setAvatarChanged] = React.useState(false);
- const { organization } = useOrganization();
-
- const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
-
- const nameField = useFormControl('name', organization?.name || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__organizationName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__organizationName'),
- });
-
- const slugField = useFormControl('slug', organization?.slug || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__organizationSlug'),
- placeholder: localizationKeys('formFieldInputPlaceholder__organizationSlug'),
- });
-
- if (!organization) {
- return null;
- }
-
- const dataChanged = organization.name !== nameField.value || organization.slug !== slugField.value;
- const canSubmit = (dataChanged || avatarChanged) && slugField.feedbackType !== 'error';
-
- const onSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- return (dataChanged ? organization.update({ name: nameField.value, slug: slugField.value }) : Promise.resolve())
- .then(wizard.nextStep)
- .catch(err => {
- handleError(err, [nameField, slugField], card.setError);
- });
- };
-
- const uploadAvatar = (file: File) => {
- return organization
- .setLogo({ file })
- .then(() => {
- setAvatarChanged(true);
- card.setIdle();
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- const onAvatarRemove = () => {
- void organization
- .setLogo({ file: null })
- .then(() => {
- setAvatarChanged(true);
- card.setIdle();
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- const onChangeSlug = (event: React.ChangeEvent) => {
- updateSlugField(event.target.value);
- };
-
- const updateSlugField = (val: string) => {
- slugField.setValue(val);
- slugField.setError(undefined);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RemoveDomainPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RemoveDomainPage.tsx
deleted file mode 100644
index 23b950749d3..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RemoveDomainPage.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type { OrganizationDomainResource } from '@clerk/types';
-import React from 'react';
-
-import { RemoveResourcePage } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { descriptors, Flex, Spinner } from '../../customizables';
-import { useFetch } from '../../hooks';
-import { localizationKeys } from '../../localization';
-import { useRouter } from '../../router';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const RemoveDomainPage = () => {
- const { organizationSettings } = useEnvironment();
- const { organization } = useOrganization();
- const { params } = useRouter();
-
- const ref = React.useRef();
- const { data: domain, status: domainStatus } = useFetch(
- organization?.getDomain,
- {
- domainId: params.id,
- },
- {
- onSuccess(domain) {
- ref.current = { ...domain };
- },
- },
- );
-
- const { domains } = useOrganization({
- domains: {
- infinite: true,
- },
- });
-
- if (!organization || !organizationSettings) {
- return null;
- }
-
- if (domainStatus.isLoading || !domain) {
- return (
- ({
- height: '100%',
- minHeight: t.sizes.$120,
- })}
- >
-
-
- );
- }
-
- return (
- domain?.delete().then(() => domains?.revalidate?.())}
- breadcrumbTitle={localizationKeys('organizationProfile.profilePage.domainSection.title')}
- Breadcrumbs={OrganizationProfileBreadcrumbs}
- />
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RequestToJoinList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RequestToJoinList.tsx
deleted file mode 100644
index 27e9ae1915f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/RequestToJoinList.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type { OrganizationMembershipRequestResource } from '@clerk/types';
-
-import { Button, Flex, localizationKeys, Td } from '../../customizables';
-import { useCardState, UserPreview, withCardStateProvider } from '../../elements';
-import { handleError } from '../../utils';
-import { DataTable, RowContainer } from './MemberListTable';
-
-const membershipRequestsParams = {
- membershipRequests: {
- pageSize: 10,
- keepPreviousData: true,
- },
-};
-
-export const RequestToJoinList = () => {
- const card = useCardState();
- const { organization, membershipRequests } = useOrganization(membershipRequestsParams);
-
- if (!organization) {
- return null;
- }
-
- return (
- null)}
- itemCount={membershipRequests?.count || 0}
- pageCount={membershipRequests?.pageCount || 0}
- itemsPerPage={membershipRequestsParams.membershipRequests.pageSize}
- isLoading={membershipRequests?.isLoading}
- emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.requestsTab.table__emptyRow')}
- headers={[
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'),
- localizationKeys('organizationProfile.membersPage.requestsTab.tableHeader__requested'),
- localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__actions'),
- ]}
- rows={(membershipRequests?.data || []).map(request => (
-
- ))}
- />
- );
-};
-
-const RequestRow = withCardStateProvider(
- (props: { request: OrganizationMembershipRequestResource; onError: ReturnType['setError'] }) => {
- const { request, onError } = props;
- const card = useCardState();
- const { membership, membershipRequests } = useOrganization(membershipRequestsParams);
-
- const onAccept = () => {
- if (!membership || !membershipRequests) {
- return;
- }
- return card
- .runAsync(async () => {
- await request.accept();
- await membershipRequests.revalidate();
- }, 'accept')
- .catch(err => handleError(err, [], onError));
- };
- const onReject = () => {
- if (!membership || !membershipRequests) {
- return;
- }
- return card
- .runAsync(async () => {
- await request.reject();
- await membershipRequests.revalidate();
- }, 'reject')
- .catch(err => handleError(err, [], onError));
- };
-
- return (
-
-
-
- |
- {request.createdAt.toLocaleDateString()} |
-
-
-
- |
-
- );
- },
-);
-
-const AcceptRejectRequestButtons = (props: { onAccept: () => unknown; onReject: () => unknown }) => {
- const card = useCardState();
- return (
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifiedDomainPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifiedDomainPage.tsx
deleted file mode 100644
index efa61bfc6c7..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifiedDomainPage.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import type {
- OrganizationDomainResource,
- OrganizationEnrollmentMode,
- OrganizationSettingsResource,
-} from '@clerk/types';
-
-import { CalloutWithAction, useGate } from '../../common';
-import { useEnvironment } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
-import { Col, descriptors, Flex, localizationKeys, Spinner, Text } from '../../customizables';
-import {
- ContentPage,
- Form,
- FormButtons,
- Header,
- Tab,
- TabPanel,
- TabPanels,
- Tabs,
- TabsList,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useFetch, useNavigateToFlowStart } from '../../hooks';
-import { InformationCircle } from '../../icons';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { LinkButtonWithDescription } from '../UserProfile/LinkButtonWithDescription';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-const useCalloutLabel = (
- domain: OrganizationDomainResource | null,
- {
- infoLabel: infoLabelKey,
- }: {
- infoLabel: LocalizationKey;
- },
-) => {
- const totalInvitations = domain?.totalPendingInvitations || 0;
- const totalSuggestions = domain?.totalPendingSuggestions || 0;
- const totalPending = totalSuggestions + totalInvitations;
-
- if (totalPending === 0) {
- return [] as string[];
- }
-
- return [
- infoLabelKey,
- localizationKeys(`organizationProfile.verifiedDomainPage.enrollmentTab.calloutInvitationCountLabel`, {
- count: totalInvitations,
- }),
- localizationKeys(`organizationProfile.verifiedDomainPage.enrollmentTab.calloutSuggestionCountLabel`, {
- count: totalInvitations,
- }),
- ];
-};
-
-const buildEnrollmentOptions = (settings: OrganizationSettingsResource) => {
- const _options = [];
- if (settings.domains.enrollmentModes.includes('manual_invitation')) {
- _options.push({
- value: 'manual_invitation',
- label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__label'),
- description: localizationKeys(
- 'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__description',
- ),
- });
- }
-
- if (settings.domains.enrollmentModes.includes('automatic_invitation')) {
- _options.push({
- value: 'automatic_invitation',
- label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__label'),
- description: localizationKeys(
- 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__description',
- ),
- });
- }
-
- if (settings.domains.enrollmentModes.includes('automatic_suggestion')) {
- _options.push({
- value: 'automatic_suggestion',
- label: localizationKeys('organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__label'),
- description: localizationKeys(
- 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__description',
- ),
- });
- }
-
- return _options;
-};
-
-const useEnrollmentOptions = () => {
- const { organizationSettings } = useEnvironment();
- return buildEnrollmentOptions(organizationSettings);
-};
-
-export const VerifiedDomainPage = withCardStateProvider(() => {
- const card = useCardState();
- const { organizationSettings } = useEnvironment();
-
- const { membership, organization, domains } = useOrganization({
- domains: {
- infinite: true,
- },
- });
-
- const { isAuthorizedUser: canManageDomain } = useGate({ permission: 'org:sys_domains:manage' });
-
- const { navigateToFlowStart } = useNavigateToFlowStart();
- const { params, navigate, queryParams } = useRouter();
- const mode = (queryParams.mode || 'edit') as 'select' | 'edit';
-
- const breadcrumbTitle = localizationKeys('organizationProfile.profilePage.domainSection.title');
- const allowsEdit = mode === 'edit';
-
- const enrollmentOptions = useEnrollmentOptions();
- const enrollmentMode = useFormControl('enrollmentMode', '', {
- type: 'radio',
- radioOptions: enrollmentOptions,
- isRequired: true,
- });
-
- const deletePending = useFormControl('deleteExistingInvitationsSuggestions', '', {
- label: localizationKeys('formFieldLabel__organizationDomainDeletePending'),
- type: 'checkbox',
- });
-
- const { data: domain, status: domainStatus } = useFetch(
- organization?.getDomain,
- {
- domainId: params.id,
- },
- {
- onSuccess(d) {
- enrollmentMode.setValue(d.enrollmentMode);
- },
- },
- );
-
- const isFormDirty = deletePending.checked || domain?.enrollmentMode !== enrollmentMode.value;
- const subtitle = localizationKeys('organizationProfile.verifiedDomainPage.subtitle', {
- domain: domain?.name,
- });
-
- const calloutLabel = useCalloutLabel(domain, {
- infoLabel: localizationKeys(`organizationProfile.verifiedDomainPage.enrollmentTab.calloutInfoLabel`),
- });
-
- const dangerCalloutLabel = useCalloutLabel(domain, {
- infoLabel: localizationKeys(`organizationProfile.verifiedDomainPage.dangerTab.calloutInfoLabel`),
- });
-
- const updateEnrollmentMode = async () => {
- if (!domain || !organization || !membership || !domains) {
- return;
- }
-
- try {
- await domain.updateEnrollmentMode({
- enrollmentMode: enrollmentMode.value as OrganizationEnrollmentMode,
- deletePending: deletePending.checked,
- });
-
- await domains.revalidate();
-
- await navigate('../../');
- } catch (e) {
- handleError(e, [enrollmentMode], card.setError);
- }
- };
-
- if (!organization || !organizationSettings) {
- return null;
- }
-
- if (domainStatus.isLoading || !domain) {
- return (
- ({
- height: '100%',
- minHeight: t.sizes.$120,
- })}
- >
-
-
- );
- }
-
- if (!(domain.verification && domain.verification.status === 'verified')) {
- void navigateToFlowStart();
- }
-
- return (
-
-
-
-
- {canManageDomain && (
-
- )}
- {allowsEdit && canManageDomain && (
-
- )}
-
-
- {canManageDomain && (
-
- {calloutLabel.length > 0 && (
-
- {calloutLabel.map((label, index) => (
-
- ))}
-
- )}
-
-
-
-
-
-
-
-
- {allowsEdit && (
-
-
-
- )}
-
-
-
-
- )}
- {allowsEdit && canManageDomain && (
-
- {dangerCalloutLabel.length > 0 && (
-
- {dangerCalloutLabel.map((label, index) => (
-
- ))}
-
- )}
- ({
- padding: `${t.space.$none} ${t.space.$4}`,
- })}
- >
- navigate(`../../domain/${domain.id}/remove`)}
- />
-
-
- )}
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifyDomainPage.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifyDomainPage.tsx
deleted file mode 100644
index 6599427b004..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/VerifyDomainPage.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import { useOrganization } from '@clerk/shared/react';
-import React, { useRef } from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { Button, descriptors, Flex, localizationKeys, Spinner } from '../../customizables';
-import type { VerificationCodeCardProps } from '../../elements';
-import {
- ContentPage,
- Form,
- FormButtonContainer,
- FormButtons,
- useCardState,
- useFieldOTP,
- withCardStateProvider,
-} from '../../elements';
-import { useFetch } from '../../hooks';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar';
-
-export const VerifyDomainPage = withCardStateProvider(() => {
- const card = useCardState();
- const { organizationSettings } = useEnvironment();
- const { organization } = useOrganization();
- const { params, navigate } = useRouter();
-
- const { data: domain, status: domainStatus } = useFetch(organization?.getDomain, {
- domainId: params.id,
- });
- const title = localizationKeys('organizationProfile.verifyDomainPage.title');
- const subtitle = localizationKeys('organizationProfile.verifyDomainPage.subtitle', {
- domainName: domain?.name ?? '',
- });
-
- const breadcrumbTitle = localizationKeys('organizationProfile.profilePage.domainSection.title');
- const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
-
- const emailField = useFormControl('affiliationEmailAddress', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__organizationDomainEmailAddress'),
- placeholder: localizationKeys('formFieldInputPlaceholder__organizationDomainEmailAddress'),
- infoText: localizationKeys('formFieldLabel__organizationDomainEmailAddressDescription'),
- isRequired: true,
- });
-
- const affiliationEmailAddressRef = useRef();
-
- const subtitleVerificationCodeScreen = localizationKeys(
- 'organizationProfile.verifyDomainPage.subtitleVerificationCodeScreen',
- {
- emailAddress: affiliationEmailAddressRef.current,
- },
- );
-
- const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
- domain
- ?.attemptAffiliationVerification?.({ code })
- .then(async res => {
- await resolve();
- if (res.verification?.status === 'verified') {
- return navigate(`../../../domain/${res.id}?mode=select`);
- }
- return;
- })
- .catch(err => reject(err));
- };
-
- const otp = useFieldOTP({
- onCodeEntryFinished: (code, resolve, reject) => {
- action(code, resolve, reject);
- },
- onResendCodeClicked: () => {
- domain?.prepareAffiliationVerification({ affiliationEmailAddress: emailField.value }).catch(err => {
- handleError(err, [emailField], card.setError);
- });
- },
- });
-
- if (!organization || !organizationSettings) {
- return null;
- }
-
- const dataChanged = organization.name !== emailField.value;
- const canSubmit = dataChanged;
- const emailDomainSuffix = `@${domain?.name}`;
-
- const onSubmitPrepare = (e: React.FormEvent) => {
- e.preventDefault();
- if (!domain) {
- return;
- }
- affiliationEmailAddressRef.current = `${emailField.value}${emailDomainSuffix}`;
- return domain
- .prepareAffiliationVerification({
- affiliationEmailAddress: affiliationEmailAddressRef.current,
- })
- .then(wizard.nextStep)
- .catch(err => {
- handleError(err, [emailField], card.setError);
- });
- };
-
- if (domainStatus.isLoading || !domain) {
- return (
- ({
- height: '100%',
- minHeight: t.sizes.$120,
- })}
- >
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx
deleted file mode 100644
index b47c065b8db..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx
+++ /dev/null
@@ -1,291 +0,0 @@
-import type { MembershipRole, OrganizationInvitationResource } from '@clerk/types';
-import { describe } from '@jest/globals';
-import { waitFor } from '@testing-library/dom';
-import React from 'react';
-
-import { ClerkAPIResponseError } from '../../../../core/resources';
-import { render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { InviteMembersPage } from '../InviteMembersPage';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-describe('InviteMembersPage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { findByText } = render(, { wrapper });
- await waitFor(async () => expect(await findByText('Invite new members to this organization')).toBeInTheDocument());
- });
-
- describe('Submitting', () => {
- it('keeps the Send button disabled until a role is selected and one or more email has been entered', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- const { getByText, getByRole, userEvent, getByTestId } = render(, { wrapper });
- expect(getByRole('button', { name: 'Send invitations' })).toBeDisabled();
-
- await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,');
- expect(getByRole('button', { name: 'Send invitations' })).toBeDisabled();
-
- await userEvent.click(getByRole('button', { name: /select an option/i }));
- await userEvent.click(getByText(/^member$/i));
-
- expect(getByRole('button', { name: 'Send invitations' })).not.toBeDisabled();
- });
-
- it('sends invite to email entered and basic member role when clicking Send', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]);
- const { getByRole, userEvent, getByTestId, getByText } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,');
- await userEvent.click(getByRole('button', { name: 'Select an option' }));
- await userEvent.click(getByText('Member'));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
-
- await waitFor(() => {
- expect(fixtures.clerk.organization?.inviteMembers).toHaveBeenCalledWith({
- emailAddresses: ['test+1@clerk.com'],
- role: 'basic_member' as MembershipRole,
- });
- });
- });
-
- it('fetches custom role and sends invite to email entered and teacher role when clicking Send', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockResolvedValueOnce({
- data: [
- {
- pathRoot: '',
- reload: jest.fn(),
- id: '1',
- description: '',
- updatedAt: new Date(),
- createdAt: new Date(),
- permissions: [],
- name: 'Teacher',
- key: 'org:teacher',
- },
- ],
- total_count: 1,
- });
- fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]);
- const { getByRole, userEvent, getByTestId, getByText } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,');
- await userEvent.click(getByRole('button', { name: 'Select an option' }));
- await userEvent.click(getByText('Teacher'));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
-
- await waitFor(() => {
- expect(fixtures.clerk.organization?.inviteMembers).toHaveBeenCalledWith({
- emailAddresses: ['test+1@clerk.com'],
- role: 'org:teacher' as MembershipRole,
- });
- });
- });
-
- it('sends invites to multiple emails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]);
- const { getByRole, userEvent, getByTestId, getByText } = render(, { wrapper });
- await userEvent.type(
- getByTestId('tag-input'),
- 'test+1@clerk.com,test+2@clerk.com,test+3@clerk.com,test+4@clerk.com,',
- );
- await userEvent.click(getByRole('button', { name: 'Select an option' }));
- await userEvent.click(getByText('Member'));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
-
- await waitFor(() => {
- expect(fixtures.clerk.organization?.inviteMembers).toHaveBeenCalledWith({
- emailAddresses: ['test+1@clerk.com', 'test+2@clerk.com', 'test+3@clerk.com', 'test+4@clerk.com'],
- role: 'basic_member' as MembershipRole,
- });
- });
- });
-
- it('sends invite for admin role', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]);
- const { getByRole, userEvent, getByText, getByTestId } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,');
- await userEvent.click(getByRole('button', { name: 'Select an option' }));
- await userEvent.click(getByText('Admin'));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
- await waitFor(() => {
- expect(fixtures.clerk.organization?.inviteMembers).toHaveBeenCalledWith({
- emailAddresses: ['test+1@clerk.com'],
- role: 'admin' as MembershipRole,
- });
- });
- });
-
- it('shows error on UI with the invalid emails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'duplicate_record',
- long_message: 'There are already pending invitations for the following email addresses: test+1@clerk.com',
- message: 'duplicate invitation',
- meta: { email_addresses: ['test+5@clerk.com', 'test+6@clerk.com', 'test+7@clerk.com'] },
- },
- ],
- status: 400,
- }),
- );
- const { getByRole, userEvent, getByText, getByTestId } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,');
- await waitFor(() => expect(getByRole('button', { name: /select an option/i })).not.toBeDisabled());
- await userEvent.click(getByRole('button', { name: /select an option/i }));
- await userEvent.click(getByText(/^member$/i));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
- await waitFor(() =>
- expect(
- getByText(
- 'The invitations could not be sent. There are already pending invitations for the following email addresses: test+5@clerk.com, test+6@clerk.com, and test+7@clerk.com.',
- ),
- ).toBeInTheDocument(),
- );
- });
-
- it('removes duplicate emails from input after error', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.dev'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'duplicate_record',
- long_message:
- 'There are already pending invitations for the following email addresses: invalid@clerk.dev',
- message: 'duplicate invitation',
- meta: { email_addresses: ['invalid@clerk.dev'] },
- },
- ],
- status: 400,
- }),
- );
- const { getByRole, userEvent, getByTestId, getByText } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'invalid@clerk.dev');
- await waitFor(() => expect(getByRole('button', { name: /select an option/i })).not.toBeDisabled());
- await userEvent.click(getByRole('button', { name: /select an option/i }));
- await userEvent.click(getByText(/^member$/i));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
-
- expect(getByTestId('tag-input')).not.toHaveValue();
- });
-
- it('removes blocked/not allowed emails from input after error', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.dev'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.inviteMembers.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'not_allowed_access',
- long_message: 'blocked@clerk.dev is not allowed to access this application.',
- message: 'Access not allowed.',
- meta: { identifiers: ['blocked@clerk.dev'] },
- },
- ],
- status: 403,
- }),
- );
- const { getByRole, getByText, userEvent, getByTestId } = render(, { wrapper });
- await userEvent.type(getByTestId('tag-input'), 'blocked@clerk.dev');
- await waitFor(() => expect(getByRole('button', { name: /select an option/i })).not.toBeDisabled());
- await userEvent.click(getByRole('button', { name: /select an option/i }));
- await userEvent.click(getByText(/^member$/i));
- await userEvent.click(getByRole('button', { name: 'Send invitations' }));
-
- expect(getByTestId('tag-input')).not.toHaveValue();
- });
- });
-
- describe('Navigation', () => {
- it('navigates back when clicking the Cancel button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'admin' }],
- });
- });
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button', { name: 'Cancel' }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('..');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx
deleted file mode 100644
index e2bb6dba8d6..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { DeletedObjectResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import { waitFor } from '@testing-library/react';
-
-import { render } from '../../../../testUtils';
-import { CardStateProvider } from '../../../elements';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { LeaveOrganizationPage } from '../ActionConfirmationPage';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-describe('LeaveOrganizationPage', () => {
- it('unable to leave the organization when confirmation has not passed', 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?.leaveOrganization.mockResolvedValueOnce({} as DeletedObjectResource);
-
- const { getByRole, userEvent, getByLabelText } = render(
-
-
- ,
- { wrapper },
- );
-
- await userEvent.type(getByLabelText(/Confirmation/i), 'Org2');
-
- expect(getByRole('button', { name: 'Leave organization' })).toBeDisabled();
- });
-
- it('leaves the organization when user clicks "Leave organization"', 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?.leaveOrganization.mockResolvedValueOnce({} as DeletedObjectResource);
-
- const { getByRole, userEvent, getByLabelText } = render(
-
-
- ,
- { wrapper },
- );
-
- await waitFor(async () => {
- await userEvent.type(getByLabelText(/Confirmation/i), 'Org1');
- await userEvent.click(getByRole('button', { name: 'Leave organization' }));
- });
-
- expect(fixtures.clerk.user?.leaveOrganization).toHaveBeenCalledWith('Org1');
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx
deleted file mode 100644
index 556f744704b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx
+++ /dev/null
@@ -1,463 +0,0 @@
-import type { OrganizationInvitationResource, OrganizationMembershipResource } from '@clerk/types';
-import { describe } from '@jest/globals';
-import { waitFor, waitForElementToBeRemoved } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-
-import { render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { OrganizationMembers } from '../OrganizationMembers';
-import { createFakeMember, createFakeOrganizationInvitation, createFakeOrganizationMembershipRequest } from './utils';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-async function waitForLoadingCompleted(container: HTMLElement) {
- return waitForElementToBeRemoved(() => container.querySelector('span[aria-busy="true"]'));
-}
-
-describe('OrganizationMembers', () => {
- it('renders the Organization Members page', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1'] });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- const { container, getByText, getByRole } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(getByRole('heading', { name: /members/i })).toBeInTheDocument();
- expect(getByText('View and manage organization members')).toBeInTheDocument();
-
- // Tabs
- expect(getByRole('tab', { name: 'Members' })).toBeInTheDocument();
- expect(getByRole('tab', { name: 'Invitations' })).toBeInTheDocument();
- });
-
- it('shows requests if domains is turned on', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1'] });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { getByRole, container } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(getByRole('tab', { name: 'Requests' })).toBeInTheDocument();
- });
-
- it('shows an invite button inside invitations tab if the current user is an admin', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'Org1', role: 'admin' }] });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { getByRole, findByText } = render(, { wrapper });
-
- await userEvent.click(getByRole('tab', { name: 'Invitations' }));
- expect(await findByText('Invited')).toBeInTheDocument();
- expect(getByRole('button', { name: 'Invite' })).toBeInTheDocument();
- });
-
- it('does not show invitations and requests if user is not an admin', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', permissions: ['org:sys_memberships:read'] }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { container, queryByRole } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(queryByRole('tab', { name: 'Members' })).toBeInTheDocument();
- expect(queryByRole('tab', { name: 'Invitations' })).not.toBeInTheDocument();
- expect(queryByRole('tab', { name: 'Requests' })).not.toBeInTheDocument();
- });
-
- it('does not show members tab or navbar route if user is lacking permissions', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', permissions: [] }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { queryByRole } = render(, { wrapper });
-
- expect(queryByRole('tab', { name: 'Members' })).not.toBeInTheDocument();
- expect(queryByRole('tab', { name: 'Invitations' })).not.toBeInTheDocument();
- expect(queryByRole('tab', { name: 'Requests' })).not.toBeInTheDocument();
- });
-
- it('navigates to invite screen when user clicks on Invite button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'Org1', role: 'admin' }] });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { container, getByRole } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- await userEvent.click(getByRole('tab', { name: 'Invitations' }));
- await userEvent.click(getByRole('button', { name: 'Invite' }));
-
- expect(fixtures.router.navigate).toHaveBeenCalledWith('invite-members');
- });
-
- it('lists all the members of the Organization', async () => {
- const membersList: OrganizationMembershipResource[] = [
- createFakeMember({
- id: '1',
- orgId: '1',
- role: 'admin',
- identifier: 'test_user1',
- firstName: 'First1',
- lastName: 'Last1',
- createdAt: new Date('2022-01-01'),
- }),
- createFakeMember({
- id: '2',
- orgId: '1',
- role: 'basic_member',
- identifier: 'test_user2',
- firstName: 'First2',
- lastName: 'Last2',
- createdAt: new Date('2022-01-01'),
- }),
- createFakeMember({
- id: '3',
- orgId: '1',
- role: 'admin',
- identifier: 'test_user3',
- firstName: 'First3',
- lastName: 'Last3',
- createdAt: new Date('2022-01-01'),
- }),
- ];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: membersList,
- total_count: 2,
- }),
- );
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 2,
- }),
- );
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { container, queryByText, queryAllByRole } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- expect(fixtures.clerk.organization?.getInvitations).not.toHaveBeenCalled();
- expect(fixtures.clerk.organization?.getMembershipRequests).not.toHaveBeenCalled();
-
- expect(queryByText('test_user1')).toBeInTheDocument();
- expect(queryByText('First1 Last1')).toBeInTheDocument();
-
- const buttons = queryAllByRole('button', { name: 'Admin' });
- buttons.forEach(button => expect(button).not.toBeDisabled());
-
- expect(queryByText('test_user2')).toBeInTheDocument();
- expect(queryByText('First2 Last2')).toBeInTheDocument();
- expect(queryByText('Member')).toBeInTheDocument();
- });
-
- it('display pagination counts for 2 pages', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 14,
- }),
- );
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { container, getByText } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- expect(fixtures.clerk.organization?.getInvitations).not.toHaveBeenCalled();
- expect(fixtures.clerk.organization?.getMembershipRequests).not.toHaveBeenCalled();
-
- await userEvent.click(getByText(/next/i));
-
- await waitFor(async () => {
- const pagination = getByText(/displaying/i).closest('p');
- expect(pagination?.textContent).toEqual('Displaying 11 – 14 of 14');
- });
- });
-
- it('display pagination counts for 1 page', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 5,
- }),
- );
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- const { container, getByText } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- const pagination = getByText(/displaying/i).closest('p');
- expect(pagination?.textContent).toEqual('Displaying 1 – 5 of 5');
- expect(getByText(/next/i)).toBeDisabled();
- });
-
- // TODO: Bring this test back once we can determine the last admin by permissions.
- it.skip('Last admin cannot change to member', async () => {
- const membersList: OrganizationMembershipResource[] = [
- createFakeMember({
- id: '1',
- orgId: '1',
- role: 'admin',
- identifier: 'test_user1',
- firstName: 'First1',
- lastName: 'Last1',
- createdAt: new Date('2022-01-01'),
- }),
- createFakeMember({
- id: '2',
- orgId: '1',
- role: 'basic_member',
- identifier: 'test_user2',
- firstName: 'First2',
- lastName: 'Last2',
- createdAt: new Date('2022-01-01'),
- }),
- ];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({ data: membersList, total_count: 0 }),
- );
-
- fixtures.clerk.organization?.getMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 0,
- }),
- );
-
- const { queryByRole } = render(, { wrapper });
-
- await waitFor(() => {
- expect(queryByRole('button', { name: 'Admin' })).toBeDisabled();
- });
- });
-
- it('displays counter in requests tab', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.getMembershipRequests.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 2,
- }),
- );
-
- const { findByText } = render(, { wrapper });
- expect(await findByText('2')).toBeInTheDocument();
- });
-
- it.todo('removes member from organization when clicking the respective button on a user row');
- it.todo('changes role on a member from organization when clicking the respective button on a user row');
- it('changes tab and renders the pending invites list', async () => {
- const invitationList: OrganizationInvitationResource[] = [
- createFakeOrganizationInvitation({
- id: '1',
- role: 'admin',
- emailAddress: 'admin1@clerk.com',
- organizationId: '1',
- createdAt: new Date('2022-01-01'),
- }),
- createFakeOrganizationInvitation({
- id: '2',
- role: 'basic_member',
- emailAddress: 'member2@clerk.com',
- organizationId: '1',
- createdAt: new Date('2022-01-01'),
- }),
- ];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- fixtures.clerk.organization?.getInvitations.mockReturnValue(
- Promise.resolve({
- data: invitationList,
- total_count: 2,
- }),
- );
-
- const { container, getByRole, getByText, findByText } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
- await userEvent.click(getByRole('tab', { name: 'Invitations' }));
-
- expect(await findByText('admin1@clerk.com')).toBeInTheDocument();
- expect(getByText('Admin')).toBeInTheDocument();
- expect(getByText('member2@clerk.com')).toBeInTheDocument();
- expect(getByText('Member')).toBeInTheDocument();
- expect(fixtures.clerk.organization?.getInvitations).toHaveBeenCalled();
- });
-
- it('changes tab and renders pending requests', async () => {
- const requests = {
- data: [
- createFakeOrganizationMembershipRequest({
- id: '1',
- publicUserData: {
- userId: '1',
- identifier: 'admin1@clerk.com',
- },
- organizationId: '1',
- createdAt: new Date('2022-01-01'),
- }),
- createFakeOrganizationMembershipRequest({
- id: '2',
- publicUserData: {
- userId: '1',
- identifier: 'member2@clerk.com',
- },
- organizationId: '1',
- createdAt: new Date('2022-01-01'),
- }),
- ],
- total_count: 4,
- };
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
-
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 0,
- }),
- );
- fixtures.clerk.organization?.getMembershipRequests.mockReturnValue(Promise.resolve(requests));
- const { queryByText, getByRole } = render(, { wrapper });
-
- await waitFor(async () => {
- await userEvent.click(getByRole('tab', { name: 'Requests' }));
- });
-
- expect(fixtures.clerk.organization?.getMembershipRequests).toHaveBeenCalledWith({
- initialPage: 1,
- pageSize: 10,
- status: 'pending',
- });
-
- expect(queryByText('admin1@clerk.com')).toBeInTheDocument();
- expect(queryByText('member2@clerk.com')).toBeInTheDocument();
- });
-
- it('shows the "You" badge when the member id from the list matches the current session user id', async () => {
- const membersList: OrganizationMembershipResource[] = [
- createFakeMember({ id: '1', orgId: '1', role: 'admin', identifier: 'test_user1' }),
- createFakeMember({ id: '2', orgId: '1', role: 'basic_member', identifier: 'test_user2' }),
- ];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- id: '1',
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1' }],
- });
- });
-
- fixtures.clerk.organization?.getRoles.mockRejectedValue(null);
- fixtures.clerk.organization?.getMemberships.mockReturnValue(
- Promise.resolve({
- data: membersList,
- total_count: 2,
- }),
- );
-
- const { container, getByText } = render(, { wrapper });
-
- await waitForLoadingCompleted(container);
-
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- expect(getByText('You')).toBeInTheDocument();
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx
deleted file mode 100644
index 24fa61618e1..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { CustomPage } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { OrganizationProfile } from '../OrganizationProfile';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-describe('OrganizationProfile', () => {
- beforeEach(() => jest.useFakeTimers());
- afterEach(() => jest.useRealTimers());
- it('includes buttons for the bigger sections', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1'] });
- });
-
- const { getByText } = render(, { wrapper });
- expect(getByText('Org1')).toBeDefined();
- expect(getByText('Members')).toBeDefined();
- expect(getByText('Settings')).toBeDefined();
- });
-
- it('includes custom nav items', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.dev'], organization_memberships: ['Org1'] });
- });
-
- const customPages: CustomPage[] = [
- {
- label: 'Custom1',
- url: 'custom1',
- mount: () => undefined,
- unmount: () => undefined,
- mountIcon: () => undefined,
- unmountIcon: () => undefined,
- },
- {
- label: 'ExternalLink',
- url: '/link',
- mountIcon: () => undefined,
- unmountIcon: () => undefined,
- },
- ];
-
- props.setProps({ customPages });
-
- const { getByText } = render(, { wrapper });
- expect(getByText('Org1')).toBeDefined();
- expect(getByText('Members')).toBeDefined();
- expect(getByText('Settings')).toBeDefined();
- expect(getByText('Custom1')).toBeDefined();
- expect(getByText('ExternalLink')).toBeDefined();
- });
-
- it('removes member nav item if user is lacking permissions', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [
- {
- name: 'Org1',
- permissions: [],
- },
- ],
- });
- });
-
- const { queryByText } = render(, { wrapper });
- expect(queryByText('Org1')).toBeInTheDocument();
- expect(queryByText('Members')).not.toBeInTheDocument();
- expect(queryByText('Settings')).toBeInTheDocument();
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx
deleted file mode 100644
index 9034d215c80..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-import type { ClerkPaginatedResponse, OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import userEvent from '@testing-library/user-event';
-
-import { act, render, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { OrganizationSettings } from '../OrganizationSettings';
-import { createFakeDomain, createFakeMember } from './utils';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-describe('OrganizationSettings', () => {
- it.skip('enables organization profile button and disables leave when user is the only admin', async () => {
- const adminsList: OrganizationMembershipResource[] = [createFakeMember({ id: '1', orgId: '1', role: 'admin' })];
- const domainList: OrganizationDomainResource[] = [
- createFakeDomain({ id: '1', organizationId: '1', name: 'clerk.com' }),
- ];
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'Org1' }] });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve({ data: adminsList, total_count: 1 }));
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: domainList,
- total_count: 1,
- }),
- );
-
- const { getByText } = render(, { wrapper });
-
- await waitFor(() => {
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- });
-
- expect(getByText('Settings')).toBeDefined();
- expect(getByText('Org1', { exact: false }).closest('button')).not.toBeNull();
- expect(getByText(/leave organization/i, { exact: false }).closest('button')).toHaveAttribute('disabled');
- });
-
- it('enables organization profile button and enables leave when user is admin and there is more', async () => {
- const domainList: OrganizationDomainResource[] = [
- createFakeDomain({ id: '1', organizationId: '1', name: 'clerk.com' }),
- ];
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'Org1' }] });
- });
-
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: domainList,
- total_count: 1,
- }),
- );
- const { getByText } = render(, { wrapper });
- await waitFor(() => {
- expect(getByText('Settings')).toBeDefined();
- expect(getByText('Org1', { exact: false }).closest('button')).not.toBeNull();
- expect(getByText(/leave organization/i, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
- });
- });
-
- it.skip('disables organization profile button and enables leave when user is not admin', async () => {
- const adminsList: ClerkPaginatedResponse = {
- data: [createFakeMember({ id: '1', orgId: '1', role: 'admin' })],
- total_count: 1,
- };
-
- 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(adminsList));
- const { getByText } = render(, { wrapper });
- await waitFor(() => {
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- expect(getByText('Settings')).toBeDefined();
- expect(getByText('Org1', { exact: false }).closest('button')).toBeNull();
- expect(getByText(/leave organization/i, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
- });
- });
-
- it('hides domains when `read` permission is missing', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.dev'],
- organization_memberships: [{ name: 'Org1', permissions: ['org:sys_memberships:read'] }],
- });
- });
- const { queryByText } = await act(() => render(, { wrapper }));
- await new Promise(r => setTimeout(r, 100));
- expect(queryByText('Verified domains')).not.toBeInTheDocument();
- expect(fixtures.clerk.organization?.getDomains).not.toBeCalled();
- });
-
- it('shows domains when `read` permission exists but hides the Add domain button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.dev'],
- organization_memberships: [{ name: 'Org1', permissions: ['org:sys_domains:read'] }],
- });
- });
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 0,
- }),
- );
- const { queryByText } = await act(() => render(, { wrapper }));
-
- await new Promise(r => setTimeout(r, 100));
- expect(queryByText('Verified domains')).toBeInTheDocument();
- expect(queryByText('Add domain')).not.toBeInTheDocument();
- expect(fixtures.clerk.organization?.getDomains).toBeCalled();
- });
-
- it('shows domains and shows the Add domain button when `org:sys_domains:manage` exists', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.dev'],
- organization_memberships: [{ name: 'Org1', permissions: ['org:sys_domains:read', 'org:sys_domains:manage'] }],
- });
- });
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 0,
- }),
- );
- const { queryByText } = await act(() => render(, { wrapper }));
-
- await new Promise(r => setTimeout(r, 100));
- expect(queryByText('Verified domains')).toBeInTheDocument();
- expect(queryByText('Add domain')).toBeInTheDocument();
- expect(fixtures.clerk.organization?.getDomains).toBeCalled();
- });
-
- describe('Danger section', () => {
- it('always displays danger section and the leave organization button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- });
- });
-
- const { getByText, queryByRole } = await act(() => render(, { wrapper }));
- await waitFor(() => {
- expect(getByText('Danger')).toBeDefined();
- expect(getByText(/leave organization/i).closest('button')).toBeInTheDocument();
- expect(queryByRole('button', { name: /delete organization/i })).not.toBeInTheDocument();
- });
- });
-
- it('enabled leave organization button with delete organization button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', admin_delete_enabled: true }],
- });
- });
-
- const { getByText } = render(, { wrapper });
- await waitFor(() => {
- expect(getByText('Danger')).toBeDefined();
- expect(getByText(/leave organization/i).closest('button')).not.toHaveAttribute('disabled');
- expect(getByText(/delete organization/i).closest('button')).toBeInTheDocument();
- });
- });
-
- it.skip('disabled leave organization button with delete organization button', async () => {
- const adminsList: ClerkPaginatedResponse = {
- data: [
- createFakeMember({
- id: '1',
- orgId: '1',
- role: 'admin',
- }),
- createFakeMember({
- id: '2',
- orgId: '1',
- role: 'admin',
- }),
- ],
- total_count: 2,
- };
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', admin_delete_enabled: true }],
- });
- });
-
- fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve(adminsList));
- const { getByText, getByRole } = render(, { wrapper });
- await waitFor(() => {
- expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled();
- expect(getByText('Danger')).toBeDefined();
- expect(getByText(/leave organization/i).closest('button')).toHaveAttribute('disabled');
- expect(getByText(/delete organization/i).closest('button')).toBeInTheDocument();
- expect(getByRole('button', { name: /delete organization/i })).toBeInTheDocument();
- });
- });
- });
-
- describe('Navigation', () => {
- it('navigates to Organization Profile edit page when clicking on organization name and user is admin', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1' }],
- });
- });
-
- fixtures.clerk.organization?.getDomains.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 0,
- }),
- );
- const { getByText } = render(, { wrapper });
- await waitFor(async () => {
- await userEvent.click(getByText('Org1', { exact: false }));
- });
- expect(fixtures.router.navigate).toHaveBeenCalledWith('profile');
- });
-
- // TODO(@panteliselef): Update this test to allow user to leave an org, only if there will be at least one person left with the minimum set of permissions
- it('navigates to Leave Organization page when clicking on the respective button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', permissions: [] }],
- });
- });
-
- const { findByText } = render(, { wrapper });
- await waitFor(async () => {
- await userEvent.click(await findByText(/leave organization/i, { exact: false }));
- });
- expect(fixtures.router.navigate).toHaveBeenCalledWith('leave');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx
deleted file mode 100644
index fde0e31dca2..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import type { OrganizationResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { ProfileSettingsPage } from '../ProfileSettingsPage';
-
-const { createFixtures } = bindCreateFixtures('OrganizationProfile');
-
-describe('ProfileSettingsPage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'Org1', role: 'admin' }] });
- });
-
- const { getByDisplayValue } = render(, { wrapper });
- expect(getByDisplayValue('Org1')).toBeDefined();
- });
-
- it('enables the continue button if the name changes value', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', slug: '', role: 'admin' }],
- });
- });
-
- const { getByLabelText, userEvent, getByRole } = render(, { wrapper });
- expect(getByRole('button', { name: 'Continue' })).toBeDisabled();
- await userEvent.type(getByLabelText('Organization name'), '2');
- expect(getByRole('button', { name: 'Continue' })).not.toBeDisabled();
- });
-
- it('updates organization name on clicking continue', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', slug: '', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.update.mockResolvedValue({} as OrganizationResource);
- const { getByDisplayValue, getByLabelText, userEvent, getByRole } = render(, { wrapper });
- await userEvent.type(getByLabelText('Organization name'), '234');
- expect(getByDisplayValue('Org1234')).toBeDefined();
- await userEvent.click(getByRole('button', { name: 'Continue' }));
- expect(fixtures.clerk.organization?.update).toHaveBeenCalledWith({ name: 'Org1234', slug: '' });
- });
-
- it('updates organization slug on clicking continue', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', slug: '', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.update.mockResolvedValue({} as OrganizationResource);
- const { getByDisplayValue, getByLabelText, userEvent, getByRole } = render(, { wrapper });
- await userEvent.type(getByLabelText('Slug URL'), 'my-org');
- expect(getByDisplayValue('my-org')).toBeDefined();
- await userEvent.click(getByRole('button', { name: 'Continue' }));
- expect(fixtures.clerk.organization?.update).toHaveBeenCalledWith({ name: 'Org1', slug: 'my-org' });
- });
-
- it('opens file drop area to update organization logo on clicking "Upload image"', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', slug: '', role: 'admin' }],
- });
- });
-
- const { userEvent, getByRole } = render(, { wrapper });
- await userEvent.click(getByRole('button', { name: 'Upload image' }));
- expect(getByRole('button', { name: 'Select file' })).toBeDefined();
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/utils.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/utils.ts
deleted file mode 100644
index ef323f7b212..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/utils.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import type {
- MembershipRole,
- OrganizationDomainResource,
- OrganizationDomainVerification,
- OrganizationEnrollmentMode,
- OrganizationInvitationResource,
- OrganizationInvitationStatus,
- OrganizationMembershipRequestResource,
- OrganizationMembershipResource,
- OrganizationResource,
- PublicUserData,
-} from '@clerk/types';
-import { jest } from '@jest/globals';
-
-type FakeMemberParams = {
- id: string;
- orgId: string;
- role?: MembershipRole;
- identifier?: string;
- firstName?: string;
- lastName?: string;
- imageUrl?: string;
- createdAt?: Date;
-};
-
-export const createFakeMember = (params: FakeMemberParams): OrganizationMembershipResource => {
- return {
- destroy: jest.fn() as any,
- update: jest.fn() as any,
- organization: { id: params.orgId } as any as OrganizationResource,
- id: params.id,
- role: params?.role || 'admin',
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- publicMetadata: {},
- publicUserData: {
- userId: params.id,
- identifier: params?.identifier || 'test_user',
- firstName: params?.firstName || 'test_firstName',
- lastName: params?.lastName || 'test_lastName',
- imageUrl: params?.imageUrl || '',
- },
- } as any;
-};
-
-type FakeDomainParams = {
- id: string;
- name: string;
- organizationId: string;
- enrollmentMode?: OrganizationEnrollmentMode;
- verification?: OrganizationDomainVerification | null;
- createdAt?: Date;
- affiliationEmailAddress?: string | null;
- totalPendingInvitations?: number;
- totalPendingSuggestions?: number;
-};
-
-export const createFakeDomain = (params: FakeDomainParams): OrganizationDomainResource => {
- return {
- pathRoot: '',
- id: params.id,
- name: params.name,
- verification: params.verification || null,
- organizationId: params.organizationId,
- enrollmentMode: params.enrollmentMode || 'manual_invitation',
- totalPendingInvitations: params.totalPendingInvitations || 0,
- totalPendingSuggestions: params.totalPendingSuggestions || 0,
- createdAt: params.createdAt || new Date(),
- updatedAt: new Date(),
- affiliationEmailAddress: params.affiliationEmailAddress || null,
- attemptAffiliationVerification: jest.fn() as any,
- delete: jest.fn() as any,
- prepareAffiliationVerification: jest.fn() as any,
- updateEnrollmentMode: jest.fn() as any,
- reload: jest.fn() as any,
- };
-};
-
-type FakeInvitationParams = {
- id: string;
- role?: MembershipRole;
- status?: OrganizationInvitationStatus;
- emailAddress: string;
- organizationId: string;
- createdAt?: Date;
-};
-
-export const createFakeOrganizationInvitation = (params: FakeInvitationParams): OrganizationInvitationResource => {
- return {
- pathRoot: '',
- id: params.id,
- emailAddress: params.emailAddress,
- organizationId: params.organizationId,
- publicMetadata: {} as any,
- role: params.role || 'basic_member',
- status: params.status || 'pending',
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- revoke: jest.fn as any,
- reload: jest.fn as any,
- };
-};
-
-type FakeMemberRequestParams = {
- id: string;
- publicUserData: Pick;
- status?: OrganizationInvitationStatus;
- organizationId: string;
- createdAt?: Date;
-};
-
-export const createFakeOrganizationMembershipRequest = (
- params: FakeMemberRequestParams,
-): OrganizationMembershipRequestResource => {
- return {
- pathRoot: '',
- id: params.id,
- organizationId: params.organizationId,
- publicUserData: {
- firstName: null,
- lastName: null,
- imageUrl: '',
- hasImage: false,
- userId: params.publicUserData.userId || '',
- identifier: params.publicUserData.identifier,
- },
- status: params.status || 'pending',
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- accept: jest.fn as any,
- reject: jest.fn as any,
- reload: jest.fn as any,
- };
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/index.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/index.ts
deleted file mode 100644
index 98a8862a2e4..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './OrganizationProfile';
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcher.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcher.tsx
deleted file mode 100644
index 34843fd80d4..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcher.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useId } from 'react';
-
-import { withOrganizationsEnabledGuard } from '../../common';
-import { withCoreUserGuard } from '../../contexts';
-import { Flow } from '../../customizables';
-import { Popover, withCardStateProvider, withFloatingTree } from '../../elements';
-import { usePopover } from '../../hooks';
-import { OrganizationSwitcherPopover } from './OrganizationSwitcherPopover';
-import { OrganizationSwitcherTrigger } from './OrganizationSwitcherTrigger';
-
-const _OrganizationSwitcher = withFloatingTree(() => {
- const { floating, reference, styles, toggle, isOpen, nodeId, context } = usePopover({
- placement: 'bottom-start',
- offset: 8,
- });
-
- const switcherButtonMenuId = useId();
-
- return (
-
-
-
-
-
-
- );
-});
-
-export const OrganizationSwitcher = withOrganizationsEnabledGuard(
- withCoreUserGuard(withCardStateProvider(_OrganizationSwitcher)),
- 'OrganizationSwitcher',
- { mode: 'hide' },
-);
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
deleted file mode 100644
index 5cea7503deb..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-import { useClerk, useOrganization, useOrganizationList, useUser } from '@clerk/shared/react';
-import type { OrganizationResource } from '@clerk/types';
-import React from 'react';
-
-import { runIfFunctionOrReturn } from '../../../utils';
-import { NotificationCountBadge, withGate } from '../../common';
-import { useEnvironment, useOrganizationSwitcherContext } from '../../contexts';
-import { descriptors, Flex, localizationKeys } from '../../customizables';
-import {
- Actions,
- ExtraSmallAction,
- OrganizationPreview,
- PersonalWorkspacePreview,
- PopoverCard,
- SmallAction,
- useCardState,
-} from '../../elements';
-import { RootBox } from '../../elements/RootBox';
-import { Billing, CogFilled } from '../../icons';
-import { useRouter } from '../../router';
-import type { PropsOfComponent, ThemableCssProp } from '../../styledSystem';
-import { OrganizationActionList } from './OtherOrganizationActions';
-
-type OrganizationSwitcherPopoverProps = { close: () => void } & PropsOfComponent;
-
-export const OrganizationSwitcherPopover = React.forwardRef(
- (props, ref) => {
- const { close, ...rest } = props;
- const card = useCardState();
- const { openOrganizationProfile, openCreateOrganization } = useClerk();
- const { organization: currentOrg } = useOrganization();
- const { isLoaded, setActive } = useOrganizationList();
- const router = useRouter();
- const {
- hidePersonal,
- //@ts-expect-error
- __unstable_manageBillingUrl,
- //@ts-expect-error
- __unstable_manageBillingLabel,
- //@ts-expect-error
- __unstable_manageBillingMembersLimit,
- createOrganizationMode,
- organizationProfileMode,
- afterLeaveOrganizationUrl,
- afterCreateOrganizationUrl,
- navigateCreateOrganization,
- navigateOrganizationProfile,
- navigateAfterSelectPersonal,
- navigateAfterSelectOrganization,
- organizationProfileProps,
- } = useOrganizationSwitcherContext();
-
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
-
- if (!isLoaded) {
- return null;
- }
-
- const handleOrganizationClicked = (organization: OrganizationResource) => {
- return card
- .runAsync(() =>
- setActive({
- organization,
- beforeEmit: () => navigateAfterSelectOrganization(organization),
- }),
- )
- .then(close);
- };
-
- const handlePersonalWorkspaceClicked = () => {
- return card
- .runAsync(() => setActive({ organization: null, beforeEmit: () => navigateAfterSelectPersonal(user) }))
- .then(close);
- };
-
- const handleCreateOrganizationClicked = () => {
- close();
- if (createOrganizationMode === 'navigation') {
- return navigateCreateOrganization();
- }
- return openCreateOrganization({ afterCreateOrganizationUrl });
- };
-
- const handleManageOrganizationClicked = () => {
- close();
- if (organizationProfileMode === 'navigation') {
- return navigateOrganizationProfile();
- }
- return openOrganizationProfile({
- ...organizationProfileProps,
- afterLeaveOrganizationUrl,
- //@ts-expect-error
- __unstable_manageBillingUrl,
- __unstable_manageBillingLabel,
- __unstable_manageBillingMembersLimit,
- });
- };
-
- const manageOrganizationSmallIconButton = (
- }
- />
- );
-
- const manageOrganizationButton = (
- }
- />
- );
-
- const billingOrganizationButton = (
- router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
- />
- );
-
- const selectedOrganizationPreview = (currentOrg: OrganizationResource) =>
- __unstable_manageBillingUrl ? (
- <>
- ({
- padding: `${t.space.$4} ${t.space.$5}`,
- })}
- />
- ({ borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}` })}
- >
- ({ marginLeft: t.space.$12, padding: `0 ${t.space.$5} ${t.space.$4}`, gap: t.space.$2 })}
- >
- {manageOrganizationButton}
- {billingOrganizationButton}
-
-
- >
- ) : (
- ({
- width: '100%',
- paddingRight: t.space.$5,
- borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}`,
- })}
- >
- ({
- padding: `${t.space.$4} ${t.space.$5}`,
- })}
- />
- {manageOrganizationSmallIconButton}
-
- );
-
- return (
-
-
-
- {currentOrg
- ? selectedOrganizationPreview(currentOrg)
- : !hidePersonal && (
- ({
- padding: `${t.space.$4} ${t.space.$5}`,
- borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}`,
- })}
- title={localizationKeys('organizationSwitcher.personalWorkspace')}
- />
- )}
-
-
-
-
-
- );
- },
-);
-
-const NotificationCountBadgeManageButton = withGate(
- ({ sx }: { sx?: ThemableCssProp }) => {
- const { organizationSettings } = useEnvironment();
-
- const isDomainsEnabled = organizationSettings?.domains?.enabled;
-
- const { membershipRequests } = useOrganization({
- membershipRequests: isDomainsEnabled || undefined,
- });
-
- return (
-
- );
- },
- {
- // if the user is not able to accept a request we should not notify them
- permission: 'org:sys_memberships:manage',
- },
-);
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
deleted file mode 100644
index 8b8ff0502cd..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { useOrganization, useOrganizationList, useUser } from '@clerk/shared/react';
-import { forwardRef } from 'react';
-
-import { NotificationCountBadge, useGate } from '../../common';
-import { useEnvironment, useOrganizationSwitcherContext } from '../../contexts';
-import { Button, descriptors, Icon, localizationKeys } from '../../customizables';
-import { OrganizationPreview, PersonalWorkspacePreview, withAvatarShimmer } from '../../elements';
-import { ChevronDown } from '../../icons';
-import type { PropsOfComponent } from '../../styledSystem';
-import { organizationListParams } from './utils';
-
-type OrganizationSwitcherTriggerProps = PropsOfComponent & {
- isOpen: boolean;
-};
-
-export const OrganizationSwitcherTrigger = withAvatarShimmer(
- forwardRef((props, ref) => {
- const { sx, ...rest } = props;
-
- const { user } = useUser();
- const { organization } = useOrganization();
- const { hidePersonal } = useOrganizationSwitcherContext();
-
- if (!user) {
- return null;
- }
-
- const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
-
- return (
-
- );
- }),
-);
-
-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 (
-
- );
-};
-
-const AcceptRejectInvitationButtons = (props: UserOrganizationInvitationResource) => {
- const card = useCardState();
- const { userInvitations } = useOrganizationList({
- userInvitations: organizationListParams.userInvitations,
- });
-
- const handleAccept = () => {
- return card
- .runAsync(props.accept)
- .then(updatedItem => userInvitations?.setData?.(pages => populateCacheRemoveItem(updatedItem, pages)))
- .catch(err => handleError(err, [], card.setError));
- };
-
- return (
-
- );
-};
-
-const InvitationPreview = withCardStateProvider(
- (
- props: PropsWithChildren<{
- publicOrganizationData:
- | UserOrganizationInvitationResource['publicOrganizationData']
- | OrganizationSuggestionResource['publicOrganizationData'];
- }>,
- ) => {
- const { children, publicOrganizationData } = props;
- return (
- ({
- justifyContent: 'space-between',
- padding: `${t.space.$4} ${t.space.$5}`,
- })}
- >
- ({
- color: t.colors.$blackAlpha600,
- ':hover': {
- color: t.colors.$blackAlpha600,
- },
- })}
- />
-
- {children}
-
- );
- },
-);
-
-const SwitcherInvitationActions = (props: PropsOfComponent & { showBorder: boolean }) => {
- const { showBorder, ...restProps } = props;
- return (
- ({
- borderBottom: showBorder ? `${t.borders.$normal} ${t.colors.$blackAlpha200}` : 'none',
- })}
- role='menu'
- {...restProps}
- />
- );
-};
-
-export const UserInvitationSuggestionList = () => {
- const { ref, userSuggestions, userInvitations } = useFetchInvitations();
- const isLoading = userInvitations.isLoading || userSuggestions.isLoading;
- const hasNextPage = userInvitations.hasNextPage || userSuggestions.hasNextPage;
- const hasAnyData = !!(userInvitations.count || userSuggestions.count);
- return (
-
- ({
- maxHeight: `calc(4 * ${t.sizes.$12})`,
- overflowY: 'auto',
- ...common.unstyledScrollbar(t),
- })}
- >
- {(userInvitations.count || 0) > 0 &&
- userInvitations.data?.map(inv => {
- return (
-
-
-
- );
- })}
-
- {(userSuggestions.count || 0) > 0 &&
- !userInvitations.hasNextPage &&
- userSuggestions.data?.map(suggestion => {
- return (
-
-
-
- );
- })}
-
- {(hasNextPage || isLoading) && }
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx
deleted file mode 100644
index ca136926f47..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { useOrganization, useOrganizationList, useUser } from '@clerk/shared/react';
-import type { OrganizationResource } from '@clerk/types';
-import React from 'react';
-
-import { InfiniteListSpinner } from '../../common';
-import { useOrganizationSwitcherContext } from '../../contexts';
-import { Box, descriptors, localizationKeys } from '../../customizables';
-import { OrganizationPreview, PersonalWorkspacePreview, PreviewButton } from '../../elements';
-import { useInView } from '../../hooks';
-import { SwitchArrowRight } from '../../icons';
-import { common } from '../../styledSystem';
-import { organizationListParams } from './utils';
-
-export type UserMembershipListProps = {
- onPersonalWorkspaceClick: React.MouseEventHandler;
- onOrganizationClick: (org: OrganizationResource) => unknown;
-};
-
-const useFetchMemberships = () => {
- const { userMemberships } = useOrganizationList({
- userMemberships: organizationListParams.userMemberships,
- });
-
- const { ref } = useInView({
- threshold: 0,
- onChange: inView => {
- if (!inView) {
- return;
- }
- if (userMemberships.hasNextPage) {
- userMemberships.fetchNext?.();
- }
- },
- });
-
- return {
- userMemberships,
- ref,
- };
-};
-export const UserMembershipList = (props: UserMembershipListProps) => {
- const { onPersonalWorkspaceClick, onOrganizationClick } = props;
-
- const { hidePersonal } = useOrganizationSwitcherContext();
- const { organization: currentOrg } = useOrganization();
- const { ref, userMemberships } = useFetchMemberships();
- const { user } = useUser();
-
- const otherOrgs = ((userMemberships.count || 0) > 0 ? userMemberships.data || [] : [])
- .map(e => e.organization)
- .filter(o => o.id !== currentOrg?.id);
-
- if (!user) {
- return null;
- }
-
- const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
-
- const { isLoading, hasNextPage } = userMemberships;
-
- return (
- ({
- overflowY: 'auto',
- ...common.unstyledScrollbar(t),
- })}
- role='group'
- aria-label={hidePersonal ? 'List of all organization memberships' : 'List of all accounts'}
- >
- {currentOrg && !hidePersonal && (
- ({ borderRadius: 0, borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}` })}
- onClick={onPersonalWorkspaceClick}
- role='menuitem'
- >
- ({
- color: t.colors.$blackAlpha600,
- ':hover': {
- color: t.colors.$blackAlpha600,
- },
- })}
- />
-
- )}
- {otherOrgs.map(organization => (
- ({ borderRadius: 0, borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}` })}
- onClick={() => onOrganizationClick(organization)}
- role='menuitem'
- >
- ({
- color: t.colors.$blackAlpha600,
- ':hover': {
- color: t.colors.$blackAlpha600,
- },
- })}
- />
-
- ))}
- {(hasNextPage || isLoading) && }
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
deleted file mode 100644
index dbab45c0d97..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
+++ /dev/null
@@ -1,414 +0,0 @@
-import type { MembershipRole } from '@clerk/types';
-import { describe } from '@jest/globals';
-
-import { act, render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { OrganizationSwitcher } from '../OrganizationSwitcher';
-import {
- createFakeUserOrganizationInvitation,
- createFakeUserOrganizationMembership,
- createFakeUserOrganizationSuggestion,
-} from './utlis';
-
-const { createFixtures } = bindCreateFixtures('OrganizationSwitcher');
-
-describe('OrganizationSwitcher', () => {
- it('renders component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- const { getByRole } = await act(() => render(, { wrapper }));
- expect(getByRole('button')).toBeInTheDocument();
- });
-
- describe('Personal Workspace', () => {
- it('shows the personal workspace when enabled', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- props.setProps({ hidePersonal: false });
- const { getByText } = await act(() => render(, { wrapper }));
- expect(getByText('Personal account')).toBeInTheDocument();
- });
-
- it('does not show the personal workspace when disabled', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- props.setProps({ hidePersonal: true });
- const { queryByText, getByRole, userEvent, getByText } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- expect(queryByText('Personal Workspace')).toBeNull();
- expect(getByText('No organization selected')).toBeInTheDocument();
- });
- });
-
- describe('OrganizationSwitcherTrigger', () => {
- it('shows the counter for pending suggestions and invitations', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1', permissions: ['org:sys_memberships:manage'] }],
- });
- });
-
- fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 2,
- }),
- );
-
- fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 3,
- }),
- );
-
- const { findByText } = render(, { wrapper });
- expect(await findByText('5')).toBeInTheDocument();
- });
-
- it('shows the counter for pending suggestions and invitations and membership requests', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withOrganizationDomains();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', id: '1', role: 'admin' }],
- });
- });
-
- fixtures.clerk.organization?.getMembershipRequests.mockReturnValue(
- Promise.resolve({
- data: [],
- total_count: 2,
- }),
- );
-
- fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 2,
- }),
- );
-
- fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
- Promise.resolve({
- data: [],
- total_count: 3,
- }),
- );
- const { findByText } = render(, { wrapper });
- expect(await findByText('7')).toBeInTheDocument();
- });
- });
-
- describe('OrganizationSwitcherPopover', () => {
- it('opens the organization switcher popover when clicked', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], create_organization_enabled: true });
- });
-
- props.setProps({ hidePersonal: true });
- const { getByText, getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- expect(getByText('Create Organization')).toBeInTheDocument();
- });
-
- it('lists all organizations the user belongs to', async () => {
- const { wrapper, props, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1', 'Org2'] });
- });
-
- fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationMembership({
- id: '1',
- organization: {
- id: '1',
- name: 'Org1',
- slug: 'org1',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- createFakeUserOrganizationMembership({
- id: '2',
- organization: {
- id: '2',
- name: 'Org2',
- slug: 'org2',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- ],
- total_count: 2,
- }),
- );
-
- props.setProps({ hidePersonal: false });
- const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- expect(getAllByText('Org1')).not.toBeNull();
- expect(getByText('Personal account')).toBeInTheDocument();
- expect(getByText('Org2')).toBeInTheDocument();
- });
-
- it.each([
- ['Admin', 'admin'],
- ['Member', 'basic_member'],
- ['Guest', 'guest_member'],
- ])('shows the text "%s" for the %s role in the active organization', async (text, role) => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: role as MembershipRole }],
- });
- });
-
- props.setProps({ hidePersonal: true });
- const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- expect(getAllByText('Org1').length).toBeGreaterThan(0);
- expect(getByText(text)).toBeInTheDocument();
- });
-
- it('opens organization profile when "Manage Organization" is clicked', async () => {
- const { wrapper, fixtures, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- });
- });
-
- props.setProps({ hidePersonal: true });
- const { getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- await userEvent.click(getByRole('menuitem', { name: 'Manage Organization' }));
- expect(fixtures.clerk.openOrganizationProfile).toHaveBeenCalled();
- });
-
- it('opens create organization when the respective button is clicked', async () => {
- const { wrapper, fixtures, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- create_organization_enabled: true,
- });
- });
-
- props.setProps({ hidePersonal: true });
- const { getByRole, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
- await userEvent.click(getByRole('menuitem', { name: 'Create Organization' }));
- expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
- });
-
- it('does not display create organization button if permissions not present', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- create_organization_enabled: false,
- });
- });
-
- props.setProps({ hidePersonal: true });
- const { queryByRole } = await act(() => render(, { wrapper }));
- expect(queryByRole('button', { name: 'Create Organization' })).not.toBeInTheDocument();
- });
-
- it('displays a list of user invitations', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- create_organization_enabled: false,
- });
- });
-
- fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationInvitation({
- id: '1',
- emailAddress: 'one@clerk.com',
- publicOrganizationData: {
- name: 'OrgOne',
- },
- }),
- createFakeUserOrganizationInvitation({
- id: '2',
- emailAddress: 'two@clerk.com',
- publicOrganizationData: { name: 'OrgTwo' },
- }),
- ],
- total_count: 11,
- }),
- );
- const { queryByText, userEvent, getByRole } = render(, {
- wrapper,
- });
-
- await userEvent.click(getByRole('button'));
-
- expect(fixtures.clerk.user?.getOrganizationInvitations).toHaveBeenCalledWith({
- initialPage: 1,
- pageSize: 10,
- status: 'pending',
- });
- expect(queryByText('OrgOne')).toBeInTheDocument();
- expect(queryByText('OrgTwo')).toBeInTheDocument();
- });
-
- it('displays a list of user suggestions', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
- create_organization_enabled: false,
- });
- });
-
- fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationSuggestion({
- id: '1',
- emailAddress: 'one@clerk.com',
- publicOrganizationData: {
- name: 'OrgOneSuggestion',
- },
- }),
- createFakeUserOrganizationSuggestion({
- id: '2',
- emailAddress: 'two@clerk.com',
- publicOrganizationData: {
- name: 'OrgTwoSuggestion',
- },
- }),
- ],
- total_count: 11,
- }),
- );
- const { queryByText, userEvent, getByRole } = render(, {
- wrapper,
- });
-
- await userEvent.click(getByRole('button'));
-
- expect(fixtures.clerk.user?.getOrganizationSuggestions).toHaveBeenCalledWith({
- initialPage: 1,
- pageSize: 10,
- status: ['pending', 'accepted'],
- });
- expect(queryByText('OrgOneSuggestion')).toBeInTheDocument();
- expect(queryByText('OrgTwoSuggestion')).toBeInTheDocument();
- });
-
- it("switches between active organizations when one is clicked'", async () => {
- const { wrapper, props, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [
- { name: 'Org1', role: 'basic_member' },
- { name: 'Org2', role: 'admin' },
- ],
- create_organization_enabled: false,
- });
- });
-
- fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
- Promise.resolve({
- data: [
- createFakeUserOrganizationMembership({
- id: '1',
- organization: {
- id: '1',
- name: 'Org1',
- slug: 'org1',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- createFakeUserOrganizationMembership({
- id: '2',
- organization: {
- id: '2',
- name: 'Org2',
- slug: 'org2',
- membersCount: 1,
- adminDeleteEnabled: false,
- maxAllowedMemberships: 1,
- pendingInvitationsCount: 1,
- },
- }),
- ],
- total_count: 2,
- }),
- );
-
- fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
-
- props.setProps({ hidePersonal: true });
- const { getByRole, getByText, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- await userEvent.click(getByText('Org2'));
-
- expect(fixtures.clerk.setActive).toHaveBeenCalledWith(
- expect.objectContaining({
- organization: expect.objectContaining({
- name: 'Org2',
- }),
- }),
- );
- });
-
- it("switches to personal workspace when clicked'", async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withOrganizations();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- organization_memberships: [
- { name: 'Org1', role: 'basic_member' },
- { name: 'Org2', role: 'admin' },
- ],
- });
- });
-
- fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
- const { getByRole, getByText, userEvent } = render(, { wrapper });
- await userEvent.click(getByRole('button'));
- await userEvent.click(getByText(/Personal account/i));
-
- expect(fixtures.clerk.setActive).toHaveBeenCalledWith(
- expect.objectContaining({
- organization: null,
- }),
- );
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utils.test.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utils.test.ts
deleted file mode 100644
index 621fe6b5b25..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utils.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { populateCacheRemoveItem, populateCacheUpdateItem } from '../utils';
-
-const staleInfiniteCache = [
- {
- data: [
- { id: '1', foo: 'zoo' },
- {
- id: '2',
- foo: 'bar',
- },
- ],
- total_count: 2,
- },
-];
-
-describe('Populate infinite cache helpers', () => {
- it('populateCacheUpdateItem', () => {
- expect(populateCacheUpdateItem({ id: '2', foo: 'too' }, staleInfiniteCache)).toEqual([
- {
- data: [
- { id: '1', foo: 'zoo' },
- {
- id: '2',
- foo: 'too',
- },
- ],
- total_count: 2,
- },
- ]);
- });
-
- it('populateCacheRemoveItem', () => {
- expect(populateCacheRemoveItem({ id: '2', foo: 'too' }, staleInfiniteCache)).toEqual([
- {
- data: [{ id: '1', foo: 'zoo' }],
- total_count: 1,
- },
- ]);
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utlis.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utlis.ts
deleted file mode 100644
index d212f68988b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/utlis.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import type {
- MembershipRole,
- OrganizationInvitationStatus,
- OrganizationMembershipResource,
- OrganizationSuggestionResource,
- OrganizationSuggestionStatus,
- UserOrganizationInvitationResource,
-} from '@clerk/types';
-import { jest } from '@jest/globals';
-
-import type { FakeOrganizationParams } from '../../CreateOrganization/__tests__/CreateOrganization.test';
-import { createFakeOrganization } from '../../CreateOrganization/__tests__/CreateOrganization.test';
-
-type FakeOrganizationInvitationParams = {
- id: string;
- createdAt?: Date;
- emailAddress: string;
- role?: MembershipRole;
- status?: OrganizationInvitationStatus;
- publicOrganizationData?: { hasImage?: boolean; id?: string; imageUrl?: string; name?: string; slug?: string };
-};
-
-export const createFakeUserOrganizationInvitation = (
- params: FakeOrganizationInvitationParams,
-): UserOrganizationInvitationResource => {
- return {
- pathRoot: '',
- emailAddress: params.emailAddress,
- publicOrganizationData: {
- hasImage: false,
- id: '',
- imageUrl: '',
- name: '',
- slug: '',
- ...params.publicOrganizationData,
- },
- role: params.role || 'basic_member',
- status: params.status || 'pending',
- id: params.id,
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- publicMetadata: {},
- accept: jest.fn() as any,
- reload: jest.fn() as any,
- };
-};
-
-type FakeUserOrganizationMembershipParams = {
- id: string;
- createdAt?: Date;
- role?: MembershipRole;
- organization: FakeOrganizationParams;
-};
-
-export const createFakeUserOrganizationMembership = (
- params: FakeUserOrganizationMembershipParams,
-): OrganizationMembershipResource => {
- return {
- organization: createFakeOrganization(params.organization),
- pathRoot: '',
- role: params.role || 'basic_member',
- id: params.id,
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- publicMetadata: {},
- publicUserData: {} as any,
- update: jest.fn() as any,
- destroy: jest.fn() as any,
- reload: jest.fn() as any,
- };
-};
-
-type FakeOrganizationSuggestionParams = {
- id: string;
- createdAt?: Date;
- emailAddress: string;
- role?: MembershipRole;
- status?: OrganizationSuggestionStatus;
- publicOrganizationData?: { hasImage?: boolean; id?: string; imageUrl?: string; name?: string; slug?: string };
-};
-
-export const createFakeUserOrganizationSuggestion = (
- params: FakeOrganizationSuggestionParams,
-): OrganizationSuggestionResource => {
- return {
- pathRoot: '',
- publicOrganizationData: {
- hasImage: false,
- id: '',
- imageUrl: '',
- name: '',
- slug: '',
- ...params.publicOrganizationData,
- },
- status: params.status || 'pending',
- id: params.id,
- createdAt: params?.createdAt || new Date(),
- updatedAt: new Date(),
- accept: jest.fn() as any,
- reload: jest.fn() as any,
- };
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/index.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/index.ts
deleted file mode 100644
index d2ce931b01c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './OrganizationSwitcher';
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/utils.ts b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/utils.ts
deleted file mode 100644
index 345d6825d00..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/utils.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { useOrganizationList } from '@clerk/shared/react';
-import type { ClerkPaginatedResponse } from '@clerk/types';
-
-export const organizationListParams = {
- userMemberships: {
- infinite: true,
- },
- userInvitations: {
- infinite: true,
- },
- userSuggestions: {
- infinite: true,
- status: ['pending', 'accepted'],
- },
-} satisfies Parameters[0];
-
-export const populateCacheUpdateItem = (
- updatedItem: T,
- itemsInfinitePages: (ClerkPaginatedResponse | undefined)[] | undefined,
-) => {
- if (typeof itemsInfinitePages === 'undefined') {
- return [{ data: [updatedItem], total_count: 1 }];
- }
-
- /**
- * We should "preserve" an undefined page if one is found. For example if swr triggers 2 requests, page 1 & page2, and the request for page2 resolves first, at that point in memory itemsInfinitePages would look like this [undefined, {....}]
- * if SWR says that has fetched 2 pages but the first result of is undefined, we should not return back an array with 1 item as this will end up having cacheKeys that point nowhere.
- */
- return itemsInfinitePages.map(item => {
- if (typeof item === 'undefined') {
- return item;
- }
- const newData = item.data.map(obj => {
- if (obj.id === updatedItem.id) {
- return {
- ...updatedItem,
- };
- }
-
- return obj;
- });
- return { ...item, data: newData };
- });
-};
-
-export const populateCacheRemoveItem = (
- updatedItem: T,
- itemsInfinitePages: (ClerkPaginatedResponse | undefined)[] | undefined,
-) => {
- const prevTotalCount = itemsInfinitePages?.[itemsInfinitePages.length - 1]?.total_count;
-
- if (!prevTotalCount) {
- return undefined;
- }
-
- /**
- * We should "preserve" an undefined page if one is found. For example if swr triggers 2 requests, page 1 & page2, and the request for page2 resolves first, at that point in memory itemsInfinitePages would look like this [undefined, {....}]
- * if SWR says that has fetched 2 pages but the first result of is undefined, we should not return back an array with 1 item as this will end up having cacheKeys that point nowhere.
- */
- return itemsInfinitePages?.map(item => {
- if (typeof item === 'undefined') {
- return item;
- }
- const newData = item.data.filter(obj => {
- return obj.id !== updatedItem.id;
- });
- return { ...item, data: newData, total_count: prevTotalCount - 1 };
- });
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/AlternativeMethods.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/AlternativeMethods.tsx
deleted file mode 100644
index 94336342938..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/AlternativeMethods.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import type { SignInFactor } from '@clerk/types';
-import React from 'react';
-
-import type { LocalizationKey } from '../../customizables';
-import { descriptors, Flex, Flow, localizationKeys, Text } from '../../customizables';
-import { ArrowBlockButton, BackLink, Card, CardAlert, Footer, Header } from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { useAlternativeStrategies } from '../../hooks/useAlternativeStrategies';
-import { ChatAltIcon, Email, LinkIcon, LockClosedIcon, RequestAuthIcon } from '../../icons';
-import { formatSafeIdentifier } from '../../utils';
-import { SignInSocialButtons } from './SignInSocialButtons';
-import { useResetPasswordFactor } from './useResetPasswordFactor';
-import { withHavingTrouble } from './withHavingTrouble';
-
-export type AlternativeMethodsProps = {
- onBackLinkClick: React.MouseEventHandler | undefined;
- onFactorSelected: (factor: SignInFactor) => void;
- currentFactor: SignInFactor | undefined | null;
- asForgotPassword?: boolean;
-};
-
-export type AlternativeMethodListProps = AlternativeMethodsProps & { onHavingTroubleClick: React.MouseEventHandler };
-
-export const AlternativeMethods = (props: AlternativeMethodsProps) => {
- return withHavingTrouble(AlternativeMethodsList, {
- ...props,
- });
-};
-
-const AlternativeMethodsList = (props: AlternativeMethodListProps) => {
- const { onBackLinkClick, onHavingTroubleClick, onFactorSelected, asForgotPassword = false } = props;
- const card = useCardState();
- const resetPasswordFactor = useResetPasswordFactor();
- const { firstPartyFactors, hasAnyStrategy } = useAlternativeStrategies({
- filterOutFactor: props?.currentFactor,
- });
-
- return (
-
-
-
- {/* TODO: Add text "Don’t have any of these?" */}
- {/* */}
-
-
-
- ,
- ]}
- >
- {card.error}
-
-
-
- {/*TODO: extract main in its own component */}
-
- {asForgotPassword && resetPasswordFactor && (
- onFactorSelected(resetPasswordFactor)}
- />
- )}
- {hasAnyStrategy && (
- <>
- {asForgotPassword && (
-
- )}
-
-
- {firstPartyFactors.map((factor, i) => (
- onFactorSelected(factor)}
- />
- ))}
-
- >
- )}
- {onBackLinkClick && (
-
- )}
-
-
-
-
-
-
- );
-};
-
-export function getButtonLabel(factor: SignInFactor): LocalizationKey {
- switch (factor.strategy) {
- case 'email_link':
- return localizationKeys('signIn.alternativeMethods.blockButton__emailLink', {
- identifier: formatSafeIdentifier(factor.safeIdentifier) || '',
- });
- case 'email_code':
- return localizationKeys('signIn.alternativeMethods.blockButton__emailCode', {
- identifier: formatSafeIdentifier(factor.safeIdentifier) || '',
- });
- case 'phone_code':
- return localizationKeys('signIn.alternativeMethods.blockButton__phoneCode', {
- identifier: formatSafeIdentifier(factor.safeIdentifier) || '',
- });
- case 'password':
- return localizationKeys('signIn.alternativeMethods.blockButton__password');
- case 'reset_password_email_code':
- return localizationKeys('signIn.forgotPasswordAlternativeMethods.blockButton__resetPassword');
- case 'reset_password_phone_code':
- return localizationKeys('signIn.forgotPasswordAlternativeMethods.blockButton__resetPassword');
- default:
- throw `Invalid sign in strategy: "${factor.strategy}"`;
- }
-}
-
-export function getButtonIcon(factor: SignInFactor) {
- const icons = {
- email_link: LinkIcon,
- email_code: Email,
- phone_code: ChatAltIcon,
- reset_password_email_code: RequestAuthIcon,
- reset_password_phone_code: RequestAuthIcon,
- password: LockClosedIcon,
- } as const;
-
- return icons[factor.strategy as keyof typeof icons];
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/HavingTrouble.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/HavingTrouble.tsx
deleted file mode 100644
index 4a16ba79ed0..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/HavingTrouble.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { localizationKeys } from '../../customizables';
-import { ErrorCard } from '../../elements';
-import type { PropsOfComponent } from '../../styledSystem';
-
-export const HavingTrouble = (props: PropsOfComponent) => {
- const { onBackLinkClick } = props;
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPassword.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPassword.tsx
deleted file mode 100644
index f189a36b7f5..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPassword.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { withRedirectToHomeSingleSessionGuard } from '../../common';
-import { useCoreSignIn, useEnvironment } from '../../contexts';
-import { Col, descriptors, localizationKeys, useLocalizations } from '../../customizables';
-import { Card, CardAlert, Form, Header, useCardState, withCardStateProvider } from '../../elements';
-import { useConfirmPassword } from '../../hooks';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import { useRouter } from '../../router';
-import { createPasswordError, handleError, useFormControl } from '../../utils';
-
-export const _ResetPassword = () => {
- const signIn = useCoreSignIn();
- const card = useCardState();
- const { navigate } = useRouter();
- const supportEmail = useSupportEmail();
- const {
- userSettings: { passwordSettings },
- } = useEnvironment();
-
- const { t, locale } = useLocalizations();
-
- const requiresNewPassword =
- signIn.status === 'needs_new_password' &&
- signIn.firstFactorVerification.strategy !== 'reset_password_email_code' &&
- signIn.firstFactorVerification.strategy !== 'reset_password_phone_code';
-
- React.useEffect(() => {
- if (requiresNewPassword) {
- card.setError(t(localizationKeys('signIn.resetPassword.requiredMessage')));
- }
- }, []);
-
- const passwordField = useFormControl('password', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__newPassword'),
- isRequired: true,
- validatePassword: true,
- buildErrorMessage: errors => createPasswordError(errors, { t, locale, passwordSettings }),
- });
-
- const confirmField = useFormControl('confirmPassword', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__confirmPassword'),
- isRequired: true,
- });
-
- const sessionsField = useFormControl('signOutOfOtherSessions', '', {
- type: 'checkbox',
- label: localizationKeys('formFieldLabel__signOutOfOtherSessions'),
- defaultChecked: true,
- });
-
- const { setConfirmPasswordFeedback, isPasswordMatch } = useConfirmPassword({
- passwordField,
- confirmPasswordField: confirmField,
- });
-
- const canSubmit = isPasswordMatch;
-
- const validateForm = () => {
- if (passwordField.value) {
- setConfirmPasswordFeedback(confirmField.value);
- }
- };
-
- const resetPassword = async () => {
- passwordField.setError(undefined);
- confirmField.setError(undefined);
- try {
- const { status, createdSessionId } = await signIn.resetPassword({
- password: passwordField.value,
- signOutOfOtherSessions: sessionsField.checked,
- });
-
- switch (status) {
- case 'complete':
- if (createdSessionId) {
- const queryParams = new URLSearchParams();
- queryParams.set('createdSessionId', createdSessionId);
- return navigate(`../reset-password-success?${queryParams.toString()}`);
- }
- return console.error(clerkInvalidFAPIResponse(status, supportEmail));
- case 'needs_second_factor':
- return navigate('../factor-two');
- default:
- return console.error(clerkInvalidFAPIResponse(status, supportEmail));
- }
- } catch (e) {
- return handleError(e, [passwordField, confirmField], card.setError);
- }
- };
-
- return (
-
- {card.error}
-
-
-
-
-
- {/* For password managers */}
-
-
-
-
-
- {
- if (e.target.value) {
- setConfirmPasswordFeedback(e.target.value);
- }
- return confirmField.props.onChange(e);
- }}
- />
-
- {!requiresNewPassword && (
-
-
-
- )}
-
-
-
-
- );
-};
-
-export const ResetPassword = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_ResetPassword));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPasswordSuccess.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPasswordSuccess.tsx
deleted file mode 100644
index 97298a62720..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/ResetPasswordSuccess.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { withRedirectToHomeSingleSessionGuard } from '../../common';
-import { Col, descriptors, localizationKeys, Spinner, Text } from '../../customizables';
-import { Card, CardAlert, Header, useCardState, withCardStateProvider } from '../../elements';
-import { useSetSessionWithTimeout } from '../../hooks/useSetSessionWithTimeout';
-import { Flex } from '../../primitives';
-
-export const _ResetPasswordSuccess = () => {
- const card = useCardState();
- useSetSessionWithTimeout();
- return (
-
- {card.error}
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const ResetPasswordSuccess = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_ResetPasswordSuccess));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignIn.tsx
deleted file mode 100644
index 7f5d8594e21..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignIn.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { SignInModalProps, SignInProps } from '@clerk/types';
-import React from 'react';
-
-import { SignInEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
-import { ComponentContext, useSignInContext, withCoreSessionSwitchGuard } from '../../contexts';
-import { Flow } from '../../customizables';
-import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
-import { ResetPassword } from './ResetPassword';
-import { ResetPasswordSuccess } from './ResetPasswordSuccess';
-import { SignInAccountSwitcher } from './SignInAccountSwitcher';
-import { SignInFactorOne } from './SignInFactorOne';
-import { SignInFactorTwo } from './SignInFactorTwo';
-import { SignInSSOCallback } from './SignInSSOCallback';
-import { SignInStart } from './SignInStart';
-
-function RedirectToSignIn() {
- const clerk = useClerk();
- React.useEffect(() => {
- void clerk.redirectToSignIn();
- }, []);
- return null;
-}
-
-function SignInRoutes(): JSX.Element {
- const signInContext = useSignInContext();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-SignInRoutes.displayName = 'SignIn';
-
-export const SignIn: React.ComponentType = withCoreSessionSwitchGuard(SignInRoutes);
-
-export const SignInModal = (props: SignInModalProps): JSX.Element => {
- const signInProps = {
- signUpUrl: `/${VIRTUAL_ROUTER_BASE_PATH}/sign-up`,
- ...props,
- };
-
- return (
-
-
- {/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInAccountSwitcher.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInAccountSwitcher.tsx
deleted file mode 100644
index 6f716e37087..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInAccountSwitcher.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { withRedirectToHomeSingleSessionGuard } from '../../common';
-import { useEnvironment, useSignInContext } from '../../contexts';
-import { Col, descriptors, Flow, Icon } from '../../customizables';
-import { Card, CardAlert, Header, PreviewButton, UserPreview, withCardStateProvider } from '../../elements';
-import { ArrowBlockButton } from '../../elements/ArrowBlockButton';
-import { useCardState } from '../../elements/contexts';
-import { Plus, SignOutDouble } from '../../icons';
-import { useRouter } from '../../router';
-import { useMultisessionActions } from '../UserButton/useMultisessionActions';
-
-const _SignInAccountSwitcher = () => {
- const card = useCardState();
- const { navigate } = useRouter();
- const { applicationName, userProfileUrl, signInUrl, afterSignOutAllUrl } = useEnvironment().displayConfig;
- const { navigateAfterSignIn } = useSignInContext();
- const { handleSignOutAllClicked, handleSessionClicked, activeSessions, handleAddAccountClicked } =
- useMultisessionActions({
- navigateAfterSignOut: () => navigate(afterSignOutAllUrl),
- navigateAfterSwitchSession: navigateAfterSignIn,
- userProfileUrl,
- signInUrl,
- user: undefined,
- });
-
- return (
-
-
- {card.error}
-
- Signed out
- Select account to continue to {applicationName}
-
-
-
- {activeSessions.map(s => (
- ({ height: theme.sizes.$16, justifyContent: 'flex-start' })}
- >
-
-
- ))}
-
-
- ({ color: theme.colors.$blackAlpha500 })}
- />
- }
- onClick={handleAddAccountClicked}
- >
- Add account
-
- ({ color: theme.colors.$blackAlpha500 })}
- />
- }
- onClick={handleSignOutAllClicked}
- >
- Sign out of all accounts
-
-
-
-
-
- );
-};
-export const SignInAccountSwitcher = withRedirectToHomeSingleSessionGuard(
- withCardStateProvider(_SignInAccountSwitcher),
-);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOne.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOne.tsx
deleted file mode 100644
index 804fe73186c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOne.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import type { ResetPasswordCodeFactor, SignInFactor } from '@clerk/types';
-import React from 'react';
-
-import { withRedirectToHomeSingleSessionGuard } from '../../common';
-import { useCoreSignIn, useEnvironment } from '../../contexts';
-import { ErrorCard, LoadingCard, withCardStateProvider } from '../../elements';
-import { useAlternativeStrategies } from '../../hooks/useAlternativeStrategies';
-import { localizationKeys } from '../../localization';
-import { useRouter } from '../../router';
-import { AlternativeMethods } from './AlternativeMethods';
-import { SignInFactorOneEmailCodeCard } from './SignInFactorOneEmailCodeCard';
-import { SignInFactorOneEmailLinkCard } from './SignInFactorOneEmailLinkCard';
-import { SignInFactorOneForgotPasswordCard } from './SignInFactorOneForgotPasswordCard';
-import { SignInFactorOnePasswordCard } from './SignInFactorOnePasswordCard';
-import { SignInFactorOnePhoneCodeCard } from './SignInFactorOnePhoneCodeCard';
-import { useResetPasswordFactor } from './useResetPasswordFactor';
-import { determineStartingSignInFactor, factorHasLocalStrategy } from './utils';
-
-const factorKey = (factor: SignInFactor | null | undefined) => {
- if (!factor) {
- return '';
- }
- let key = factor.strategy;
- if ('emailAddressId' in factor) {
- key += factor.emailAddressId;
- }
- if ('phoneNumberId' in factor) {
- key += factor.phoneNumberId;
- }
- return key;
-};
-
-export function _SignInFactorOne(): JSX.Element {
- const signIn = useCoreSignIn();
- const { preferredSignInStrategy } = useEnvironment().displayConfig;
- const availableFactors = signIn.supportedFirstFactors;
- const router = useRouter();
-
- const lastPreparedFactorKeyRef = React.useRef('');
- const [{ currentFactor }, setFactor] = React.useState<{
- currentFactor: SignInFactor | undefined | null;
- prevCurrentFactor: SignInFactor | undefined | null;
- }>(() => ({
- currentFactor: determineStartingSignInFactor(availableFactors, signIn.identifier, preferredSignInStrategy),
- prevCurrentFactor: undefined,
- }));
-
- const { hasAnyStrategy } = useAlternativeStrategies({
- filterOutFactor: currentFactor,
- });
-
- const [showAllStrategies, setShowAllStrategies] = React.useState(
- () => !currentFactor || !factorHasLocalStrategy(currentFactor),
- );
-
- const resetPasswordFactor = useResetPasswordFactor();
-
- const [showForgotPasswordStrategies, setShowForgotPasswordStrategies] = React.useState(false);
-
- React.useEffect(() => {
- // Handle the case where a user lands on alternative methods screen,
- // clicks a social button but then navigates back to sign in.
- // SignIn status resets to 'needs_identifier'
- if (signIn.status === 'needs_identifier' || signIn.status === null) {
- void router.navigate('../');
- }
- }, []);
-
- if (!currentFactor && signIn.status) {
- return (
-
- );
- }
-
- const toggleAllStrategies = hasAnyStrategy ? () => setShowAllStrategies(s => !s) : undefined;
-
- const toggleForgotPasswordStrategies = () => setShowForgotPasswordStrategies(s => !s);
-
- const handleFactorPrepare = () => {
- lastPreparedFactorKeyRef.current = factorKey(currentFactor);
- };
- const selectFactor = (factor: SignInFactor) => {
- setFactor(prev => ({
- currentFactor: factor,
- prevCurrentFactor: prev.currentFactor,
- }));
- };
- if (showAllStrategies || showForgotPasswordStrategies) {
- const canGoBack = factorHasLocalStrategy(currentFactor);
-
- const toggle = showAllStrategies ? toggleAllStrategies : toggleForgotPasswordStrategies;
-
- return (
- {
- selectFactor(f);
- toggle?.();
- }}
- currentFactor={currentFactor}
- />
- );
- }
-
- if (!currentFactor) {
- return ;
- }
-
- switch (currentFactor?.strategy) {
- case 'password':
- return (
- {
- handleFactorPrepare();
- setFactor(prev => ({
- currentFactor: {
- ...factor,
- },
- prevCurrentFactor: prev.currentFactor,
- }));
- }}
- onForgotPasswordMethodClick={resetPasswordFactor ? toggleForgotPasswordStrategies : toggleAllStrategies}
- onShowAlternativeMethodsClick={toggleAllStrategies}
- />
- );
- case 'email_code':
- return (
-
- );
- case 'phone_code':
- return (
-
- );
- case 'email_link':
- return (
-
- );
- case 'reset_password_phone_code':
- return (
- {
- setFactor(prev => ({
- currentFactor: prev.prevCurrentFactor,
- prevCurrentFactor: prev.currentFactor,
- }));
- toggleForgotPasswordStrategies();
- }}
- cardTitle={localizationKeys('signIn.forgotPassword.title_phone')}
- formSubtitle={localizationKeys('signIn.forgotPassword.formSubtitle_phone')}
- />
- );
-
- case 'reset_password_email_code':
- return (
- {
- setFactor(prev => ({
- currentFactor: prev.prevCurrentFactor,
- prevCurrentFactor: prev.currentFactor,
- }));
- toggleForgotPasswordStrategies();
- }}
- cardTitle={localizationKeys('signIn.forgotPassword.title_email')}
- formSubtitle={localizationKeys('signIn.forgotPassword.formSubtitle_email')}
- />
- );
- default:
- return ;
- }
-}
-
-export const SignInFactorOne = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInFactorOne));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneCodeForm.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneCodeForm.tsx
deleted file mode 100644
index 852cb3166a8..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneCodeForm.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { isUserLockedError } from '@clerk/shared';
-import { useClerk } from '@clerk/shared/react';
-import type { EmailCodeFactor, PhoneCodeFactor, ResetPasswordCodeFactor } from '@clerk/types';
-import React from 'react';
-
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { useCoreSignIn, useSignInContext } from '../../contexts';
-import type { VerificationCodeCardProps } from '../../elements';
-import { useCardState, VerificationCodeCard } from '../../elements';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import type { LocalizationKey } from '../../localization';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-
-export type SignInFactorOneCodeCard = Pick<
- VerificationCodeCardProps,
- 'onShowAlternativeMethodsClicked' | 'showAlternativeMethods' | 'onBackLinkClicked'
-> & {
- factor: EmailCodeFactor | PhoneCodeFactor | ResetPasswordCodeFactor;
- factorAlreadyPrepared: boolean;
- onFactorPrepare: () => void;
-};
-
-export type SignInFactorOneCodeFormProps = SignInFactorOneCodeCard & {
- cardTitle: LocalizationKey;
- cardSubtitle: LocalizationKey;
- formTitle: LocalizationKey;
- formSubtitle: LocalizationKey;
- resendButton: LocalizationKey;
-};
-
-export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) => {
- const signIn = useCoreSignIn();
- const card = useCardState();
- const { navigate } = useRouter();
- const { navigateAfterSignIn } = useSignInContext();
- const { setActive } = useClerk();
- const supportEmail = useSupportEmail();
- const clerk = useClerk();
-
- const goBack = () => {
- return navigate('../');
- };
-
- React.useEffect(() => {
- if (!props.factorAlreadyPrepared) {
- prepare();
- }
- }, []);
-
- const prepare = () => {
- void signIn
- .prepareFirstFactor(props.factor)
- .then(() => props.onFactorPrepare())
- .catch(err => handleError(err, [], card.setError));
- };
-
- const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
- signIn
- .attemptFirstFactor({ strategy: props.factor.strategy, code })
- .then(async res => {
- await resolve();
-
- switch (res.status) {
- case 'complete':
- return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
- case 'needs_second_factor':
- return navigate('../factor-two');
- case 'needs_new_password':
- return navigate('../reset-password');
- default:
- return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- }
- })
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- return reject(err);
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailCodeCard.tsx
deleted file mode 100644
index 2cfcc12f457..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailCodeCard.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { EmailCodeFactor } from '@clerk/types';
-
-import { useEnvironment } from '../../contexts';
-import { Flow, localizationKeys } from '../../customizables';
-import type { SignInFactorOneCodeCard } from './SignInFactorOneCodeForm';
-import { SignInFactorOneCodeForm } from './SignInFactorOneCodeForm';
-
-type SignInFactorOneEmailCodeCardProps = SignInFactorOneCodeCard & { factor: EmailCodeFactor };
-
-export const SignInFactorOneEmailCodeCard = (props: SignInFactorOneEmailCodeCardProps) => {
- const { applicationName } = useEnvironment().displayConfig;
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailLinkCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailLinkCard.tsx
deleted file mode 100644
index 848196556b4..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneEmailLinkCard.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { isUserLockedError } from '@clerk/shared/error';
-import { useClerk } from '@clerk/shared/react';
-import type { EmailLinkFactor, SignInResource } from '@clerk/types';
-import React from 'react';
-
-import { EmailLinkStatusCard } from '../../common';
-import { buildEmailLinkRedirectUrl } from '../../common/redirects';
-import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts';
-import { Flow, localizationKeys, useLocalizations } from '../../customizables';
-import type { VerificationCodeCardProps } from '../../elements';
-import { VerificationLinkCard } from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { useEmailLink } from '../../hooks/useEmailLink';
-import { useRouter } from '../../router/RouteContext';
-import { handleError } from '../../utils';
-
-type SignInFactorOneEmailLinkCardProps = Pick & {
- factor: EmailLinkFactor;
- factorAlreadyPrepared: boolean;
- onFactorPrepare: () => void;
-};
-
-export const SignInFactorOneEmailLinkCard = (props: SignInFactorOneEmailLinkCardProps) => {
- const { t } = useLocalizations();
- const card = useCardState();
- const signIn = useCoreSignIn();
- const signInContext = useSignInContext();
- const { signInUrl } = useEnvironment().displayConfig;
- const { navigate } = useRouter();
- const { navigateAfterSignIn } = useSignInContext();
- const { setActive } = useClerk();
- const { startEmailLinkFlow, cancelEmailLinkFlow } = useEmailLink(signIn);
- const [showVerifyModal, setShowVerifyModal] = React.useState(false);
- const clerk = useClerk();
-
- React.useEffect(() => {
- void startEmailLinkVerification();
- }, []);
-
- const restartVerification = () => {
- cancelEmailLinkFlow();
- void startEmailLinkVerification();
- };
-
- const startEmailLinkVerification = () => {
- startEmailLinkFlow({
- emailAddressId: props.factor.emailAddressId,
- redirectUrl: buildEmailLinkRedirectUrl(signInContext, signInUrl),
- })
- .then(res => handleVerificationResult(res))
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- handleError(err, [], card.setError);
- });
- };
-
- const handleVerificationResult = async (si: SignInResource) => {
- const ver = si.firstFactorVerification;
- if (ver.status === 'expired') {
- card.setError(t(localizationKeys('formFieldError__verificationLinkExpired')));
- } else if (ver.verifiedFromTheSameClient()) {
- setShowVerifyModal(true);
- } else {
- await completeSignInFlow(si);
- }
- };
-
- const completeSignInFlow = async (si: SignInResource) => {
- if (si.status === 'complete') {
- return setActive({
- session: si.createdSessionId,
- beforeEmit: navigateAfterSignIn,
- });
- } else if (si.status === 'needs_second_factor') {
- return navigate('../factor-two');
- }
- };
-
- if (showVerifyModal) {
- return (
-
- );
- }
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneForgotPasswordCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneForgotPasswordCard.tsx
deleted file mode 100644
index 89a50c5e96e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOneForgotPasswordCard.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { ResetPasswordCodeFactor } from '@clerk/types';
-
-import { Flow, localizationKeys } from '../../customizables';
-import type { SignInFactorOneCodeCard, SignInFactorOneCodeFormProps } from './SignInFactorOneCodeForm';
-import { SignInFactorOneCodeForm } from './SignInFactorOneCodeForm';
-
-type SignInForgotPasswordCardProps = SignInFactorOneCodeCard &
- Pick & {
- factor: ResetPasswordCodeFactor;
- };
-
-export const SignInFactorOneForgotPasswordCard = (props: SignInForgotPasswordCardProps) => {
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePasswordCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePasswordCard.tsx
deleted file mode 100644
index cacd93168cf..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePasswordCard.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { isUserLockedError } from '@clerk/shared/error';
-import { useClerk } from '@clerk/shared/react';
-import type { ResetPasswordCodeFactor } from '@clerk/types';
-import React from 'react';
-
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { useCoreSignIn, useSignInContext } from '../../contexts';
-import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
-import { Card, CardAlert, Footer, Form, Header, IdentityPreview, useCardState } from '../../elements';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import { useRouter } from '../../router/RouteContext';
-import { handleError, useFormControl } from '../../utils';
-import { HavingTrouble } from './HavingTrouble';
-import { useResetPasswordFactor } from './useResetPasswordFactor';
-
-type SignInFactorOnePasswordProps = {
- onForgotPasswordMethodClick: React.MouseEventHandler | undefined;
- onShowAlternativeMethodsClick: React.MouseEventHandler | undefined;
- onFactorPrepare: (f: ResetPasswordCodeFactor) => void;
-};
-
-const usePasswordControl = (props: SignInFactorOnePasswordProps) => {
- const { onForgotPasswordMethodClick, onShowAlternativeMethodsClick } = props;
- const resetPasswordFactor = useResetPasswordFactor();
-
- const passwordControl = useFormControl('password', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__password'),
- placeholder: localizationKeys('formFieldInputPlaceholder__password'),
- });
-
- return {
- ...passwordControl,
- props: {
- ...passwordControl.props,
- actionLabel:
- resetPasswordFactor || onShowAlternativeMethodsClick ? localizationKeys('formFieldAction__forgotPassword') : '',
- onActionClicked: onForgotPasswordMethodClick
- ? onForgotPasswordMethodClick
- : onShowAlternativeMethodsClick
- ? onShowAlternativeMethodsClick
- : () => null,
- },
- };
-};
-
-export const SignInFactorOnePasswordCard = (props: SignInFactorOnePasswordProps) => {
- const { onShowAlternativeMethodsClick } = props;
- const card = useCardState();
- const { setActive } = useClerk();
- const signIn = useCoreSignIn();
- const { navigateAfterSignIn } = useSignInContext();
- const supportEmail = useSupportEmail();
- const passwordControl = usePasswordControl(props);
- const { navigate } = useRouter();
- const [showHavingTrouble, setShowHavingTrouble] = React.useState(false);
- const toggleHavingTrouble = React.useCallback(() => setShowHavingTrouble(s => !s), [setShowHavingTrouble]);
- const clerk = useClerk();
-
- const goBack = () => {
- return navigate('../');
- };
-
- const handlePasswordSubmit: React.FormEventHandler = async e => {
- e.preventDefault();
- return signIn
- .attemptFirstFactor({ strategy: 'password', password: passwordControl.value })
- .then(res => {
- switch (res.status) {
- case 'complete':
- return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
- case 'needs_second_factor':
- return navigate('../factor-two');
- default:
- return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- }
- })
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- handleError(err, [passwordControl], card.setError);
- });
- };
-
- if (showHavingTrouble) {
- return ;
- }
-
- return (
-
-
- {card.error}
-
-
-
-
-
- {/*TODO: extract main in its own component */}
-
-
- {/* For password managers */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePhoneCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePhoneCodeCard.tsx
deleted file mode 100644
index 333d0ccd524..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorOnePhoneCodeCard.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { PhoneCodeFactor } from '@clerk/types';
-
-import { useEnvironment } from '../../contexts';
-import { Flow, localizationKeys } from '../../customizables';
-import type { SignInFactorOneCodeCard } from './SignInFactorOneCodeForm';
-import { SignInFactorOneCodeForm } from './SignInFactorOneCodeForm';
-
-type SignInFactorOnePhoneCodeCardProps = SignInFactorOneCodeCard & { factor: PhoneCodeFactor };
-
-export const SignInFactorOnePhoneCodeCard = (props: SignInFactorOnePhoneCodeCardProps) => {
- const { applicationName } = useEnvironment().displayConfig;
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwo.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwo.tsx
deleted file mode 100644
index 59294503010..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwo.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import type { SignInFactor } from '@clerk/types';
-import React from 'react';
-
-import { withRedirectToHomeSingleSessionGuard } from '../../common';
-import { useCoreSignIn } from '../../contexts';
-import { LoadingCard, withCardStateProvider } from '../../elements';
-import { SignInFactorTwoAlternativeMethods } from './SignInFactorTwoAlternativeMethods';
-import { SignInFactorTwoBackupCodeCard } from './SignInFactorTwoBackupCodeCard';
-import { SignInFactorTwoPhoneCodeCard } from './SignInFactorTwoPhoneCodeCard';
-import { SignInFactorTwoTOTPCard } from './SignInFactorTwoTOTPCard';
-import { determineStartingSignInSecondFactor } from './utils';
-
-const factorKey = (factor: SignInFactor | null | undefined) => {
- if (!factor) {
- return '';
- }
- let key = factor.strategy;
- if ('phoneNumberId' in factor) {
- key += factor.phoneNumberId;
- }
- return key;
-};
-
-export function _SignInFactorTwo(): JSX.Element {
- const signIn = useCoreSignIn();
- const availableFactors = signIn.supportedSecondFactors;
-
- const lastPreparedFactorKeyRef = React.useRef('');
- const [currentFactor, setCurrentFactor] = React.useState(() =>
- determineStartingSignInSecondFactor(availableFactors),
- );
- const [showAllStrategies, setShowAllStrategies] = React.useState(!currentFactor);
- const toggleAllStrategies = () => setShowAllStrategies(s => !s);
-
- // TODO
- const handleFactorPrepare = () => {
- lastPreparedFactorKeyRef.current = factorKey(currentFactor);
- };
-
- const selectFactor = (factor: SignInFactor) => {
- setCurrentFactor(factor);
- toggleAllStrategies();
- };
-
- if (!currentFactor) {
- return ;
- }
-
- if (showAllStrategies) {
- return (
-
- );
- }
-
- switch (currentFactor?.strategy) {
- case 'phone_code':
- return (
-
- );
- case 'totp':
- return (
-
- );
- case 'backup_code':
- return ;
- default:
- return ;
- }
-}
-
-export const SignInFactorTwo = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInFactorTwo));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoAlternativeMethods.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoAlternativeMethods.tsx
deleted file mode 100644
index e99c96c33f9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoAlternativeMethods.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { SignInFactor } from '@clerk/types';
-import React from 'react';
-
-import { useCoreSignIn } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
-import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
-import { ArrowBlockButton, Card, CardAlert, Footer, Header } from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { backupCodePrefFactorComparator, formatSafeIdentifier } from '../../utils';
-import { HavingTrouble } from './HavingTrouble';
-
-export type AlternativeMethodsProps = {
- onBackLinkClick: React.MouseEventHandler | undefined;
- onFactorSelected: (factor: SignInFactor) => void;
-};
-
-export const SignInFactorTwoAlternativeMethods = (props: AlternativeMethodsProps) => {
- const [showHavingTrouble, setShowHavingTrouble] = React.useState(false);
- const toggleHavingTrouble = React.useCallback(() => setShowHavingTrouble(s => !s), [setShowHavingTrouble]);
-
- if (showHavingTrouble) {
- return ;
- }
-
- return (
-
- );
-};
-
-const AlternativeMethodsList = (props: AlternativeMethodsProps & { onHavingTroubleClick: React.MouseEventHandler }) => {
- const { onHavingTroubleClick, onFactorSelected } = props;
- const card = useCardState();
- const { supportedSecondFactors } = useCoreSignIn();
-
- return (
-
-
- {card.error}
-
-
-
- {/*TODO: extract main in its own component */}
-
-
- {supportedSecondFactors.sort(backupCodePrefFactorComparator).map((factor, i) => (
- onFactorSelected(factor)}
- />
- ))}
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export function getButtonLabel(factor: SignInFactor): LocalizationKey {
- switch (factor.strategy) {
- case 'phone_code':
- return localizationKeys('signIn.alternativeMethods.blockButton__phoneCode', {
- identifier: formatSafeIdentifier(factor.safeIdentifier) || '',
- });
- case 'totp':
- return localizationKeys('signIn.alternativeMethods.blockButton__totp');
- case 'backup_code':
- return localizationKeys('signIn.alternativeMethods.blockButton__backupCode');
- default:
- throw `Invalid sign in strategy: "${factor.strategy}"`;
- }
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoBackupCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoBackupCodeCard.tsx
deleted file mode 100644
index 6a44a5052b8..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoBackupCodeCard.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { isUserLockedError } from '@clerk/shared/error';
-import { useClerk } from '@clerk/shared/react';
-import type { SignInResource } from '@clerk/types';
-import React from 'react';
-
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts';
-import { Col, descriptors, localizationKeys } from '../../customizables';
-import { Card, CardAlert, Footer, Form, Header, useCardState } from '../../elements';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { isResetPasswordStrategy } from './utils';
-
-type SignInFactorTwoBackupCodeCardProps = {
- onShowAlternativeMethodsClicked: React.MouseEventHandler;
-};
-
-export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCardProps) => {
- const { onShowAlternativeMethodsClicked } = props;
- const signIn = useCoreSignIn();
- const { displayConfig } = useEnvironment();
- const { navigateAfterSignIn } = useSignInContext();
- const { setActive } = useClerk();
- const { navigate } = useRouter();
- const supportEmail = useSupportEmail();
- const card = useCardState();
- const codeControl = useFormControl('code', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__backupCode'),
- isRequired: true,
- });
- const clerk = useClerk();
-
- const isResettingPassword = (resource: SignInResource) =>
- isResetPasswordStrategy(resource.firstFactorVerification?.strategy) &&
- resource.firstFactorVerification?.status === 'verified';
-
- const handleBackupCodeSubmit: React.FormEventHandler = e => {
- e.preventDefault();
- return signIn
- .attemptSecondFactor({ strategy: 'backup_code', code: codeControl.value })
- .then(res => {
- switch (res.status) {
- case 'complete':
- if (isResettingPassword(res) && res.createdSessionId) {
- const queryParams = new URLSearchParams();
- queryParams.set('createdSessionId', res.createdSessionId);
- return navigate(`../reset-password-success?${queryParams.toString()}`);
- }
- return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
- default:
- return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- }
- })
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- handleError(err, [codeControl], card.setError);
- });
- };
-
- return (
-
- {card.error}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {onShowAlternativeMethodsClicked && (
-
- )}
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoCodeForm.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoCodeForm.tsx
deleted file mode 100644
index 9307f7e21d9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoCodeForm.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { isUserLockedError } from '@clerk/shared/error';
-import { useClerk } from '@clerk/shared/react';
-import type { PhoneCodeFactor, SignInResource, TOTPFactor } from '@clerk/types';
-import React from 'react';
-
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { useCoreSignIn, useSignInContext } from '../../contexts';
-import { localizationKeys, Text } from '../../customizables';
-import type { VerificationCodeCardProps } from '../../elements';
-import { useCardState, VerificationCodeCard } from '../../elements';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import type { LocalizationKey } from '../../localization';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-import { isResetPasswordStrategy } from './utils';
-
-export type SignInFactorTwoCodeCard = Pick & {
- factor: PhoneCodeFactor | TOTPFactor;
- factorAlreadyPrepared: boolean;
- onFactorPrepare: () => void;
- prepare?: () => Promise;
-};
-
-type SignInFactorTwoCodeFormProps = SignInFactorTwoCodeCard & {
- cardTitle: LocalizationKey;
- cardSubtitle: LocalizationKey;
- formTitle: LocalizationKey;
- formSubtitle: LocalizationKey;
- resendButton?: LocalizationKey;
-};
-
-export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) => {
- const signIn = useCoreSignIn();
- const card = useCardState();
- const { navigateAfterSignIn } = useSignInContext();
- const { setActive } = useClerk();
- const { navigate } = useRouter();
- const supportEmail = useSupportEmail();
- const clerk = useClerk();
-
- React.useEffect(() => {
- if (props.factorAlreadyPrepared) {
- return;
- }
-
- void prepare?.();
- }, []);
-
- const prepare = props.prepare
- ? () => {
- return props
- .prepare?.()
- .then(() => props.onFactorPrepare())
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- handleError(err, [], card.setError);
- });
- }
- : undefined;
-
- const isResettingPassword = (resource: SignInResource) =>
- isResetPasswordStrategy(resource.firstFactorVerification?.strategy) &&
- resource.firstFactorVerification?.status === 'verified';
-
- const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
- signIn
- .attemptSecondFactor({ strategy: props.factor.strategy, code })
- .then(async res => {
- await resolve();
- switch (res.status) {
- case 'complete':
- if (isResettingPassword(res) && res.createdSessionId) {
- const queryParams = new URLSearchParams();
- queryParams.set('createdSessionId', res.createdSessionId);
- return navigate(`../reset-password-success?${queryParams.toString()}`);
- }
- return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
- default:
- return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- }
- })
- .catch(err => {
- if (isUserLockedError(err)) {
- // @ts-expect-error -- private method for the time being
- return clerk.__internal_navigateWithError('..', err.errors[0]);
- }
-
- return reject(err);
- });
- };
-
- return (
-
- {isResettingPassword(signIn) && (
-
- )}
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoPhoneCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoPhoneCodeCard.tsx
deleted file mode 100644
index 6475e70677b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoPhoneCodeCard.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { PhoneCodeFactor } from '@clerk/types';
-
-import { useCoreSignIn } from '../../contexts';
-import { Flow, localizationKeys } from '../../customizables';
-import type { SignInFactorTwoCodeCard } from './SignInFactorTwoCodeForm';
-import { SignInFactorTwoCodeForm } from './SignInFactorTwoCodeForm';
-
-type SignInFactorTwoPhoneCodeCardProps = SignInFactorTwoCodeCard & { factor: PhoneCodeFactor };
-
-export const SignInFactorTwoPhoneCodeCard = (props: SignInFactorTwoPhoneCodeCardProps) => {
- const signIn = useCoreSignIn();
-
- const prepare = () => {
- // TODO: Why does the BE throw an error if I simply pass
- // the whole factor?
- const { phoneNumberId, strategy } = props.factor;
- return signIn.prepareSecondFactor({ phoneNumberId, strategy });
- };
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoTOTPCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoTOTPCard.tsx
deleted file mode 100644
index eeeca199dbd..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInFactorTwoTOTPCard.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { TOTPFactor } from '@clerk/types';
-
-import { Flow, localizationKeys } from '../../customizables';
-import type { SignInFactorTwoCodeCard } from './SignInFactorTwoCodeForm';
-import { SignInFactorTwoCodeForm } from './SignInFactorTwoCodeForm';
-
-type SignInFactorTwoTOTPCardProps = SignInFactorTwoCodeCard & { factor: TOTPFactor };
-
-export const SignInFactorTwoTOTPCard = (props: SignInFactorTwoTOTPCardProps) => {
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSSOCallback.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSSOCallback.tsx
deleted file mode 100644
index c1bad1c3664..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSSOCallback.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { SSOCallback, withRedirectToHomeSingleSessionGuard } from '../../common';
-
-export const SignInSSOCallback = withRedirectToHomeSingleSessionGuard(SSOCallback);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSocialButtons.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSocialButtons.tsx
deleted file mode 100644
index 2ba1b6aae35..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInSocialButtons.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import React from 'react';
-
-import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignIn, useSignInContext } from '../../contexts';
-import { useEnvironment } from '../../contexts/EnvironmentContext';
-import { useCardState } from '../../elements/contexts';
-import type { SocialButtonsProps } from '../../elements/SocialButtons';
-import { SocialButtons } from '../../elements/SocialButtons';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-
-export const SignInSocialButtons = React.memo((props: SocialButtonsProps) => {
- const clerk = useClerk();
- const { navigate } = useRouter();
- const card = useCardState();
- const { displayConfig } = useEnvironment();
- const ctx = useSignInContext();
- const signIn = useCoreSignIn();
- const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signInUrl);
- const redirectUrlComplete = ctx.afterSignInUrl || '/';
-
- return (
- {
- return signIn
- .authenticateWithRedirect({ strategy, redirectUrl, redirectUrlComplete })
- .catch(err => handleError(err, [], card.setError));
- }}
- web3Callback={() => {
- return clerk
- .authenticateWithMetamask({
- customNavigate: navigate,
- redirectUrl: redirectUrlComplete,
- signUpContinueUrl: ctx.signUpContinueUrl,
- })
- .catch(err => handleError(err, [], card.setError));
- }}
- />
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/SignInStart.tsx
deleted file mode 100644
index 18271f82330..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/SignInStart.tsx
+++ /dev/null
@@ -1,393 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { ClerkAPIError, SignInCreateParams } from '@clerk/types';
-import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
-
-import { ERROR_CODES } from '../../../core/constants';
-import { clerkInvalidFAPIResponse } from '../../../core/errors';
-import { getClerkQueryParam } from '../../../utils';
-import type { SignInStartIdentifier } from '../../common';
-import {
- getIdentifierControlDisplayValues,
- groupIdentifiers,
- withRedirectToHomeSingleSessionGuard,
-} from '../../common';
-import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts';
-import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
-import {
- Card,
- CardAlert,
- Footer,
- Form,
- Header,
- LoadingCard,
- SocialButtonsReversibleContainerWithDivider,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useLoadingStatus } from '../../hooks';
-import { useSupportEmail } from '../../hooks/useSupportEmail';
-import { useRouter } from '../../router';
-import type { FormControlState } from '../../utils';
-import { buildRequest, handleError, isMobileDevice, useFormControl } from '../../utils';
-import { SignInSocialButtons } from './SignInSocialButtons';
-
-export function _SignInStart(): JSX.Element {
- const card = useCardState();
- const clerk = useClerk();
- const status = useLoadingStatus();
- const { displayConfig, userSettings } = useEnvironment();
- const signIn = useCoreSignIn();
- const { navigate } = useRouter();
- const ctx = useSignInContext();
- const { navigateAfterSignIn, signUpUrl } = ctx;
- const supportEmail = useSupportEmail();
- const identifierAttributes = useMemo(
- () => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
- [userSettings.enabledFirstFactorIdentifiers],
- );
-
- const onlyPhoneNumberInitialValueExists =
- !!ctx.initialValues?.phoneNumber && !(ctx.initialValues.emailAddress || ctx.initialValues.username);
- const shouldStartWithPhoneNumberIdentifier =
- onlyPhoneNumberInitialValueExists && identifierAttributes.includes('phone_number');
- const [identifierAttribute, setIdentifierAttribute] = useState(
- shouldStartWithPhoneNumberIdentifier ? 'phone_number' : identifierAttributes[0] || '',
- );
- const [hasSwitchedByAutofill, setHasSwitchedByAutofill] = useState(false);
-
- const organizationTicket = getClerkQueryParam('__clerk_ticket') || '';
-
- const standardFormAttributes = userSettings.enabledFirstFactorIdentifiers;
- const web3FirstFactors = userSettings.web3FirstFactors;
- const authenticatableSocialStrategies = userSettings.authenticatableSocialStrategies;
- const passwordBasedInstance = userSettings.instanceIsPasswordBased;
- const { currentIdentifier, nextIdentifier } = getIdentifierControlDisplayValues(
- identifierAttributes,
- identifierAttribute,
- );
- const instantPasswordField = useFormControl('password', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__password'),
- placeholder: localizationKeys('formFieldInputPlaceholder__password') as any,
- });
-
- const ctxInitialValues = ctx.initialValues || {};
- const initialValues: Record = useMemo(
- () => ({
- email_address: ctxInitialValues.emailAddress,
- email_address_username: ctxInitialValues.emailAddress || ctxInitialValues.username,
- username: ctxInitialValues.username,
- phone_number: ctxInitialValues.phoneNumber,
- }),
- [ctx.initialValues],
- );
-
- const hasSocialOrWeb3Buttons = !!authenticatableSocialStrategies.length || !!web3FirstFactors.length;
- const [shouldAutofocus, setShouldAutofocus] = useState(!isMobileDevice() && !hasSocialOrWeb3Buttons);
- const textIdentifierField = useFormControl('identifier', initialValues[identifierAttribute] || '', {
- ...currentIdentifier,
- isRequired: true,
- });
-
- const phoneIdentifierField = useFormControl('identifier', initialValues['phone_number'] || '', {
- ...currentIdentifier,
- isRequired: true,
- });
-
- const identifierField = identifierAttribute === 'phone_number' ? phoneIdentifierField : textIdentifierField;
-
- const switchToNextIdentifier = () => {
- setIdentifierAttribute(
- i => identifierAttributes[(identifierAttributes.indexOf(i) + 1) % identifierAttributes.length],
- );
- setShouldAutofocus(true);
- setHasSwitchedByAutofill(false);
- };
-
- const handlePhoneNumberPaste = (value: string) => {
- textIdentifierField.setValue(initialValues[identifierAttribute] || '');
- phoneIdentifierField.setValue(value);
- setIdentifierAttribute('phone_number');
- setShouldAutofocus(true);
- };
-
- // switch to the phone input (if available) if a "+" is entered
- // (either by the browser or the user)
- // this does not work in chrome as it does not fire the change event and the value is
- // not available via js
- useLayoutEffect(() => {
- if (
- identifierField.value.startsWith('+') &&
- identifierAttributes.includes('phone_number') &&
- identifierAttribute !== 'phone_number' &&
- !hasSwitchedByAutofill
- ) {
- handlePhoneNumberPaste(identifierField.value);
- // do not switch automatically on subsequent autofills
- // by the browser to avoid a switch loop
- setHasSwitchedByAutofill(true);
- }
- }, [identifierField.value, identifierAttributes]);
-
- useEffect(() => {
- if (!organizationTicket) {
- return;
- }
-
- status.setLoading();
- card.setLoading();
- signIn
- .create({
- strategy: 'ticket',
- ticket: organizationTicket,
- })
- .then(res => {
- switch (res.status) {
- case 'needs_first_factor':
- return navigate('factor-one');
- case 'needs_second_factor':
- return navigate('factor-two');
- case 'complete':
- return clerk.setActive({
- session: res.createdSessionId,
- beforeEmit: navigateAfterSignIn,
- });
- default: {
- console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- return;
- }
- }
- })
- .catch(err => {
- return attemptToRecoverFromSignInError(err);
- })
- .finally(() => {
- status.setIdle();
- card.setIdle();
- });
- }, []);
-
- useEffect(() => {
- async function handleOauthError() {
- const error = signIn?.firstFactorVerification?.error;
- if (error) {
- switch (error.code) {
- case ERROR_CODES.NOT_ALLOWED_TO_SIGN_UP:
- case ERROR_CODES.OAUTH_ACCESS_DENIED:
- case ERROR_CODES.NOT_ALLOWED_ACCESS:
- case ERROR_CODES.SAML_USER_ATTRIBUTE_MISSING:
- case ERROR_CODES.OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML:
- case ERROR_CODES.USER_LOCKED:
- card.setError(error.longMessage);
- break;
- default:
- // Error from server may be too much information for the end user, so set a generic error
- card.setError('Unable to complete action at this time. If the problem persists please contact support.');
- }
- // TODO: This is a workaround in order to reset the sign in attempt
- // so that the oauth error does not persist on full page reloads.
- void (await signIn.create({}));
- }
- }
-
- void handleOauthError();
- }, []);
-
- const buildSignInParams = (fields: Array>): SignInCreateParams => {
- const hasPassword = fields.some(f => f.name === 'password' && !!f.value);
- if (!hasPassword) {
- fields = fields.filter(f => f.name !== 'password');
- }
- return {
- ...buildRequest(fields),
- ...(hasPassword && { strategy: 'password' }),
- } as SignInCreateParams;
- };
-
- const signInWithFields = async (...fields: Array>) => {
- try {
- const res = await signIn.create(buildSignInParams(fields));
- switch (res.status) {
- case 'needs_identifier':
- // Check if we need to initiate a saml flow
- if (res.supportedFirstFactors.some(ff => ff.strategy === 'saml')) {
- await authenticateWithSaml();
- }
- break;
- case 'needs_first_factor':
- return navigate('factor-one');
- case 'needs_second_factor':
- return navigate('factor-two');
- case 'complete':
- return clerk.setActive({
- session: res.createdSessionId,
- beforeEmit: navigateAfterSignIn,
- });
- default: {
- console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
- return;
- }
- }
- } catch (e) {
- return attemptToRecoverFromSignInError(e);
- }
- };
-
- const authenticateWithSaml = async () => {
- const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signInUrl);
- const redirectUrlComplete = ctx.afterSignInUrl || '/';
-
- return signIn.authenticateWithRedirect({
- strategy: 'saml',
- redirectUrl,
- redirectUrlComplete,
- });
- };
-
- const attemptToRecoverFromSignInError = async (e: any) => {
- if (!e.errors) {
- return;
- }
- const instantPasswordError: ClerkAPIError = e.errors.find(
- (e: ClerkAPIError) =>
- e.code === ERROR_CODES.INVALID_STRATEGY_FOR_USER || e.code === ERROR_CODES.FORM_PASSWORD_INCORRECT,
- );
- const alreadySignedInError: ClerkAPIError = e.errors.find(
- (e: ClerkAPIError) => e.code === 'identifier_already_signed_in',
- );
-
- if (instantPasswordError) {
- await signInWithFields(identifierField);
- } else if (alreadySignedInError) {
- const sid = alreadySignedInError.meta!.sessionId!;
- await clerk.setActive({ session: sid, beforeEmit: navigateAfterSignIn });
- } else {
- handleError(e, [identifierField, instantPasswordField], card.setError);
- }
- };
-
- const handleFirstPartySubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- return signInWithFields(identifierField, instantPasswordField);
- };
-
- const DynamicField = useMemo(() => {
- const components = {
- tel: Form.PhoneInput,
- password: Form.PasswordInput,
- text: Form.PlainInput,
- email: Form.PlainInput,
- };
-
- return components[identifierField.type as keyof typeof components];
- }, [identifierField.type]);
-
- if (status.isLoading) {
- return ;
- }
-
- // @ts-expect-error `action` is not typed
- const { action, ...identifierFieldProps } = identifierField.props;
- return (
-
-
-
-
-
-
-
- ,
- ]}
- >
- {card.error}
-
-
-
-
- {/*TODO: extract main in its own component */}
-
-
- {hasSocialOrWeb3Buttons && (
-
- )}
- {standardFormAttributes.length ? (
-
-
-
-
-
-
-
- ) : null}
-
-
-
-
- );
-}
-
-const InstantPasswordRow = ({ field }: { field?: FormControlState<'password'> }) => {
- const [autofilled, setAutofilled] = useState(false);
- const ref = useRef(null);
- const show = !!(autofilled || field?.value);
-
- // show password if it's autofilled by the browser
- useLayoutEffect(() => {
- const intervalId = setInterval(() => {
- if (ref?.current) {
- const autofilled = window.getComputedStyle(ref.current, ':autofill').animationName === 'onAutoFillStart';
- if (autofilled) {
- setAutofilled(autofilled);
- clearInterval(intervalId);
- }
- }
- }, 500);
-
- return () => {
- clearInterval(intervalId);
- };
- }, []);
-
- useEffect(() => {
- //if the field receives a value, we default to normal behaviour
- if (field?.value && field.value !== '') {
- setAutofilled(false);
- }
- }, [field?.value]);
-
- if (!field) {
- return null;
- }
-
- return (
-
-
-
- );
-};
-
-export const SignInStart = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInStart));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPassword.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPassword.test.tsx
deleted file mode 100644
index a827c5544bf..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPassword.test.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import type { SignInResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-
-import { fireEvent, render, screen, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { runFakeTimers } from '../../../utils/test/runFakeTimers';
-import { ResetPassword } from '../ResetPassword';
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-describe('ResetPassword', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
-
- render(, { wrapper });
- screen.getByRole('heading', { name: /Reset password/i });
-
- screen.getByLabelText(/New password/i);
- screen.getByLabelText(/Confirm password/i);
- });
-
- it('renders information text below Password field', async () => {
- const { wrapper } = 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,
- }),
- );
-
- await runFakeTimers(async () => {
- render(, { wrapper });
- screen.getByRole('heading', { name: /Reset password/i });
-
- const passwordField = screen.getByLabelText(/New password/i);
- fireEvent.focus(passwordField);
- await waitFor(() => {
- screen.getByText(/Your password must contain 8 or more characters/i);
- });
- });
- });
-
- it('renders a hidden identifier field', async () => {
- const identifier = 'test@clerk.com';
- const { wrapper } = await createFixtures(f => {
- f.startSignInWithEmailAddress({ identifier });
- });
- render(, { wrapper });
-
- const identifierField: HTMLInputElement = screen.getByTestId('hidden-identifier');
- expect(identifierField.value).toBe(identifier);
- });
-
- describe('Actions', () => {
- it('resets the password and does not require MFA', async () => {
- const { wrapper, fixtures } = await createFixtures();
- fixtures.signIn.resetPassword.mockResolvedValue({
- status: 'complete',
- createdSessionId: '1234_session_id',
- } as SignInResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/New password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/Confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('button', { name: /Reset Password/i }));
- expect(fixtures.signIn.resetPassword).toHaveBeenCalledWith({
- password: 'testtest',
- signOutOfOtherSessions: true,
- });
- expect(fixtures.router.navigate).toHaveBeenCalledWith(
- '../reset-password-success?createdSessionId=1234_session_id',
- );
- });
-
- it('resets the password, does not require MFA and leaves sessions intact', async () => {
- const { wrapper, fixtures } = await createFixtures();
- fixtures.signIn.resetPassword.mockResolvedValue({
- status: 'complete',
- createdSessionId: '1234_session_id',
- } as SignInResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/New password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/Confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('checkbox', { name: /sign out of all other devices/i }));
- await userEvent.click(screen.getByRole('button', { name: /Reset Password/i }));
- expect(fixtures.signIn.resetPassword).toHaveBeenCalledWith({
- password: 'testtest',
- signOutOfOtherSessions: false,
- });
- expect(fixtures.router.navigate).toHaveBeenCalledWith(
- '../reset-password-success?createdSessionId=1234_session_id',
- );
- });
-
- it('resets the password and requires MFA', async () => {
- const { wrapper, fixtures } = await createFixtures();
- fixtures.signIn.resetPassword.mockResolvedValue({
- status: 'needs_second_factor',
- createdSessionId: '1234_session_id',
- } as SignInResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/New password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/Confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('button', { name: /Reset Password/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../factor-two');
- });
-
- it('results in error if the passwords do not match and persists', async () => {
- const { wrapper } = await createFixtures();
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr');
- const confirmField = screen.getByLabelText(/confirm password/i);
- await userEvent.type(confirmField, 'testrwerrwqrwe');
- await waitFor(() => {
- expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument();
- });
-
- await userEvent.clear(confirmField);
- await waitFor(() => {
- expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument();
- });
- });
- }, 10000);
-
- it('navigates to the root page upon pressing the back link', async () => {
- const { wrapper, fixtures } = await createFixtures();
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByText(/back/i));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../');
- });
-
- it('resets the password, when it is required for the user', async () => {
- const { wrapper, fixtures } = await createFixtures();
- fixtures.clerk.client.signIn.status = 'needs_new_password';
- fixtures.clerk.client.signIn.firstFactorVerification.strategy = 'oauth_google';
- fixtures.signIn.resetPassword.mockResolvedValue({
- status: 'complete',
- createdSessionId: '1234_session_id',
- } as SignInResource);
- const { userEvent } = render(, { wrapper });
-
- expect(screen.queryByText(/account already exists/i)).toBeInTheDocument();
- expect(screen.queryByRole('checkbox', { name: /sign out of all other devices/i })).not.toBeInTheDocument();
- await userEvent.type(screen.getByLabelText(/New password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/Confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('button', { name: /Reset Password/i }));
- expect(fixtures.signIn.resetPassword).toHaveBeenCalledWith({
- password: 'testtest',
- signOutOfOtherSessions: true,
- });
- expect(fixtures.router.navigate).toHaveBeenCalledWith(
- '../reset-password-success?createdSessionId=1234_session_id',
- );
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx
deleted file mode 100644
index fa848f93d63..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { describe, it } from '@jest/globals';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { runFakeTimers } from '../../../utils/test/runFakeTimers';
-import { ResetPasswordSuccess } from '../ResetPasswordSuccess';
-
-const { createFixtures: createFixturesWithQuery } = bindCreateFixtures('SignIn', {
- router: {
- queryString: '?createdSessionId=1234_session_id',
- },
-});
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-describe('ResetPasswordSuccess', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
-
- render(, { wrapper });
- screen.getByRole('heading', { name: /Reset password/i });
- screen.getByText(/Your password was successfully changed. Signing you in, please wait a moment/i);
- });
-
- it('sets active session after 2000 ms', async () => {
- const { wrapper, fixtures } = await createFixturesWithQuery();
- runFakeTimers(timers => {
- render(, { wrapper });
- timers.advanceTimersByTime(1000);
- expect(fixtures.clerk.setActive).not.toHaveBeenCalled();
- timers.advanceTimersByTime(1000);
- expect(fixtures.clerk.setActive).toHaveBeenCalled();
- });
- });
-
- it('does not set a session if createdSessionId is missing', async () => {
- const { wrapper, fixtures } = await createFixtures();
- runFakeTimers(timers => {
- render(, { wrapper });
- timers.advanceTimersByTime(2000);
- expect(fixtures.clerk.setActive).not.toHaveBeenCalled();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx
deleted file mode 100644
index 29f4610249b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { describe, it } from '@jest/globals';
-
-import { render } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignInAccountSwitcher } from '../SignInAccountSwitcher';
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-const initConfig = createFixtures.config(f => {
- f.withMultiSessionMode();
- f.withUser({ first_name: 'Nick', last_name: 'Kouk', email_addresses: ['test1@clerk.com'] });
- f.withUser({ first_name: 'Mike', last_name: 'Lamar', email_addresses: ['test2@clerk.com'] });
- f.withUser({ first_name: 'Graciela', last_name: 'Brennan', email_addresses: ['test3@clerk.com'] });
-});
-
-describe('SignInAccountSwitcher', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
- render(, { wrapper });
- });
-
- it('renders a list of buttons with all signed in accounts', async () => {
- const { wrapper } = await createFixtures(initConfig);
- const { getByText } = render(, { wrapper });
- expect(getByText('Nick Kouk')).toBeDefined();
- expect(getByText('Mike Lamar')).toBeDefined();
- expect(getByText('Graciela Brennan')).toBeDefined();
- });
-
- it('sets an active session when user clicks an already logged in account from the list', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
- fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
- const { userEvent, getByText } = render(, { wrapper });
- await userEvent.click(getByText('Nick Kouk'));
- expect(fixtures.clerk.setActive).toHaveBeenCalled();
- });
-
- // this one uses the windowNavigate method. we need to mock it correctly
- it.skip('navigates to SignInStart component if user clicks on "Add account" button', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
- const { userEvent, getByText } = render(, { wrapper });
- await userEvent.click(getByText('Add account'));
- expect(fixtures.router.navigate).toHaveBeenCalled();
- });
-
- it('signs out when user clicks on "Sign out of all accounts"', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
- const { userEvent, getByText } = render(, { wrapper });
- expect(getByText('Nick Kouk')).toBeDefined();
- expect(getByText('Mike Lamar')).toBeDefined();
- expect(getByText('Graciela Brennan')).toBeDefined();
- await userEvent.click(getByText('Sign out of all accounts'));
- expect(fixtures.clerk.signOut).toHaveBeenCalled();
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorOne.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorOne.test.tsx
deleted file mode 100644
index ba51c0b6826..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorOne.test.tsx
+++ /dev/null
@@ -1,788 +0,0 @@
-import { parseError } from '@clerk/shared/error';
-import type { SignInResource } from '@clerk/types';
-import { describe, it, jest } from '@jest/globals';
-import { waitFor } from '@testing-library/dom';
-
-import { ClerkAPIResponseError } from '../../../../core/resources';
-import { act, render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { runFakeTimers } from '../../../utils/test/runFakeTimers';
-import { SignInFactorOne } from '../SignInFactorOne';
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-describe('SignInFactorOne', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- render(, { wrapper });
- screen.getByText('Check your email');
- });
-
- it('prefills the email if the identifier is an email', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: ['email_code', 'email_link'] });
- f.withPassword();
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportEmailLink: true, identifier: 'test@clerk.com' });
- });
-
- render(, { wrapper });
- screen.getByText('test@clerk.com');
- });
-
- it('prefills the phone number if the identifier is a phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPassword();
- f.startSignInWithPhoneNumber({ identifier: '+301234567890' });
- });
-
- render(, { wrapper });
- screen.getByText('+30 123 4567890');
- });
-
- describe('Navigation', () => {
- it('navigates to SignInStart component when user clicks the edit icon', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { container, userEvent } = render(, { wrapper });
- container.getElementsByClassName('cl-identityPreviewEditButton');
- const editButton = container.getElementsByClassName('cl-identityPreviewEditButton').item(0);
- expect(editButton).toBeDefined();
- if (editButton) {
- await userEvent.click(editButton);
- }
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../');
- });
-
- it('navigates to SignInStart component if the user lands on SignInFactorOne directly without calling signIn.create', async () => {
- const { wrapper, fixtures } = await createFixtures();
- render(, { wrapper });
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../');
- });
- });
-
- describe('Submitting', () => {
- it('navigates to SignInFactorTwo page when user submits first factor and second factor is enabled', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'needs_second_factor' } as SignInResource),
- );
- await runFakeTimers(async timers => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- timers.runOnlyPendingTimers();
- await waitFor(() => expect(fixtures.router.navigate).toHaveBeenCalledWith('../factor-two'));
- });
- });
-
- it('sets an active session when user submits first factor successfully and second factor does not exist', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(Promise.resolve({ status: 'complete' } as SignInResource));
- await runFakeTimers(async timers => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- timers.runOnlyPendingTimers();
- await waitFor(() => {
- expect(fixtures.clerk.setActive).toHaveBeenCalled();
- });
- });
- });
- });
-
- describe('Selected First Factor Method', () => {
- describe('Password', () => {
- it('shows an input to fill with password', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportPassword: true });
- });
- render(, { wrapper });
- screen.getByText('Password');
- });
-
- it('should render the other methods component when clicking on "Forgot password"', async () => {
- const email = 'test@clerk.com';
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({
- supportEmailCode: true,
- supportPassword: true,
- supportResetPassword: false,
- identifier: email,
- });
- });
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText(/Forgot password/i));
- screen.getByText('Use another method');
- expect(screen.queryByText('Or, sign in with another method.')).not.toBeInTheDocument();
- screen.getByText(`Email code to ${email}`);
- expect(screen.queryByText('Sign in with your password')).not.toBeInTheDocument();
- });
-
- it('should render the Forgot Password alternative methods component when clicking on "Forgot password" (email)', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({
- supportEmailCode: true,
- supportPassword: true,
- supportResetPassword: true,
- });
- });
- const { userEvent } = render(, { wrapper });
-
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- await userEvent.click(screen.getByText(/Forgot password/i));
- screen.getByText('Forgot Password?');
- screen.getByText('Or, sign in with another method.');
- await userEvent.click(screen.getByText('Reset your password'));
- screen.getByText('Check your email');
- screen.getByText('to reset your password');
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithPhoneNumber({ supportPassword: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect Password',
- message: 'is incorrect',
- meta: { param_name: 'password' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText('Password'), '123456');
- await userEvent.click(screen.getByText('Continue'));
- await waitFor(() => expect(screen.getByText('Incorrect Password')).toBeDefined());
- });
- });
-
- it('redirects back to sign-in if the user is locked', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithPhoneNumber({ supportPassword: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
-
- const errJSON = {
- code: 'user_locked',
- long_message: 'Your account is locked. Please try again after 1 hour.',
- message: 'Account locked',
- meta: { duration_in_seconds: 3600 },
- };
-
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [errJSON],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText('Password'), '123456');
- await userEvent.click(screen.getByText('Continue'));
- await waitFor(() => {
- expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON));
- });
- });
- });
- });
-
- describe('Forgot Password', () => {
- it('shows an input to add the code sent to phone', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithPhoneNumber({
- supportPassword: true,
- supportResetPassword: true,
- });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText(/Forgot password/i));
- screen.getByText('Forgot Password?');
-
- await userEvent.click(screen.getByText('Reset your password'));
- screen.getByText('Check your phone');
- screen.getByText('Reset password code');
- });
-
- it('redirects to `reset-password` on successful code verification', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({
- supportEmailCode: true,
- supportPassword: true,
- supportResetPassword: true,
- });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'needs_new_password' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText(/Forgot password/i));
- await userEvent.click(screen.getByText('Reset your password'));
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptFirstFactor).toHaveBeenCalledWith({
- strategy: 'reset_password_email_code',
- code: '123456',
- });
- await waitFor(() => {
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../reset-password');
- });
- });
-
- it('redirects to `reset-password` on successful code verification received in phone number', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithPhoneNumber({
- supportPassword: true,
- supportResetPassword: true,
- });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'needs_new_password' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText(/Forgot password/i));
- await userEvent.click(screen.getByText('Reset your password'));
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptFirstFactor).toHaveBeenCalledWith({
- strategy: 'reset_password_phone_code',
- code: '123456',
- });
- await waitFor(() => {
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../reset-password');
- });
- });
- });
-
- describe('Verification link', () => {
- it('shows message to use the magic link in their email', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withEmailLink();
- f.startSignInWithEmailAddress({ supportEmailLink: true, supportPassword: false });
- });
-
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- render(, { wrapper });
- screen.getByText('Use the verification link sent to your email');
- });
-
- it('enables the "Resend link" button after 60 seconds', async () => {
- jest.useFakeTimers();
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withEmailLink();
- f.startSignInWithEmailAddress({ supportEmailLink: true, supportPassword: false });
- });
-
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- const { getByText } = render(, { wrapper });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(30000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(30000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
-
- jest.useRealTimers();
- });
- });
-
- describe('Email Code', () => {
- it('shows an input to add the code sent to email', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- render(, { wrapper });
- screen.getByText('Enter the verification code sent to your email address');
- });
-
- it('enables the "Resend code" button after 30 seconds', async () => {
- jest.useFakeTimers();
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
-
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { getByText } = render(, { wrapper });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(15000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(15000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
-
- jest.useRealTimers();
- });
-
- it('auto submits when typing all the 6 digits of the code', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptFirstFactor).toHaveBeenCalled();
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect code',
- message: 'is incorrect',
- meta: { param_name: 'code' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => expect(screen.getByText('Incorrect code')).toBeDefined());
- });
- });
-
- it('redirects back to sign-in if the user is locked', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
-
- const errJSON = {
- code: 'user_locked',
- long_message: 'Your account is locked. Please try again after 2 hours.',
- message: 'Account locked',
- meta: { duration_in_seconds: 7200 },
- };
-
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [errJSON],
- status: 422,
- }),
- );
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON));
- });
- });
- });
-
- describe('Phone Code', () => {
- it('shows an input to add the code sent to phone', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- render(, { wrapper });
- screen.getByText('Enter the verification code sent to your phone number');
- });
-
- it('enables the "Resend" button after 30 seconds', async () => {
- jest.useFakeTimers();
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
-
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { getByText } = render(, { wrapper });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(15000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await act(() => {
- jest.advanceTimersByTime(15000);
- });
- expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
-
- jest.useRealTimers();
- });
-
- it('auto submits when typing all the 6 digits of the code', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptFirstFactor).toHaveBeenCalled();
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect phone code',
- message: 'is incorrect',
- meta: { param_name: 'code' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined());
- });
- });
-
- it('redirects back to sign-in if the user is locked', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true, supportPassword: false });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
-
- const errJSON = {
- code: 'user_locked',
- long_message: 'Your account is locked. Please contact support for more information.',
- message: 'Account locked',
- };
-
- fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [errJSON],
- status: 422,
- }),
- );
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => {
- expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON));
- });
- });
- });
- });
- });
-
- describe('Use another method', () => {
- it('should render the other authentication methods list component when clicking on "Use another method"', async () => {
- const email = 'test@clerk.com';
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: ['email_code', 'email_link'] });
- f.withPassword();
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportEmailLink: true, identifier: email });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(`Email code to ${email}`);
- screen.getByText(`Email link to ${email}`);
- expect(screen.queryByText(`Sign in with your password`)).not.toBeInTheDocument();
- });
-
- it('"Use another method" should not exist if only the current strategy is available', async () => {
- const email = 'test@clerk.com';
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: [], verifications: [] });
- f.withPassword();
- f.startSignInWithEmailAddress({
- supportEmailCode: false,
- supportEmailLink: false,
- identifier: email,
- supportPassword: true,
- });
- });
-
- render(, { wrapper });
- expect(screen.queryByText(`Use another method`)).not.toBeInTheDocument();
- screen.getByText(`Get help`);
- });
-
- it('should go back to the main screen when clicking the "<- Back" button from the "Use another method" page', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Back'));
- screen.getByText('Enter your password');
- });
-
- it('should list all the enabled first factor methods', async () => {
- const email = 'test@clerk.com';
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true, identifier: email });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(`Sign in with your password`);
- const deactivatedMethod = screen.queryByText(`Send link to ${email}`);
- expect(deactivatedMethod).not.toBeInTheDocument();
- });
-
- it('should list enabled first factor methods without the current one', async () => {
- const email = 'test@clerk.com';
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withSocialProvider({ provider: 'google' });
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true, identifier: email });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- const currentMethod = screen.queryByText(`Send code to ${email}`);
- expect(currentMethod).not.toBeInTheDocument();
- screen.getByText(/Continue with google/i);
- screen.getByText(`Sign in with your password`);
- const deactivatedMethod = screen.queryByText(`Send link to ${email}`);
- expect(deactivatedMethod).not.toBeInTheDocument();
- });
-
- it('clicking the password method should show the password input', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithEmailAddress({ supportEmailCode: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Sign in with your password'));
- screen.getByText('Enter your password');
- });
-
- it('clicking the email link method should show the magic link screen', async () => {
- const email = 'test@clerk.com';
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({ supportEmailLink: true, identifier: email });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(`Email link to ${email}`);
- await userEvent.click(screen.getByText(`Email link to ${email}`));
- screen.getByText('Check your email');
- screen.getByText('Verification link');
- });
-
- it('clicking the email code method should show the email code input', async () => {
- const email = 'test@clerk.com';
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithEmailAddress({ supportEmailCode: true, identifier: email });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.createEmailLinkFlow.mockReturnValue({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any);
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(`Email code to ${email}`);
- await userEvent.click(screen.getByText(`Email code to ${email}`));
- screen.getByText('Check your email');
- screen.getByText('Verification code');
- });
-
- it('clicking the phone code method should show the phone code input', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'password' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- });
- fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.createEmailLinkFlow.mockReturnValue({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any);
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(/code to \+/);
- await userEvent.click(screen.getByText(/code to \+/));
- screen.getByText('Check your phone');
- });
-
- describe('Get Help', () => {
- it('should render the get help component when clicking the "Get Help" button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: ['email_code', 'email_link'] });
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportEmailLink: true });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- screen.getByText('Email support');
- });
-
- it('should go back to "Use another method" screen when clicking the "<- Back" button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: ['email_code', 'email_link'] });
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportEmailLink: true });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- await userEvent.click(screen.getByText('Back'));
- screen.getByText('Use another method');
- });
-
- // this test needs us to mock the window.location.href to work properly
- it.skip('should open a "mailto:" link when clicking the email support button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ first_factors: ['email_code', 'email_link'] });
- f.startSignInWithEmailAddress({ supportEmailCode: true, supportEmailLink: true });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- screen.getByText('Email support');
- await userEvent.click(screen.getByText('Email support'));
- //TODO: check that location.href setter is called
- });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorTwo.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorTwo.test.tsx
deleted file mode 100644
index 750bd0c3ae0..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInFactorTwo.test.tsx
+++ /dev/null
@@ -1,589 +0,0 @@
-import { parseError } from '@clerk/shared/error';
-import type { SignInResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-
-import { ClerkAPIResponseError } from '../../../../core/resources';
-import { render, screen, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { runFakeTimers } from '../../../utils/test/runFakeTimers';
-import { SignInFactorTwo } from '../SignInFactorTwo';
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-describe('SignInFactorTwo', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- });
- render(, { wrapper });
- });
-
- describe('Navigation', () => {
- //This isn't yet implemented in the component
- it.todo('navigates to SignInStart component if user lands on SignInFactorTwo page but they should not');
- });
-
- describe('Submitting', () => {
- it('correctly shows the input for code submission', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.startSignInFactorTwo();
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- render(, { wrapper });
-
- const inputs = screen.getAllByLabelText(/digit/i);
- expect(inputs.length).toBe(6);
- });
-
- it('correctly shows text indicating user need to complete 2FA to reset password', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.startSignInFactorTwo({
- supportResetPasswordEmail: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- render(, { wrapper });
-
- screen.getByText(/before resetting your password/i);
- });
-
- it('sets an active session when user submits second factor successfully', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.startSignInFactorTwo();
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- await runFakeTimers(async timers => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- timers.runOnlyPendingTimers();
- await waitFor(() => {
- expect(fixtures.clerk.setActive).toHaveBeenCalled();
- });
- });
- });
-
- it('redirects to reset-password-success after second factor successfully', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.startSignInFactorTwo({
- supportResetPasswordPhone: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({
- status: 'complete',
- firstFactorVerification: {
- status: 'verified',
- strategy: 'reset_password_phone_code',
- },
- createdSessionId: '1234_session_id',
- } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => {
- expect(fixtures.clerk.setActive).not.toHaveBeenCalled();
- expect(fixtures.router.navigate).toHaveBeenCalledWith(
- '../reset-password-success?createdSessionId=1234_session_id',
- );
- });
- });
- });
-
- describe('Selected Second Factor Method', () => {
- describe('Phone Code', () => {
- it('renders the correct screen with the text "Check your phone"', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- render(, { wrapper });
- screen.getByText('Check your phone');
- });
-
- // this is coming from the backend, so maybe we have nothing to test here
- it.todo('hides with * the phone number digits except the last 2');
-
- it('enables the "Resend code" button after 30 seconds', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
-
- runFakeTimers(timers => {
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { getByText } = render(, { wrapper });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- timers.advanceTimersByTime(15000);
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- getByText('(15)', { exact: false });
- timers.advanceTimersByTime(15000);
- expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
- });
- });
-
- it('disables again the resend code button after clicking it', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValue(Promise.resolve({} as SignInResource));
-
- await runFakeTimers(async timers => {
- const { getByText, userEvent } = render(, { wrapper });
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- timers.advanceTimersByTime(30000);
- expect(getByText(/Resend/).closest('button')).not.toHaveAttribute('disabled');
- await userEvent.click(getByText(/Resend/));
- timers.advanceTimersByTime(1000);
- expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled');
- });
- });
-
- it('auto submits when typing all the 6 digits of the code', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled();
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect phone code',
- message: 'is incorrect',
- meta: { param_name: 'code' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined());
- });
- }, 10000);
-
- it('redirects back to sign-in if the user is locked', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInWithPhoneNumber({ supportPhoneCode: true });
- f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
-
- const errJSON = {
- code: 'user_locked',
- long_message: 'Your account is locked. Please contact support for more information.',
- message: 'Account locked',
- };
-
- fixtures.signIn.attemptSecondFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [errJSON],
- status: 422,
- }),
- );
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON));
- });
- });
- });
-
- describe('Authenticator app', () => {
- it('renders the correct screen with the text "Authenticator app"', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({ supportPhoneCode: false, supportTotp: true });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { getByText } = render(, { wrapper });
- expect(getByText('Enter the verification code generated by your authenticator app')).toBeDefined();
- });
-
- it('auto submits when typing all the 6 digits of the code', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({ supportPhoneCode: false, supportTotp: true });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled();
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({ supportPhoneCode: false, supportTotp: true });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect authenticator code',
- message: 'is incorrect',
- meta: { param_name: 'code' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456');
- await waitFor(() => expect(screen.getByText('Incorrect authenticator code')).toBeDefined());
- });
- }, 10000);
- });
-
- describe('Backup code', () => {
- it('renders the correct screen with the text "Enter a backup code"', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({
- supportPhoneCode: false,
- supportBackupCode: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { getByText } = render(, { wrapper });
- expect(getByText('Enter a backup code')).toBeDefined();
- });
-
- it('submits the value when user clicks the continue button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({
- supportPhoneCode: false,
- supportBackupCode: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { getByText, getByLabelText, userEvent } = render(, { wrapper });
- await userEvent.type(getByLabelText('Backup code'), '123456');
- await userEvent.click(getByText('Continue'));
- expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled();
- });
-
- it('does not proceed when user clicks the continue button with password field empty', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({
- supportPhoneCode: false,
- supportBackupCode: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockReturnValueOnce(
- Promise.resolve({ status: 'complete' } as SignInResource),
- );
- const { getByText, userEvent } = render(, { wrapper });
-
- // type nothing in the input field
-
- await userEvent.click(getByText('Continue'));
- expect(fixtures.signIn.attemptSecondFactor).not.toHaveBeenCalled();
- });
-
- it('shows a UI error when submission fails', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({
- supportPhoneCode: false,
- supportBackupCode: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- fixtures.signIn.attemptSecondFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [
- {
- code: 'form_code_incorrect',
- long_message: 'Incorrect backup code',
- message: 'is incorrect',
- meta: { param_name: 'code' },
- },
- ],
- status: 422,
- }),
- );
- await runFakeTimers(async () => {
- const { userEvent, getByLabelText, getByText } = render(, { wrapper });
- await userEvent.type(getByLabelText('Backup code'), '123456');
- await userEvent.click(getByText('Continue'));
- await waitFor(() => expect(screen.getByText('Incorrect backup code')).toBeDefined());
- });
- }, 10000);
-
- it('redirects back to sign-in if the user is locked', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.withPreferredSignInStrategy({ strategy: 'otp' });
- f.startSignInFactorTwo({
- supportPhoneCode: false,
- supportBackupCode: true,
- });
- });
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
-
- const errJSON = {
- code: 'user_locked',
- long_message: 'Your account is locked. Please try again after 30 minutes.',
- message: 'Account locked',
- meta: { duration_in_seconds: 1800 },
- };
-
- fixtures.signIn.attemptSecondFactor.mockRejectedValueOnce(
- new ClerkAPIResponseError('Error', {
- data: [errJSON],
- status: 422,
- }),
- );
-
- await runFakeTimers(async () => {
- const { userEvent, getByLabelText, getByText } = render(, { wrapper });
- await userEvent.type(getByLabelText('Backup code'), '123456');
- await userEvent.click(getByText('Continue'));
- await waitFor(() => {
- expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON));
- });
- });
- });
- });
- });
-
- describe('Use another method', () => {
- it('renders the other authentication methods list component when clicking on "Use another method"', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- });
-
- it('goes back to the main screen when clicking the "<- Back" button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: false,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Back'));
- screen.getByText('Check your phone');
- });
-
- it('lists all the enabled second factor methods', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- screen.getByText(/Send SMS code to \+/i);
- screen.getByText(/Use a backup code/i);
- screen.getByText(/Authenticator/i);
- });
-
- it('shows the SMS code input when clicking the Phone code method', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText(/Send SMS code to \+/i));
- screen.getByText(/Check your phone/i);
- });
- it('shows the Authenticator app screen when clicking the Authenticator app method', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText(/authenticator/i));
- screen.getByText(/Enter the verification code/i);
- screen.getByText(/authenticator/i);
- });
-
- it('shows the Backup code screen when clicking the Backup code method', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText(/backup/i));
- screen.getByText(/enter a backup code/i);
- });
-
- describe('Get Help', () => {
- it('should render the get help component when clicking the "Get Help" button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- screen.getByText('Email support');
- });
-
- it('should go back to "Use another method" screen when clicking the "<- Back" button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- await userEvent.click(screen.getByText('Back'));
- screen.getByText('Use another method');
- });
-
- // this test needs us to mock the window.location.href to work properly
- it.skip('should open a "mailto:" link when clicking the email support button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withPassword();
- f.startSignInFactorTwo({
- supportPhoneCode: true,
- supportBackupCode: true,
- supportTotp: true,
- });
- });
-
- const { userEvent } = render(, { wrapper });
- await userEvent.click(screen.getByText('Use another method'));
- await userEvent.click(screen.getByText('Get help'));
- screen.getByText('Email support');
- await userEvent.click(screen.getByText('Email support'));
- //TODO: check that location.href setter is called
- });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInStart.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInStart.test.tsx
deleted file mode 100644
index 41c7df4682c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/SignInStart.test.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-import type { SignInResource } from '@clerk/types';
-import { OAUTH_PROVIDERS } from '@clerk/types';
-
-import { fireEvent, render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignInStart } from '../SignInStart';
-
-const { createFixtures } = bindCreateFixtures('SignIn');
-
-describe('SignInStart', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText('Sign in');
- });
-
- describe('Login Methods', () => {
- it('enables login with email address', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- });
-
- render(, { wrapper });
- screen.getByText(/email address/i);
- });
-
- it('enables login with username', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUsername();
- });
-
- render(, { wrapper });
- screen.getByText(/username/i);
- });
-
- it('enables login with phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber();
- });
- render(, { wrapper });
- screen.getByText('Phone number');
- });
-
- it('enables login with all three (email address, phone number, username)', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withUsername();
- f.withEmailAddress();
- });
- render(, { wrapper });
- screen.getByText(/email address or username/i);
- });
- });
-
- describe('Social OAuth', () => {
- it.each(OAUTH_PROVIDERS)('shows the "Continue with $name" social OAuth button', async ({ provider, name }) => {
- const { wrapper } = await createFixtures(f => {
- f.withSocialProvider({ provider });
- });
-
- render(, { wrapper });
-
- const socialOAuth = screen.getByText(`Continue with ${name}`);
- expect(socialOAuth).toBeDefined();
- });
-
- it('uses the "cl-socialButtonsIconButton__SOCIALOAUTHNAME" classname when rendering the social button icon only', async () => {
- const { wrapper } = await createFixtures(f => {
- OAUTH_PROVIDERS.forEach(({ provider }) => {
- f.withSocialProvider({ provider });
- });
- });
-
- const { container } = render(, { wrapper });
-
- // target the css classname as this is public API
- OAUTH_PROVIDERS.forEach(providerData => {
- const icon = container.getElementsByClassName(`cl-socialButtonsIconButton__${providerData.provider}`);
- expect(icon.length).toEqual(1);
- });
- });
- });
-
- describe('navigation', () => {
- it('calls create on clicking Continue button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- });
- fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_first_factor' } as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
- await userEvent.click(screen.getByText('Continue'));
- expect(fixtures.signIn.create).toHaveBeenCalled();
- });
-
- it('navigates to /factor-one page when user clicks on Continue button and create needs a first factor', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- });
- fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_first_factor' } as SignInResource));
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
- await userEvent.click(screen.getByText('Continue'));
- expect(fixtures.signIn.create).toHaveBeenCalled();
- expect(fixtures.router.navigate).toHaveBeenCalledWith('factor-one');
- });
-
- it('navigates to /factor-two page when user clicks on Continue button and create needs a second factor', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- });
- fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_second_factor' } as SignInResource));
- const { userEvent } = render(, { wrapper });
- expect(screen.getByText('Continue')).toBeInTheDocument();
- await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
- await userEvent.click(screen.getByText('Continue'));
- expect(fixtures.signIn.create).toHaveBeenCalled();
- expect(fixtures.router.navigate).toHaveBeenCalledWith('factor-two');
- });
- });
-
- describe('SAML', () => {
- it('initiates a SAML flow if saml is listed as a supported first factor', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- });
- fixtures.signIn.create.mockReturnValueOnce(
- Promise.resolve({
- status: 'needs_identifier',
- supportedFirstFactors: [{ strategy: 'saml' }],
- } as unknown as SignInResource),
- );
- const { userEvent } = render(, { wrapper });
- await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
- await userEvent.click(screen.getByText('Continue'));
- expect(fixtures.signIn.create).toHaveBeenCalled();
- expect(fixtures.signIn.authenticateWithRedirect).toHaveBeenCalledWith({
- strategy: 'saml',
- redirectUrl: 'http://localhost/#/sso-callback?redirect_url=http%3A%2F%2Flocalhost%2F',
- redirectUrlComplete: '/',
- });
- });
- });
-
- describe('Identifier switching', () => {
- it('shows the email label', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText(/email address/i);
- });
-
- it('shows the phone label', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText(/phone number/i);
- });
-
- it('prioritizes phone over username', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUsername();
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText(/phone number/i);
- });
-
- it('shows the use phone action', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress();
- f.withUsername();
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText(/use phone/i);
- });
-
- it('shows the use username action', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUsername();
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
- screen.getByText(/use username/i);
- });
-
- it('shows the username action upon clicking on use phone', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUsername();
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
- let button = screen.getByText(/use username/i);
- fireEvent.click(button);
-
- screen.getByText(/username/i);
-
- button = screen.getByText(/use phone/i);
- fireEvent.click(button);
-
- screen.getByText(/use username/i);
- });
-
- it('shows an input with type="tel" for the phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withSupportEmail();
- });
- render(, { wrapper });
-
- expect(screen.getByRole('textbox', { name: /phone number/i })).toHaveAttribute('type', 'tel');
- });
- });
-
- describe('initialValues', () => {
- it('prefills the emailAddress field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withEmailAddress();
- });
- props.setProps({ initialValues: { emailAddress: 'foo@clerk.com' } });
-
- render(, { wrapper });
- screen.getByDisplayValue(/foo@clerk.com/i);
- });
-
- it('prefills the phoneNumber field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withPhoneNumber();
- });
- props.setProps({ initialValues: { phoneNumber: '+306911111111' } });
-
- render(, { wrapper });
- screen.getByDisplayValue(/691 1111111/i);
- });
-
- it('prefills the username field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withUsername();
- });
-
- props.setProps({ initialValues: { username: 'foo' } });
- render(, { wrapper });
- screen.getByDisplayValue(/foo/i);
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/utils.test.ts b/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/utils.test.ts
deleted file mode 100644
index c6db79c0085..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/__tests__/utils.test.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import type { SignInResource } from '@clerk/types';
-
-import { determineSalutation, determineStartingSignInFactor } from '../utils';
-
-describe('determineStrategy(signIn, displayConfig)', () => {
- describe('with password as the preferred sign in strategy', () => {
- it('selects password if available', () => {
- const signIn = {
- supportedFirstFactors: [
- {
- strategy: 'password',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'password')).toEqual({
- strategy: 'password',
- });
- });
-
- it('selects based on user input in the previous step if password is not available', () => {
- const signIn = {
- identifier: 'jdoe@example.com',
- supportedFirstFactors: [
- {
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- },
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'password')).toEqual({
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- });
- });
-
- it('selects by prioritizing email_code if all the above fail', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- {
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'password')).toEqual({
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- });
- });
-
- it('selects phone_code if all the above fail', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'password')).toEqual({
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- });
- });
-
- it('returns null if every other scenario', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'password')).toBeNull();
- });
- });
-
- describe('with OTP as the preferred sign in strategy', () => {
- it('selects based on user input in the previous step', () => {
- const signIn = {
- identifier: 'jdoe@example.com',
- supportedFirstFactors: [
- {
- strategy: 'password',
- },
- {
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- },
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'otp')).toEqual({
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- });
- });
-
- it('selects by prioritizing email_code if the above fails', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [
- {
- strategy: 'password',
- },
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- {
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'otp')).toEqual({
- strategy: 'email_code',
- safeIdentifier: 'ccoe@example.com',
- });
- });
-
- it('selects phone_code if all the above fail', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [
- {
- strategy: 'password',
- },
- {
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'otp')).toEqual({
- strategy: 'phone_code',
- safeIdentifier: 'jdoe@example.com',
- });
- });
-
- it('selects password as a last resort if available', () => {
- const signIn = {
- supportedFirstFactors: [
- {
- strategy: 'password',
- },
- ],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'otp')).toEqual({
- strategy: 'password',
- });
- });
-
- it('returns null if every other scenario', () => {
- const signIn = {
- identifier: undefined,
- supportedFirstFactors: [],
- } as unknown as SignInResource;
- expect(determineStartingSignInFactor(signIn.supportedFirstFactors, signIn.identifier, 'otp')).toBeNull();
- });
- });
-
- describe('determineSalutation(signIn)', () => {
- it('returns firstname, then lastname or the identifier', () => {
- let signIn = {
- identifier: 'jdoe@example.com',
- userData: {
- firstName: 'Joe',
- lastName: 'Doe',
- },
- } as unknown as SignInResource;
- expect(determineSalutation(signIn)).toBe('Joe');
-
- signIn = {
- identifier: 'jdoe@example.com',
- userData: {
- lastName: 'Doe',
- },
- } as unknown as SignInResource;
- expect(determineSalutation(signIn)).toBe('Doe');
-
- signIn = {
- identifier: 'jdoe@example.com',
- } as unknown as SignInResource;
- expect(determineSalutation(signIn)).toBe('jdoe@example.com');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/index.ts b/packages/clerk-js/src/ui.retheme/components/SignIn/index.ts
deleted file mode 100644
index a573a358ee9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SignIn';
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.test.tsx
deleted file mode 100644
index 7883ee5de90..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.test.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { SignInFactor, SignInStrategy } from '@clerk/types';
-
-import {
- allStrategiesButtonsComparator,
- otpPrefFactorComparator,
- passwordPrefFactorComparator,
-} from './factorSortingUtils';
-
-describe('otpPrefFactorComparator(a,b)', function () {
- it('sorts an array of factors based on the otp pref sorter', function () {
- const factors: SignInFactor[] = [
- { strategy: 'password' },
- { strategy: 'password' },
- { strategy: 'email_code', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'phone_code', phoneNumberId: '', safeIdentifier: '' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- ];
-
- const expectedOrder: SignInStrategy[] = [
- 'email_link',
- 'email_link',
- 'email_code',
- 'phone_code',
- 'password',
- 'password',
- ];
-
- expect(factors.sort(otpPrefFactorComparator).map(f => f.strategy)).toEqual(expectedOrder);
- });
-});
-
-describe('passwordPrefFactorComparator(a,b)', function () {
- it('sorts an array of factors based on the password pref sorter', function () {
- const factors: SignInFactor[] = [
- { strategy: 'email_code', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'password' },
- { strategy: 'phone_code', phoneNumberId: '', safeIdentifier: '' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'password' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- ];
-
- const expectedOrder: SignInStrategy[] = [
- 'password',
- 'password',
- 'email_link',
- 'email_link',
- 'email_code',
- 'phone_code',
- ];
-
- expect(factors.sort(passwordPrefFactorComparator).map(f => f.strategy)).toEqual(expectedOrder);
- });
-});
-
-describe('allStrategiesButtonsComparator(a,b)', function () {
- it('sorts an array of factors based on the password pref sorter', function () {
- const factors: SignInFactor[] = [
- { strategy: 'email_code', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'password' },
- { strategy: 'phone_code', phoneNumberId: '', safeIdentifier: '' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- { strategy: 'password' },
- { strategy: 'email_link', emailAddressId: '', safeIdentifier: '' },
- ];
-
- const expectedOrder: SignInStrategy[] = [
- 'email_link',
- 'email_link',
- 'email_code',
- 'phone_code',
- 'password',
- 'password',
- ];
-
- expect(factors.sort(allStrategiesButtonsComparator).map(f => f.strategy)).toEqual(expectedOrder);
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.tsx
deleted file mode 100644
index 77373e2ce97..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/strategies/factorSortingUtils.tsx
+++ /dev/null
@@ -1,43 +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 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 allStrategiesButtonsComparator = makeSortingFunction(STRATEGY_SORT_ORDER_ALL_STRATEGIES_BUTTONS);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/useResetPasswordFactor.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/useResetPasswordFactor.tsx
deleted file mode 100644
index d1387bc630f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/useResetPasswordFactor.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { ResetPasswordCodeFactor } from '@clerk/types';
-
-import { useCoreSignIn } from '../../contexts';
-import { isResetPasswordStrategy } from './utils';
-
-export function useResetPasswordFactor() {
- const signIn = useCoreSignIn();
-
- return signIn.supportedFirstFactors.find(({ strategy }) => isResetPasswordStrategy(strategy)) as
- | ResetPasswordCodeFactor
- | undefined;
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/utils.ts b/packages/clerk-js/src/ui.retheme/components/SignIn/utils.ts
deleted file mode 100644
index 18b29c1beac..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/utils.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { titleize } from '@clerk/shared';
-import type { PreferredSignInStrategy, SignInFactor, SignInResource, SignInStrategy } from '@clerk/types';
-
-import { PREFERRED_SIGN_IN_STRATEGIES } from '../../common/constants';
-import { otpPrefFactorComparator, passwordPrefFactorComparator } from './strategies/factorSortingUtils';
-
-const FONT_SIZE_STEP = 2;
-
-// creates a hidden element and returns what the text's width
-// would be if it were rendered inside the parent
-function textWidthForCurrentSize(text: string, parent: HTMLElement) {
- const hiddenTextContainer = document.createElement('div');
- hiddenTextContainer.style.position = 'absolute';
- hiddenTextContainer.style.left = '-99in';
- hiddenTextContainer.style.whiteSpace = 'nowrap';
- hiddenTextContainer.innerHTML = text;
-
- parent.appendChild(hiddenTextContainer);
- const result = hiddenTextContainer.clientWidth;
- parent.removeChild(hiddenTextContainer);
- return result;
-}
-
-export function fitTextInOneLine(text: string, containerEl: HTMLElement, defaultSize: string): void {
- const getContainerFontSize = () => window.getComputedStyle(containerEl).getPropertyValue('font-size');
- const decreaseSize = () => {
- const fontSizeWithUnit = getContainerFontSize();
- const newSize = (Number.parseInt(fontSizeWithUnit) - FONT_SIZE_STEP) * 0.85;
- containerEl.style.fontSize = `${newSize}px`;
- };
- const increaseSize = () => {
- const fontSizeWithUnit = getContainerFontSize();
- const newSize = Number.parseInt(fontSizeWithUnit) + FONT_SIZE_STEP / 2;
- containerEl.style.fontSize = `${newSize}px`;
- };
-
- containerEl.style.fontSize = defaultSize;
- while (textWidthForCurrentSize(text, containerEl) > containerEl.clientWidth) {
- decreaseSize();
- }
-
- if (
- getContainerFontSize() >= defaultSize ||
- textWidthForCurrentSize(text, containerEl) > containerEl.clientWidth * 0.75
- ) {
- return;
- }
-
- while (textWidthForCurrentSize(text, containerEl) < containerEl.clientWidth) {
- increaseSize();
- }
-}
-
-const factorForIdentifier = (i: string | null) => (f: SignInFactor) => {
- return 'safeIdentifier' in f && f.safeIdentifier === i;
-};
-
-function determineStrategyWhenPasswordIsPreferred(
- factors: SignInFactor[],
- identifier: string | null,
-): SignInFactor | null {
- const selected = factors.sort(passwordPrefFactorComparator)[0];
- if (selected.strategy === 'password') {
- return selected;
- }
- return factors.find(factorForIdentifier(identifier)) || selected || null;
-}
-
-function determineStrategyWhenOTPIsPreferred(factors: SignInFactor[], identifier: string | null): SignInFactor | null {
- const sortedBasedOnPrefFactor = factors.sort(otpPrefFactorComparator);
- const forIdentifier = sortedBasedOnPrefFactor.find(factorForIdentifier(identifier));
- if (forIdentifier) {
- return forIdentifier;
- }
- const firstBasedOnPref = sortedBasedOnPrefFactor[0];
- if (firstBasedOnPref.strategy === 'email_link') {
- return firstBasedOnPref;
- }
- return factors.find(factorForIdentifier(identifier)) || firstBasedOnPref || null;
-}
-
-// The algorithm can be found at
-// https://www.notion.so/clerkdev/Implement-sign-in-alt-methods-e6e60ffb644645b3a0553b50556468ce
-export function determineStartingSignInFactor(
- firstFactors: SignInFactor[],
- identifier: string | null,
- preferredSignInStrategy: PreferredSignInStrategy,
-): SignInFactor | null | undefined {
- if (!firstFactors || firstFactors.length === 0) {
- return null;
- }
-
- return preferredSignInStrategy === PREFERRED_SIGN_IN_STRATEGIES.Password
- ? determineStrategyWhenPasswordIsPreferred(firstFactors, identifier)
- : determineStrategyWhenOTPIsPreferred(firstFactors, identifier);
-}
-
-export function determineSalutation(signIn: Partial): string {
- if (!signIn) {
- return '';
- }
-
- return titleize(signIn.userData?.firstName) || titleize(signIn.userData?.lastName) || signIn?.identifier || '';
-}
-
-const localStrategies: SignInStrategy[] = ['email_code', 'password', 'phone_code', 'email_link'];
-
-export function factorHasLocalStrategy(factor: SignInFactor | undefined | null): boolean {
- if (!factor) {
- return false;
- }
- return localStrategies.includes(factor.strategy);
-}
-
-// The priority of second factors is: TOTP -> Phone code -> any other factor
-export function determineStartingSignInSecondFactor(secondFactors: SignInFactor[]): SignInFactor | null {
- if (!secondFactors || secondFactors.length === 0) {
- return null;
- }
-
- const totpFactor = secondFactors.find(f => f.strategy === 'totp');
- if (totpFactor) {
- return totpFactor;
- }
-
- const phoneCodeFactor = secondFactors.find(f => f.strategy === 'phone_code');
- if (phoneCodeFactor) {
- return phoneCodeFactor;
- }
-
- return secondFactors[0];
-}
-
-const resetPasswordStrategies: SignInStrategy[] = ['reset_password_phone_code', 'reset_password_email_code'];
-export const isResetPasswordStrategy = (strategy: SignInStrategy | string | null | undefined) =>
- !!strategy && resetPasswordStrategies.includes(strategy as SignInStrategy);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignIn/withHavingTrouble.tsx b/packages/clerk-js/src/ui.retheme/components/SignIn/withHavingTrouble.tsx
deleted file mode 100644
index 84af28aa3b5..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignIn/withHavingTrouble.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-
-import type { AlternativeMethodsProps } from './AlternativeMethods';
-import { HavingTrouble } from './HavingTrouble';
-
-export const withHavingTrouble = (
- Component: React.ComponentType
,
- props: AlternativeMethodsProps,
-) => {
- const [showHavingTrouble, setShowHavingTrouble] = React.useState(false);
- const toggleHavingTrouble = React.useCallback(() => setShowHavingTrouble(s => !s), [setShowHavingTrouble]);
-
- if (showHavingTrouble) {
- return ;
- }
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUp.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUp.tsx
deleted file mode 100644
index 9d056c50735..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUp.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { SignUpModalProps, SignUpProps } from '@clerk/types';
-import React from 'react';
-
-import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
-import { ComponentContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts';
-import { Flow } from '../../customizables';
-import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
-import { SignUpContinue } from './SignUpContinue';
-import { SignUpSSOCallback } from './SignUpSSOCallback';
-import { SignUpStart } from './SignUpStart';
-import { SignUpVerifyEmail } from './SignUpVerifyEmail';
-import { SignUpVerifyPhone } from './SignUpVerifyPhone';
-
-function RedirectToSignUp() {
- const clerk = useClerk();
- React.useEffect(() => {
- void clerk.redirectToSignUp();
- }, []);
- return null;
-}
-
-function SignUpRoutes(): JSX.Element {
- const signUpContext = useSignUpContext();
-
- return (
-
-
- !!clerk.client.signUp.emailAddress}
- >
-
-
- !!clerk.client.signUp.phoneNumber}
- >
-
-
-
-
-
-
-
-
-
- !!clerk.client.signUp.emailAddress}
- >
-
-
- !!clerk.client.signUp.phoneNumber}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-SignUpRoutes.displayName = 'SignUp';
-
-export const SignUp: React.ComponentType = withCoreSessionSwitchGuard(SignUpRoutes);
-
-export const SignUpModal = (props: SignUpModalProps): JSX.Element => {
- const signUpProps = {
- signInUrl: `/${VIRTUAL_ROUTER_BASE_PATH}/sign-in`,
- ...props,
- };
-
- return (
-
-
- {/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpContinue.tsx
deleted file mode 100644
index 9a37eb6e776..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpContinue.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import React from 'react';
-
-import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
-import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
-import {
- Card,
- CardAlert,
- Footer,
- Header,
- LoadingCard,
- SocialButtonsReversibleContainerWithDivider,
- withCardStateProvider,
-} from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { useRouter } from '../../router';
-import type { FormControlState } from '../../utils';
-import { buildRequest, handleError, useFormControl } from '../../utils';
-import { SignUpForm } from './SignUpForm';
-import type { ActiveIdentifier } from './signUpFormHelpers';
-import {
- determineActiveFields,
- emailOrPhone,
- getInitialActiveIdentifier,
- minimizeFieldsForExistingSignup,
-} from './signUpFormHelpers';
-import { SignUpSocialButtons } from './SignUpSocialButtons';
-import { completeSignUpFlow } from './util';
-
-function _SignUpContinue() {
- const card = useCardState();
- const clerk = useClerk();
- const { navigate } = useRouter();
- const { displayConfig, userSettings } = useEnvironment();
- const { attributes } = userSettings;
- const { navigateAfterSignUp, signInUrl, unsafeMetadata, initialValues = {} } = useSignUpContext();
- const signUp = useCoreSignUp();
- const isProgressiveSignUp = userSettings.signUp.progressive;
- const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
- getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
- );
-
- // Redirect to sign-up if there is no persisted sign-up
- if (!signUp.id) {
- void navigate(displayConfig.signUpUrl);
- return ;
- }
-
- // TODO: This form should be shared between SignUpStart and SignUpContinue
- const formState = {
- firstName: useFormControl('firstName', initialValues.firstName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__firstName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__firstName'),
- }),
- lastName: useFormControl('lastName', initialValues.lastName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__lastName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__lastName'),
- }),
- emailAddress: useFormControl('emailAddress', initialValues.emailAddress || '', {
- type: 'email',
- label: localizationKeys('formFieldLabel__emailAddress'),
- placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress'),
- }),
- username: useFormControl('username', initialValues.username || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__username'),
- placeholder: localizationKeys('formFieldInputPlaceholder__username'),
- }),
- phoneNumber: useFormControl('phoneNumber', initialValues.phoneNumber || '', {
- type: 'tel',
- label: localizationKeys('formFieldLabel__phoneNumber'),
- placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'),
- }),
- password: useFormControl('password', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__password'),
- placeholder: localizationKeys('formFieldInputPlaceholder__password'),
- validatePassword: true,
- }),
- } as const;
-
- const hasEmail = !!formState.emailAddress.value;
- const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status == 'verified';
- const hasVerifiedWeb3 = signUp.verifications?.web3Wallet?.status == 'verified';
-
- const fields = determineActiveFields({
- attributes,
- hasEmail,
- activeCommIdentifierType,
- signUp,
- isProgressiveSignUp,
- });
- minimizeFieldsForExistingSignup(fields, signUp);
-
- const oauthOptions = userSettings.authenticatableSocialStrategies;
- const web3Options = userSettings.web3FirstFactors;
-
- const handleChangeActive = (type: ActiveIdentifier) => {
- if (!emailOrPhone(attributes, isProgressiveSignUp)) {
- return;
- }
- setActiveCommIdentifierType(type);
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- type FormStateKey = keyof typeof formState;
- const fieldsToSubmit = Object.entries(fields).reduce(
- (acc, [k, v]) => [...acc, ...(v && formState[k as FormStateKey] ? [formState[k as FormStateKey]] : [])],
- [] as Array,
- );
-
- if (unsafeMetadata) {
- fieldsToSubmit.push({ id: 'unsafeMetadata', value: unsafeMetadata } as any);
- }
-
- // Add both email & phone to the submitted fields to trigger and render an error for both respective inputs in
- // case all the below requirements are met:
- // 1. Sign up contains both in the missing fields
- // 2. The instance settings has both email & phone as optional (emailOrPhone)
- // 3. Neither of them is provided
- const emailAddressProvided = !!(fieldsToSubmit.find(f => f.id === 'emailAddress')?.value || '');
- const phoneNumberProvided = !!(fieldsToSubmit.find(f => f.id === 'phoneNumber')?.value || '');
- const emailOrPhoneMissing =
- signUp.missingFields.includes('email_address') && signUp.missingFields.includes('phone_number');
-
- if (
- emailOrPhoneMissing &&
- !emailAddressProvided &&
- !phoneNumberProvided &&
- emailOrPhone(attributes, isProgressiveSignUp)
- ) {
- fieldsToSubmit.push(formState['emailAddress']);
- fieldsToSubmit.push(formState['phoneNumber']);
- }
-
- card.setLoading();
- card.setError(undefined);
- return signUp
- .update(buildRequest(fieldsToSubmit))
- .then(res =>
- completeSignUpFlow({
- signUp: res,
- verifyEmailPath: './verify-email-address',
- verifyPhonePath: './verify-phone-number',
- handleComplete: () => clerk.setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignUp }),
- navigate,
- }),
- )
- .catch(err => handleError(err, fieldsToSubmit, card.setError))
- .finally(() => card.setIdle());
- };
-
- const canToggleEmailPhone = emailOrPhone(attributes, isProgressiveSignUp);
- const showOauthProviders = !hasVerifiedExternalAccount && oauthOptions.length > 0;
- const showWeb3Providers = !hasVerifiedWeb3 && web3Options.length > 0;
-
- return (
-
-
- {card.error}
-
-
-
-
-
-
- {(showOauthProviders || showWeb3Providers) && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-// TODO: flow / page naming
-export const SignUpContinue = withCardStateProvider(_SignUpContinue);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailCodeCard.tsx
deleted file mode 100644
index 63d5ba5ad5a..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailCodeCard.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-
-import { useCoreSignUp } from '../../contexts';
-import { Flow, localizationKeys } from '../../customizables';
-import { SignUpVerificationCodeForm } from './SignUpVerificationCodeForm';
-
-export const SignUpEmailCodeCard = () => {
- const signUp = useCoreSignUp();
-
- React.useEffect(() => {
- // TODO: This prepare method is not idempotent.
- // We need to make sure that R18 won't trigger this twice
- void prepare();
- }, []);
-
- const prepare = () => {
- const emailVerificationStatus = signUp.verifications.emailAddress.status;
- if (!signUp.status || emailVerificationStatus === 'verified') {
- return;
- }
- return signUp.prepareEmailAddressVerification({ strategy: 'email_code' });
- };
-
- const attempt = (code: string) => signUp.attemptEmailAddressVerification({ code });
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailLinkCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailLinkCard.tsx
deleted file mode 100644
index fbdd1d4eb2b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpEmailLinkCard.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { SignUpResource } from '@clerk/types';
-import React from 'react';
-
-import { EmailLinkStatusCard } from '../../common';
-import { buildEmailLinkRedirectUrl } from '../../common/redirects';
-import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
-import { Flow, localizationKeys, useLocalizations } from '../../customizables';
-import { VerificationLinkCard } from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { useEmailLink } from '../../hooks/useEmailLink';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-import { completeSignUpFlow } from './util';
-
-export const SignUpEmailLinkCard = () => {
- const { t } = useLocalizations();
- const signUp = useCoreSignUp();
- const signUpContext = useSignUpContext();
- const { navigateAfterSignUp } = signUpContext;
- const card = useCardState();
- const { displayConfig } = useEnvironment();
- const { navigate } = useRouter();
- const { setActive } = useClerk();
- const [showVerifyModal, setShowVerifyModal] = React.useState(false);
-
- const { startEmailLinkFlow, cancelEmailLinkFlow } = useEmailLink(signUp);
-
- React.useEffect(() => {
- void startEmailLinkVerification();
- }, []);
-
- const restartVerification = () => {
- cancelEmailLinkFlow();
- void startEmailLinkVerification();
- };
-
- const startEmailLinkVerification = () => {
- return startEmailLinkFlow({ redirectUrl: buildEmailLinkRedirectUrl(signUpContext, displayConfig.signUpUrl) })
- .then(res => handleVerificationResult(res))
- .catch(err => {
- handleError(err, [], card.setError);
- });
- };
-
- const handleVerificationResult = async (su: SignUpResource) => {
- const ver = su.verifications.emailAddress;
- if (ver.status === 'expired') {
- card.setError(t(localizationKeys('formFieldError__verificationLinkExpired')));
- } else if (ver.verifiedFromTheSameClient()) {
- setShowVerifyModal(true);
- } else {
- await completeSignUpFlow({
- signUp: su,
- verifyEmailPath: '../verify-email-address',
- verifyPhonePath: '../verify-phone-number',
- handleComplete: () => setActive({ session: su.createdSessionId, beforeEmit: navigateAfterSignUp }),
- navigate,
- });
- }
- };
-
- if (showVerifyModal) {
- return (
-
- );
- }
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpForm.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpForm.tsx
deleted file mode 100644
index aa17253e7bc..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpForm.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React from 'react';
-
-import { useAppearance } from '../../customizables';
-import { Form } from '../../elements';
-import type { FormControlState } from '../../utils';
-import type { ActiveIdentifier, Fields } from './signUpFormHelpers';
-
-type SignUpFormProps = {
- handleSubmit: React.FormEventHandler;
- fields: Fields;
- formState: Record, FormControlState>;
- canToggleEmailPhone: boolean;
- handleEmailPhoneToggle: (type: ActiveIdentifier) => void;
-};
-
-export const SignUpForm = (props: SignUpFormProps) => {
- const { handleSubmit, fields, formState, canToggleEmailPhone, handleEmailPhoneToggle } = props;
- const { showOptionalFields } = useAppearance().parsedLayout;
-
- const shouldShow = (name: keyof typeof fields) => {
- // In case both email & phone are optional, then don't take into account the
- // Layout showOptionalFields prop and the required field.
- if ((name === 'emailAddress' || name === 'phoneNumber') && canToggleEmailPhone) {
- return !!fields[name];
- }
-
- return !!fields[name] && (showOptionalFields || fields[name]?.required);
- };
-
- return (
-
- {(shouldShow('firstName') || shouldShow('lastName')) && (
-
- {shouldShow('firstName') && (
-
- )}
- {shouldShow('lastName') && (
-
- )}
-
- )}
- {shouldShow('username') && (
-
-
-
- )}
- {shouldShow('emailAddress') && (
-
- handleEmailPhoneToggle('phoneNumber') : undefined}
- />
-
- )}
- {shouldShow('phoneNumber') && (
-
- handleEmailPhoneToggle('emailAddress') : undefined}
- />
-
- )}
- {shouldShow('password') && (
-
-
-
- )}
- Continue
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpPhoneCodeCard.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpPhoneCodeCard.tsx
deleted file mode 100644
index 46407f4bee8..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpPhoneCodeCard.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react';
-
-import { useCoreSignUp } from '../../contexts';
-import { Flow, localizationKeys } from '../../customizables';
-import { withCardStateProvider } from '../../elements';
-import { SignUpVerificationCodeForm } from './SignUpVerificationCodeForm';
-
-export const SignUpPhoneCodeCard = withCardStateProvider(() => {
- const signUp = useCoreSignUp();
-
- React.useEffect(() => {
- // TODO: This prepare method is not idempotent.
- // We need to make sure that R18 won't trigger this twice
- void prepare();
- }, []);
-
- const prepare = () => {
- const phoneVerificationStatus = signUp.verifications.phoneNumber.status;
- if (!signUp.status || phoneVerificationStatus === 'verified') {
- return;
- }
- return signUp.preparePhoneNumberVerification({ strategy: 'phone_code' });
- };
-
- const attempt = (code: string) => signUp.attemptPhoneNumberVerification({ code });
-
- return (
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSSOCallback.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSSOCallback.tsx
deleted file mode 100644
index a17cff10c54..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSSOCallback.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { SSOCallback, withRedirectToHomeSingleSessionGuard } from '../../common';
-
-export const SignUpSSOCallback = withRedirectToHomeSingleSessionGuard(SSOCallback);
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSocialButtons.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSocialButtons.tsx
deleted file mode 100644
index 4dd84f36039..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpSocialButtons.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { OAuthStrategy } from '@clerk/types';
-import React from 'react';
-
-import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignUp, useSignUpContext } from '../../contexts';
-import { useEnvironment } from '../../contexts/EnvironmentContext';
-import { useCardState } from '../../elements';
-import type { SocialButtonsProps } from '../../elements/SocialButtons';
-import { SocialButtons } from '../../elements/SocialButtons';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-
-export type SignUpSocialButtonsProps = SocialButtonsProps & { continueSignUp?: boolean };
-
-export const SignUpSocialButtons = React.memo((props: SignUpSocialButtonsProps) => {
- const clerk = useClerk();
- const { navigate } = useRouter();
- const card = useCardState();
- const { displayConfig } = useEnvironment();
- const ctx = useSignUpContext();
- const signUp = useCoreSignUp();
- const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signUpUrl);
- const redirectUrlComplete = ctx.afterSignUpUrl || '/';
- const { continueSignUp = false, ...rest } = props;
-
- return (
- {
- return signUp
- .authenticateWithRedirect({
- continueSignUp,
- redirectUrl,
- redirectUrlComplete,
- strategy,
- unsafeMetadata: ctx.unsafeMetadata,
- })
- .catch(err => handleError(err, [], card.setError));
- }}
- web3Callback={() => {
- return clerk
- .authenticateWithMetamask({
- customNavigate: navigate,
- redirectUrl: redirectUrlComplete,
- signUpContinueUrl: 'continue',
- unsafeMetadata: ctx.unsafeMetadata,
- })
- .catch(err => handleError(err, [], card.setError));
- }}
- />
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpStart.tsx
deleted file mode 100644
index 79fafd6967c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpStart.tsx
+++ /dev/null
@@ -1,292 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import React from 'react';
-
-import { ERROR_CODES } from '../../../core/constants';
-import { getClerkQueryParam } from '../../../utils/getClerkQueryParam';
-import { buildSSOCallbackURL, withRedirectToHomeSingleSessionGuard } from '../../common';
-import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
-import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables';
-import {
- Card,
- CardAlert,
- Footer,
- Header,
- LoadingCard,
- SocialButtonsReversibleContainerWithDivider,
- withCardStateProvider,
-} from '../../elements';
-import { useCardState } from '../../elements/contexts';
-import { useLoadingStatus } from '../../hooks';
-import { useRouter } from '../../router';
-import type { FormControlState } from '../../utils';
-import { buildRequest, createPasswordError, handleError, useFormControl } from '../../utils';
-import { SignUpForm } from './SignUpForm';
-import type { ActiveIdentifier } from './signUpFormHelpers';
-import { determineActiveFields, emailOrPhone, getInitialActiveIdentifier, showFormFields } from './signUpFormHelpers';
-import { SignUpSocialButtons } from './SignUpSocialButtons';
-import { completeSignUpFlow } from './util';
-
-function _SignUpStart(): JSX.Element {
- const card = useCardState();
- const clerk = useClerk();
- const status = useLoadingStatus();
- const signUp = useCoreSignUp();
- const { showOptionalFields } = useAppearance().parsedLayout;
- const { userSettings, displayConfig } = useEnvironment();
- const { navigate } = useRouter();
- const { attributes } = userSettings;
- const { setActive } = useClerk();
- const ctx = useSignUpContext();
- const { navigateAfterSignUp, signInUrl, unsafeMetadata } = ctx;
- const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
- getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
- );
- const { t, locale } = useLocalizations();
- const initialValues = ctx.initialValues || {};
-
- const [missingRequirementsWithTicket, setMissingRequirementsWithTicket] = React.useState(false);
-
- const {
- userSettings: { passwordSettings },
- } = useEnvironment();
-
- const formState = {
- firstName: useFormControl('firstName', signUp.firstName || initialValues.firstName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__firstName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__firstName'),
- }),
- lastName: useFormControl('lastName', signUp.lastName || initialValues.lastName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__lastName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__lastName'),
- }),
- emailAddress: useFormControl('emailAddress', signUp.emailAddress || initialValues.emailAddress || '', {
- type: 'email',
- label: localizationKeys('formFieldLabel__emailAddress'),
- placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress'),
- }),
- username: useFormControl('username', signUp.username || initialValues.username || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__username'),
- placeholder: localizationKeys('formFieldInputPlaceholder__username'),
- }),
- phoneNumber: useFormControl('phoneNumber', signUp.phoneNumber || initialValues.phoneNumber || '', {
- type: 'tel',
- label: localizationKeys('formFieldLabel__phoneNumber'),
- placeholder: localizationKeys('formFieldInputPlaceholder__phoneNumber'),
- }),
- password: useFormControl('password', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__password'),
- placeholder: localizationKeys('formFieldInputPlaceholder__password'),
- validatePassword: true,
- buildErrorMessage: errors => createPasswordError(errors, { t, locale, passwordSettings }),
- }),
- ticket: useFormControl(
- 'ticket',
- getClerkQueryParam('__clerk_ticket') || getClerkQueryParam('__clerk_invitation_token') || '',
- ),
- } as const;
-
- const hasTicket = !!formState.ticket.value;
- const hasEmail = !!formState.emailAddress.value;
- const isProgressiveSignUp = userSettings.signUp.progressive;
-
- const fields = determineActiveFields({
- attributes,
- hasTicket,
- hasEmail,
- activeCommIdentifierType,
- isProgressiveSignUp,
- });
-
- const handleTokenFlow = () => {
- if (!formState.ticket.value) {
- return;
- }
- status.setLoading();
- card.setLoading();
- signUp
- .create({ strategy: 'ticket', ticket: formState.ticket.value })
- .then(signUp => {
- formState.emailAddress.setValue(signUp.emailAddress || '');
- // In case we are in a Ticket flow and the sign up is not complete yet, update the state
- // to render properly the SignUp form with other available options to complete it (e.g. OAuth)
- if (signUp.status === 'missing_requirements') {
- setMissingRequirementsWithTicket(true);
- }
-
- return completeSignUpFlow({
- signUp,
- verifyEmailPath: 'verify-email-address',
- verifyPhonePath: 'verify-phone-number',
- handleComplete: () => setActive({ session: signUp.createdSessionId, beforeEmit: navigateAfterSignUp }),
- navigate,
- });
- })
- .catch(err => {
- /* Clear ticket values when an error occurs in the initial sign up attempt */
- formState.ticket.setValue('');
- handleError(err, [], card.setError);
- })
- .finally(() => {
- status.setIdle();
- card.setIdle();
- });
- };
-
- React.useLayoutEffect(() => {
- void handleTokenFlow();
- }, []);
-
- React.useEffect(() => {
- async function handleOauthError() {
- const error = signUp.verifications.externalAccount.error;
-
- if (error) {
- switch (error.code) {
- case ERROR_CODES.NOT_ALLOWED_TO_SIGN_UP:
- case ERROR_CODES.OAUTH_ACCESS_DENIED:
- case ERROR_CODES.NOT_ALLOWED_ACCESS:
- case ERROR_CODES.SAML_USER_ATTRIBUTE_MISSING:
- case ERROR_CODES.OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML:
- case ERROR_CODES.USER_LOCKED:
- card.setError(error.longMessage);
- break;
- default:
- // Error from server may be too much information for the end user, so set a generic error
- card.setError('Unable to complete action at this time. If the problem persists please contact support.');
- }
-
- // TODO: This is a hack to reset the sign in attempt so that the oauth error
- // does not persist on full page reloads.
- // We will revise this strategy as part of the Clerk DX epic.
- void (await signUp.create({}));
- }
- }
-
- void handleOauthError();
- }, []);
-
- const handleChangeActive = (type: ActiveIdentifier) => {
- if (!emailOrPhone(attributes, isProgressiveSignUp)) {
- return;
- }
- setActiveCommIdentifierType(type);
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- type FormStateKey = keyof typeof formState;
- const fieldsToSubmit = Object.entries(fields).reduce(
- (acc, [k, v]) => [...acc, ...(v && formState[k as FormStateKey] ? [formState[k as FormStateKey]] : [])],
- [] as Array,
- );
-
- if (unsafeMetadata) {
- fieldsToSubmit.push({ id: 'unsafeMetadata', value: unsafeMetadata } as any);
- }
-
- if (fields.ticket) {
- const noop = () => {
- //
- };
- // fieldsToSubmit: Constructing a fake fields object for strategy.
- fieldsToSubmit.push({ id: 'strategy', value: 'ticket', setValue: noop, onChange: noop, setError: noop } as any);
- }
-
- // In case of emailOrPhone (both email & phone are optional) and neither of them is provided,
- // add both to the submitted fields to trigger and render an error for both respective inputs
- const emailAddressProvided = !!(fieldsToSubmit.find(f => f.id === 'emailAddress')?.value || '');
- const phoneNumberProvided = !!(fieldsToSubmit.find(f => f.id === 'phoneNumber')?.value || '');
-
- if (!emailAddressProvided && !phoneNumberProvided && emailOrPhone(attributes, isProgressiveSignUp)) {
- fieldsToSubmit.push(formState['emailAddress']);
- fieldsToSubmit.push(formState['phoneNumber']);
- }
-
- card.setLoading();
- card.setError(undefined);
-
- const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signUpUrl);
- const redirectUrlComplete = ctx.afterSignUpUrl || '/';
-
- return signUp
- .create(buildRequest(fieldsToSubmit))
- .then(res =>
- completeSignUpFlow({
- signUp: res,
- verifyEmailPath: 'verify-email-address',
- verifyPhonePath: 'verify-phone-number',
- handleComplete: () => setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignUp }),
- navigate,
- redirectUrl,
- redirectUrlComplete,
- }),
- )
- .catch(err => handleError(err, fieldsToSubmit, card.setError))
- .finally(() => card.setIdle());
- };
-
- if (status.isLoading) {
- return ;
- }
-
- const canToggleEmailPhone = emailOrPhone(attributes, isProgressiveSignUp);
- const visibleFields = Object.entries(fields).filter(([_, opts]) => showOptionalFields || opts?.required);
- const shouldShowForm = showFormFields(userSettings) && visibleFields.length > 0;
-
- const showOauthProviders =
- (!hasTicket || missingRequirementsWithTicket) && userSettings.authenticatableSocialStrategies.length > 0;
- const showWeb3Providers = !hasTicket && userSettings.web3FirstFactors.length > 0;
-
- return (
-
-
- {card.error}
-
-
-
-
-
-
- {(showOauthProviders || showWeb3Providers) && (
-
- )}
- {shouldShowForm && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export const SignUpStart = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignUpStart));
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerificationCodeForm.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerificationCodeForm.tsx
deleted file mode 100644
index 8970ba004ff..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerificationCodeForm.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import type { SignUpResource } from '@clerk/types';
-
-import { useSignUpContext } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
-import type { VerificationCodeCardProps } from '../../elements';
-import { VerificationCodeCard } from '../../elements';
-import { useRouter } from '../../router';
-import { completeSignUpFlow } from './util';
-
-type SignInFactorOneCodeFormProps = {
- cardTitle: LocalizationKey;
- cardSubtitle: LocalizationKey;
- formTitle: LocalizationKey;
- formSubtitle: LocalizationKey;
- resendButton: LocalizationKey;
- prepare: () => Promise | undefined;
- attempt: (code: string) => Promise;
- safeIdentifier?: string | undefined | null;
-};
-
-export const SignUpVerificationCodeForm = (props: SignInFactorOneCodeFormProps) => {
- const { navigateAfterSignUp } = useSignUpContext();
- const { setActive } = useClerk();
- const { navigate } = useRouter();
-
- const goBack = () => {
- return navigate('../');
- };
-
- const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
- void props
- .attempt(code)
- .then(async res => {
- await resolve();
- return completeSignUpFlow({
- signUp: res,
- verifyEmailPath: '../verify-email-address',
- verifyPhonePath: '../verify-phone-number',
- handleComplete: () => setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignUp }),
- navigate,
- });
- })
- .catch(err => {
- // TODO: Check if this is enough
- return reject(err);
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyEmail.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyEmail.tsx
deleted file mode 100644
index 4057763d3e0..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyEmail.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useEnvironment } from '../../contexts';
-import { withCardStateProvider } from '../../elements';
-import { SignUpEmailCodeCard } from './SignUpEmailCodeCard';
-import { SignUpEmailLinkCard } from './SignUpEmailLinkCard';
-
-export const SignUpVerifyEmail = withCardStateProvider(() => {
- const { userSettings } = useEnvironment();
- const { attributes } = userSettings;
- const emailLinkStrategyEnabled = attributes.email_address.verifications.includes('email_link');
-
- if (emailLinkStrategyEnabled) {
- return ;
- }
-
- return ;
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyPhone.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyPhone.tsx
deleted file mode 100644
index d9bec7a0885..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/SignUpVerifyPhone.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { SignUpPhoneCodeCard } from './SignUpPhoneCodeCard';
-
-export const SignUpVerifyPhone = () => {
- return ;
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpContinue.test.tsx
deleted file mode 100644
index 4736bdfdad1..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpContinue.test.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { OAUTH_PROVIDERS } from '@clerk/types';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignUpContinue } from '../SignUpContinue';
-
-const { createFixtures } = bindCreateFixtures('SignUp');
-
-describe('SignUpContinue', () => {
- it('renders the component if there is a persisted sign up', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- f.startSignUpWithEmailAddress();
- });
- render(, { wrapper });
- screen.getByText(/missing/i);
- });
-
- it('does not render the form if there is not a persisted sign up', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- expect(screen.queryByText(/missing/i)).toBeNull();
- });
-
- it('navigates to the sign up page if there is not a persisted sign up', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.signUpUrl);
- });
-
- it('shows the fields for the sign up', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- f.startSignUpWithEmailAddress();
- });
- render(, { wrapper });
- // Because the email address is already set, it should not be shown,
- // as we're in PSU mode and it's not a missing field.
- expect(screen.queryByText(/email address/i)).not.toBeInTheDocument();
- screen.getByText(/password/i);
- });
-
- it('shows the continue button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- f.startSignUpWithEmailAddress();
- });
- render(, { wrapper });
- const button = screen.getByText('Continue');
- expect(button.tagName.toUpperCase()).toBe('BUTTON');
- });
-
- it.each(OAUTH_PROVIDERS)('shows the "Continue with $name" social OAuth button', async ({ provider, name }) => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- f.startSignUpWithEmailAddress();
- f.withSocialProvider({ provider });
- });
-
- render(, { wrapper });
- screen.getByText(`Continue with ${name}`);
- });
-
- describe('Sign in Link', () => {
- it('Shows the Sign In message with the appropriate link', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- f.startSignUpWithEmailAddress();
- });
- render(, { wrapper });
-
- const signInLink = screen.getByText('Have an account?').nextElementSibling;
- expect(signInLink?.textContent).toBe('Sign in');
- expect(signInLink?.tagName.toUpperCase()).toBe('A');
- expect(signInLink?.getAttribute('href')).toMatch(fixtures.environment.displayConfig.signInUrl);
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx
deleted file mode 100644
index 8391e93bca0..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import React from 'react';
-
-import { EmailLinkError, EmailLinkErrorCode } from '../../../../core/resources';
-import { render, runFakeTimers, screen, waitFor } from '../../../../testUtils';
-import { SignUpEmailLinkFlowComplete } from '../../../common/EmailLinkCompleteFlowCard';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-
-const { createFixtures } = bindCreateFixtures('SignUp');
-
-describe('SignUpEmailLinkFlowComplete', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- render(, { wrapper });
- screen.getByText(/signing up/i);
- });
-
- it.todo('shows the component for at least 500ms before handling the flow');
-
- it('magic link verification is called', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- await runFakeTimers(async timers => {
- render(, { wrapper });
- timers.runOnlyPendingTimers();
- await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled());
- });
- });
-
- describe('Success', () => {
- it('shows the success message when successfully verified', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- await runFakeTimers(async timers => {
- render(, { wrapper });
- timers.runOnlyPendingTimers();
- await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled());
- screen.getByText(/success/i);
- });
- });
- });
-
- describe('Error messages', () => {
- it('shows the expired error message when the appropriate error is thrown', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce(
- await Promise.resolve(() => {
- throw new EmailLinkError(EmailLinkErrorCode.Expired);
- }),
- );
-
- await runFakeTimers(async timers => {
- render(, { wrapper });
- timers.runOnlyPendingTimers();
- await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled());
- screen.getByText(/expired/i);
- });
- });
-
- it('shows the failed error message when the appropriate error is thrown', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce(
- await Promise.resolve(() => {
- throw new EmailLinkError(EmailLinkErrorCode.Failed);
- }),
- );
- await runFakeTimers(async timers => {
- render(, { wrapper });
- timers.runOnlyPendingTimers();
- await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled());
- screen.getByText(/invalid/i);
- });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpStart.test.tsx
deleted file mode 100644
index a7000e6668f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpStart.test.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-import { OAUTH_PROVIDERS } from '@clerk/types';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignUpStart } from '../SignUpStart';
-
-const { createFixtures } = bindCreateFixtures('SignUp');
-
-describe('SignUpStart', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
- render(, { wrapper });
- screen.getByText(/create/i);
- });
-
- describe('Sign up options', () => {
- it('enables sign up with email address', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Email address');
- });
-
- it('enables sign up with phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Phone number');
- });
-
- it('enables sign up with email address and password', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Email address');
- screen.getByText('Password');
- });
-
- it('enables sign up with phone number and password', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Phone number');
- screen.getByText('Password');
- });
-
- it('enables sign up with email address or phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: false });
- f.withPhoneNumber({ required: false });
- });
- render(, { wrapper });
- screen.getByText('Email address');
- screen.getByText('Use phone instead');
- });
-
- it('enables sign up with email address and phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPhoneNumber({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Email address');
- screen.getByText('Phone number');
- });
-
- it('enables sign up with email address, phone number and password', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPhoneNumber({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- screen.getByText('Email address');
- screen.getByText('Phone number');
- screen.getByText('Password');
- });
-
- it('enables optional email', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: false });
- f.withPhoneNumber({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- expect(screen.getByText('Email address').nextElementSibling?.textContent).toBe('Optional');
- });
-
- it('enables optional phone number', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPhoneNumber({ required: false });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- expect(screen.getByText('Phone number').nextElementSibling?.textContent).toBe('Optional');
- });
-
- it('shows the "Continue" button', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
- expect(screen.getByText('Continue').tagName.toUpperCase()).toBe('BUTTON');
- });
-
- it.each(OAUTH_PROVIDERS)('shows the "Continue with $name" social OAuth button', async ({ provider, name }) => {
- const { wrapper } = await createFixtures(f => {
- f.withSocialProvider({ provider });
- });
-
- render(, { wrapper });
- screen.getByText(`Continue with ${name}`);
- });
-
- it('displays the "or" divider when using oauth and email options', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withSocialProvider({ provider: 'google' });
- });
-
- render(, { wrapper });
- screen.getByText(/Continue with/i);
- screen.getByText(/or/i);
- });
- });
-
- describe('Sign in Link', () => {
- it('Shows the Sign In message with the appropriate link', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPassword({ required: true });
- });
- render(, { wrapper });
-
- const signInLink = screen.getByText('Have an account?').nextElementSibling;
- expect(signInLink?.textContent).toBe('Sign in');
- expect(signInLink?.tagName.toUpperCase()).toBe('A');
- expect(signInLink?.getAttribute('href')).toMatch(fixtures.environment.displayConfig.signInUrl);
- });
- });
-
- describe('Preserved values from FAPI', () => {
- it('Shows the values from the sign up object as default prepopulated values', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.withPhoneNumber({ required: true });
- f.withName({ required: true });
- });
-
- fixtures.clerk.client.signUp.emailAddress = 'george@clerk.com';
- fixtures.clerk.client.signUp.firstName = 'George';
- fixtures.clerk.client.signUp.lastName = 'Clerk';
- fixtures.clerk.client.signUp.phoneNumber = '+1123456789';
-
- const screen = render(, { wrapper });
-
- expect(screen.getByRole('textbox', { name: 'Email address' })).toHaveValue('george@clerk.com');
- expect(screen.getByRole('textbox', { name: 'First name' })).toHaveValue('George');
- expect(screen.getByRole('textbox', { name: 'Last name' })).toHaveValue('Clerk');
- expect(screen.getByRole('textbox', { name: 'Phone number' })).toHaveValue('(123) 456-789');
- });
- });
-
- describe('initialValues', () => {
- it('prefills the emailAddress field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withEmailAddress();
- });
- props.setProps({ initialValues: { emailAddress: 'foo@clerk.com' } });
-
- render(, { wrapper });
- screen.getByDisplayValue(/foo@clerk.com/i);
- });
-
- it('prefills the phoneNumber field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withPhoneNumber();
- });
- props.setProps({ initialValues: { phoneNumber: '+306911111111' } });
-
- render(, { wrapper });
- screen.getByDisplayValue(/691 1111111/i);
- });
-
- it('prefills the username field with the correct initial value', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withUsername();
- });
-
- props.setProps({ initialValues: { username: 'foo' } });
- render(, { wrapper });
- screen.getByDisplayValue(/foo/i);
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx
deleted file mode 100644
index 60171c25bd6..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignUpVerifyEmail } from '../SignUpVerifyEmail';
-
-const { createFixtures } = bindCreateFixtures('SignUp');
-
-describe('SignUpVerifyEmail', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
- render(, { wrapper });
- screen.getByText(/verify/i);
- });
-
- it('shows the email associated with the sign up', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- render(, { wrapper });
- screen.getByText('test@clerk.com');
- });
-
- it('shows the verify with link message', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true, verifications: ['email_link'] });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- fixtures.signUp.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- render(, { wrapper });
- screen.getAllByText(/Verification Link/i);
- });
-
- it('shows the verify with code message', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true, verifications: ['email_code'] });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- fixtures.signUp.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- render(, { wrapper });
- screen.getAllByText(/Verification Code/i);
- });
-
- it('clicking on the edit icon navigates to the previous route', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- const { userEvent } = render(, { wrapper });
- await userEvent.click(
- screen.getByRole('button', {
- name: /edit/i,
- }),
- ),
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../');
- });
-
- it('Resend link button exists', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true, verifications: ['email_link'] });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- fixtures.signUp.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- render(, { wrapper });
- const resendButton = screen.getByText(/Resend/i);
- expect(resendButton.tagName.toUpperCase()).toBe('BUTTON');
- });
-
- it('Resend code button exists', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress({ required: true, verifications: ['email_code'] });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- fixtures.signUp.createEmailLinkFlow.mockImplementation(
- () =>
- ({
- startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))),
- } as any),
- );
-
- render(, { wrapper });
- const resendButton = screen.getByText(/Resend/i);
- expect(resendButton.tagName.toUpperCase()).toBe('BUTTON');
- });
-
- it.todo('Resend link button is pressable after 60 seconds');
- it.todo('Resend code button is pressable after 30 seconds');
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx
deleted file mode 100644
index b562c5b4b3f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { SignUpVerifyPhone } from '../SignUpVerifyPhone';
-
-const { createFixtures } = bindCreateFixtures('SignUp');
-
-describe('SignUpVerifyPhone', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures();
- render(, { wrapper });
- screen.getByText(/verify/i);
- });
-
- it('shows the phone number associated with the sign up', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- f.startSignUpWithPhoneNumber({ phoneNumber: '+306911111111' });
- });
- render(, { wrapper });
- screen.getByText('+30 691 1111111');
- });
-
- it('shows the verify with code message', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- f.startSignUpWithPhoneNumber();
- });
- render(, { wrapper });
- screen.getAllByText(/Verification Code/i);
- });
-
- it('clicking on the edit icon navigates to the previous route', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- f.startSignUpWithPhoneNumber();
- });
- const { userEvent } = render(, { wrapper });
- await userEvent.click(
- screen.getByRole('button', {
- name: /edit/i,
- }),
- ),
- expect(fixtures.router.navigate).toHaveBeenCalledWith('../');
- });
-
- it('Resend code button exists', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withPhoneNumber({ required: true });
- f.startSignUpWithEmailAddress({ emailAddress: 'test@clerk.com' });
- });
- render(, { wrapper });
- const resendButton = screen.getByText(/Resend/i);
- expect(resendButton.tagName.toUpperCase()).toBe('BUTTON');
- });
-
- it.todo('Resend code button is pressable after 30 seconds');
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/signUpFormHelpers.test.ts b/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/signUpFormHelpers.test.ts
deleted file mode 100644
index 3535c404738..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/__tests__/signUpFormHelpers.test.ts
+++ /dev/null
@@ -1,526 +0,0 @@
-import { determineActiveFields, getInitialActiveIdentifier } from '../signUpFormHelpers';
-
-describe('determineActiveFields()', () => {
- // For specs refer to https://www.notion.so/clerkdev/Vocabulary-8f775765258643978f5811c88b140b2d
- // and the current Instance User settings options
- describe('returns first party field based on auth config', () => {
- type Scenario = [string, any, any];
- const isProgressiveSignUp = false;
-
- const scenaria: Scenario[] = [
- [
- 'email only option',
- {
- email_address: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- phone_number: {
- enabled: true,
- required: false,
- used_for_first_factor: false,
- },
- first_name: {
- enabled: true,
- required: true,
- },
- last_name: {
- enabled: true,
- required: true,
- },
- password: {
- enabled: true,
- required: true,
- },
- username: {
- enabled: true,
- required: false,
- },
- },
- {
- emailAddress: {
- required: true,
- disabled: false,
- },
- firstName: {
- required: true,
- },
- lastName: {
- required: true,
- },
- password: {
- required: true,
- },
- username: {
- required: false,
- },
- },
- ],
- [
- 'phone only option',
- {
- email_address: {
- enabled: true,
- required: false,
- used_for_first_factor: false,
- },
- phone_number: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- first_name: {
- enabled: true,
- required: true,
- },
- last_name: {
- enabled: true,
- required: true,
- },
- password: {
- enabled: true,
- required: true,
- },
- username: {
- enabled: true,
- required: true,
- },
- },
- {
- phoneNumber: {
- required: true,
- },
- firstName: {
- required: true,
- },
- lastName: {
- required: true,
- },
- password: {
- required: true,
- },
- username: {
- required: true,
- },
- },
- ],
- [
- 'email or phone option',
- {
- phone_number: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- email_address: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- first_name: {
- enabled: true,
- required: true,
- },
- last_name: {
- enabled: true,
- required: true,
- },
- password: {
- enabled: true,
- required: true,
- },
- username: {
- enabled: true,
- required: false,
- },
- },
- {
- emailAddress: {
- required: true, // email will be toggled on initially
- disabled: false,
- },
- firstName: {
- required: true,
- },
- lastName: {
- required: true,
- },
- password: {
- required: true,
- },
- username: {
- required: false,
- },
- },
- ],
- [
- 'optional first and last name',
- {
- email_address: {
- enabled: true,
- required: false,
- used_for_first_factor: false,
- },
- phone_number: {
- enabled: true,
- required: false,
- used_for_first_factor: false,
- },
- first_name: {
- enabled: true,
- required: false,
- },
- last_name: {
- enabled: true,
- required: false,
- },
- password: {
- enabled: true,
- required: true,
- },
- username: {
- enabled: true,
- required: false,
- },
- },
- {
- firstName: {
- required: false,
- },
- lastName: {
- required: false,
- },
- password: {
- required: true,
- },
- username: {
- required: false,
- },
- },
- ],
- [
- 'no fields enabled',
- {
- phone_number: {
- enabled: false,
- required: false,
- used_for_first_factor: false,
- },
- email_address: {
- enabled: false,
- required: false,
- used_for_first_factor: false,
- },
- first_name: {
- enabled: false,
- required: false,
- },
- last_name: {
- enabled: false,
- required: false,
- },
- password: {
- enabled: true,
- required: false,
- },
- username: {
- enabled: false,
- required: false,
- },
- },
- {},
- ],
- ];
-
- it.each(scenaria)('%s', (___, attributes, result) => {
- expect(
- determineActiveFields({
- attributes: attributes,
- activeCommIdentifierType: getInitialActiveIdentifier(attributes, isProgressiveSignUp),
- isProgressiveSignUp,
- }),
- ).toEqual(result);
- });
-
- it.each(scenaria)('with ticket, %s', (___, attributes, result) => {
- // Email address or phone number cannot be required when there's a
- // ticket token present. Instead, we'll require the ticket parameter.
-
- const resultClone = JSON.parse(JSON.stringify(result));
-
- const expected = { ...resultClone, ticket: { required: true } };
-
- delete expected.emailAddress;
- delete expected.phoneNumber;
-
- const res = determineActiveFields({
- attributes: attributes,
- hasTicket: true,
- activeCommIdentifierType: getInitialActiveIdentifier(attributes, isProgressiveSignUp),
- isProgressiveSignUp,
- });
-
- expect(res).toMatchObject(expected);
- });
-
- it('email is shown but disabled if it has a value in the token case', () => {
- const [___, attributes, result] = scenaria[0];
-
- const resultClone = JSON.parse(JSON.stringify(result));
-
- const expected = { ...resultClone, ticket: { required: true } };
-
- expected.emailAddress.disabled = true;
-
- delete expected.phoneNumber;
-
- const res = determineActiveFields({
- attributes: attributes,
- hasTicket: true,
- hasEmail: true,
- activeCommIdentifierType: getInitialActiveIdentifier(attributes, isProgressiveSignUp),
- isProgressiveSignUp,
- });
-
- expect(res).toMatchObject(expected);
- });
- });
-
- describe('calculates active fields based on user settings for Progressive Sign up', () => {
- type Scenario = [string, any, any];
- const isProgressiveSignUp = true;
-
- const mockDefaultAttributesProgressive = {
- first_name: {
- enabled: false,
- required: false,
- },
- last_name: {
- enabled: false,
- required: false,
- },
- password: {
- enabled: false,
- required: false,
- },
- username: {
- enabled: false,
- required: false,
- },
- };
-
- const scenarios: Scenario[] = [
- [
- 'email required',
- {
- ...mockDefaultAttributesProgressive,
- email_address: {
- enabled: true,
- required: true,
- used_for_first_factor: false,
- },
- phone_number: {
- enabled: false,
- required: false,
- used_for_first_factor: true,
- },
- },
- {
- emailAddress: {
- required: true,
- disabled: false,
- },
- },
- ],
- [
- 'phone required',
- {
- ...mockDefaultAttributesProgressive,
- email_address: {
- enabled: false,
- required: false,
- used_for_first_factor: true,
- },
- phone_number: {
- enabled: true,
- required: true,
- used_for_first_factor: false,
- },
- },
- {
- phoneNumber: {
- required: true,
- },
- },
- ],
- [
- 'email & phone required',
- {
- ...mockDefaultAttributesProgressive,
- phone_number: {
- enabled: true,
- required: true,
- used_for_first_factor: false,
- },
- email_address: {
- enabled: true,
- required: true,
- used_for_first_factor: false,
- },
- },
- {
- emailAddress: {
- required: true,
- disabled: false,
- },
- phoneNumber: {
- required: true,
- },
- },
- ],
- [
- 'email OR phone',
- {
- ...mockDefaultAttributesProgressive,
- phone_number: {
- enabled: true,
- required: false,
- used_for_first_factor: true,
- },
- email_address: {
- enabled: true,
- required: false,
- used_for_first_factor: true,
- },
- },
- {
- emailAddress: {
- required: false, // email will be toggled on initially
- disabled: false,
- },
- },
- ],
- [
- 'email required, phone optional',
- {
- ...mockDefaultAttributesProgressive,
- phone_number: {
- enabled: true,
- required: false,
- used_for_first_factor: true,
- },
- email_address: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- },
- {
- emailAddress: {
- required: true,
- disabled: false,
- },
- phoneNumber: {
- required: false,
- },
- },
- ],
- [
- 'phone required, email optional',
- {
- ...mockDefaultAttributesProgressive,
- phone_number: {
- enabled: true,
- required: true,
- used_for_first_factor: true,
- },
- email_address: {
- enabled: true,
- required: false,
- used_for_first_factor: true,
- },
- },
- {
- emailAddress: {
- required: false,
- disabled: false,
- },
- phoneNumber: {
- required: true,
- },
- },
- ],
- ];
-
- it.each(scenarios)('%s', (___, attributes, result) => {
- expect(
- determineActiveFields({
- attributes: attributes,
- activeCommIdentifierType: getInitialActiveIdentifier(attributes, isProgressiveSignUp),
- isProgressiveSignUp,
- }),
- ).toEqual(result);
- });
-
- it('phone is shown if enabled in the token case', () => {
- const [___, attributes, result] = [
- 'email only option',
- {
- email_address: {
- enabled: true,
- required: true,
- },
- phone_number: {
- enabled: true,
- required: true,
- },
- first_name: {
- enabled: true,
- required: true,
- },
- last_name: {
- enabled: true,
- required: true,
- },
- password: {
- enabled: false,
- required: false,
- },
- username: {
- enabled: false,
- required: false,
- },
- },
- {
- emailAddress: {
- required: true,
- disabled: true,
- },
- phoneNumber: {
- required: true,
- },
- ticket: {
- required: true,
- },
- firstName: {
- required: true,
- },
- lastName: {
- required: true,
- },
- },
- ] as Scenario;
-
- const res = determineActiveFields({
- attributes: attributes,
- hasTicket: true,
- hasEmail: true,
- activeCommIdentifierType: getInitialActiveIdentifier(attributes, isProgressiveSignUp),
- isProgressiveSignUp,
- });
-
- expect(res).toEqual(result);
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/index.ts b/packages/clerk-js/src/ui.retheme/components/SignUp/index.ts
deleted file mode 100644
index 6169154a2c9..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SignUp';
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/signUpFormHelpers.ts b/packages/clerk-js/src/ui.retheme/components/SignUp/signUpFormHelpers.ts
deleted file mode 100644
index 9e17ee63136..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/signUpFormHelpers.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-import { camelToSnake } from '@clerk/shared';
-import type { Attributes, SignUpResource, UserSettingsResource } from '@clerk/types';
-
-import type { FieldState } from '../../common';
-
-/**
- * ActiveIdentifier denotes which one of the email address or phone number takes priority when enabled
- */
-export type ActiveIdentifier = 'emailAddress' | 'phoneNumber' | null | undefined;
-
-const FieldKeys = ['emailAddress', 'phoneNumber', 'username', 'firstName', 'lastName', 'password', 'ticket'];
-export type FieldKey = (typeof FieldKeys)[number];
-
-export type FormState = {
- [key in FieldKey]: FieldState;
-};
-
-export type Field = {
- disabled?: boolean;
- /**
- * Denotes if the corresponding input is required to be filled
- */
- required: boolean;
-};
-
-export type Fields = {
- [key in FieldKey]: Field | undefined;
-};
-
-type FieldDeterminationProps = {
- attributes: Attributes;
- activeCommIdentifierType?: ActiveIdentifier;
- hasTicket?: boolean;
- hasEmail?: boolean;
- signUp?: SignUpResource | undefined;
- isProgressiveSignUp: boolean;
-};
-
-export function determineActiveFields(fieldProps: FieldDeterminationProps): Fields {
- return FieldKeys.reduce((fields: Fields, fieldKey: string) => {
- const field = getField(fieldKey, fieldProps);
- if (field) {
- fields[fieldKey] = field;
- }
- return fields;
- }, {} as Fields);
-}
-
-// If continuing with an existing sign-up, show only fields absolutely necessary to minimize fiction
-export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpResource) {
- if (signUp) {
- const hasEmailFilled = !!signUp.emailAddress;
- const hasVerifiedEmail = signUp.verifications?.emailAddress?.status == 'verified';
- const hasVerifiedPhone = signUp.verifications?.phoneNumber?.status == 'verified';
- const hasVerifiedExternalAccount = signUp.verifications?.externalAccount?.status == 'verified';
- const hasVerifiedWeb3Wallet = signUp.verifications?.web3Wallet?.status == 'verified';
-
- if (hasEmailFilled || hasVerifiedEmail) {
- delete fields.emailAddress;
- }
-
- if (hasVerifiedPhone) {
- delete fields.phoneNumber;
- }
-
- if (hasVerifiedExternalAccount || hasVerifiedWeb3Wallet) {
- delete fields.password;
- }
-
- if (signUp.firstName) {
- delete fields.firstName;
- }
-
- if (signUp.lastName) {
- delete fields.lastName;
- }
-
- if (signUp.username) {
- delete fields.username;
- }
-
- // Hide any non-required fields
- Object.entries(fields).forEach(([k, v]) => {
- if (v && !v.required) {
- // @ts-ignore
- delete fields[k];
- }
- });
- }
-}
-
-export const getInitialActiveIdentifier = (attributes: Attributes, isProgressiveSignUp: boolean): ActiveIdentifier => {
- if (emailOrPhone(attributes, isProgressiveSignUp)) {
- // If we are in the case of Email OR Phone, email takes priority
- return 'emailAddress';
- }
-
- const { email_address, phone_number } = attributes;
-
- if (email_address.enabled && isProgressiveSignUp ? email_address.required : email_address.used_for_first_factor) {
- return 'emailAddress';
- }
-
- if (phone_number.enabled && isProgressiveSignUp ? phone_number.required : phone_number.used_for_first_factor) {
- return 'phoneNumber';
- }
-
- return null;
-};
-
-export function showFormFields(userSettings: UserSettingsResource): boolean {
- const { authenticatableSocialStrategies, web3FirstFactors } = userSettings;
-
- return userSettings.hasValidAuthFactor || (!authenticatableSocialStrategies.length && !web3FirstFactors.length);
-}
-
-export function emailOrPhone(attributes: Attributes, isProgressiveSignUp: boolean) {
- const { email_address, phone_number } = attributes;
-
- if (isProgressiveSignUp) {
- return email_address.enabled && phone_number.enabled && !email_address.required && !phone_number.required;
- }
-
- return email_address.used_for_first_factor && phone_number.used_for_first_factor;
-}
-
-function getField(fieldKey: FieldKey, fieldProps: FieldDeterminationProps): Field | undefined {
- switch (fieldKey) {
- case 'emailAddress':
- return getEmailAddressField(fieldProps);
- case 'phoneNumber':
- return getPhoneNumberField(fieldProps);
- case 'password':
- return getPasswordField(fieldProps.attributes);
- case 'ticket':
- return getTicketField(fieldProps.hasTicket);
- case 'username':
- case 'firstName':
- case 'lastName':
- return getGenericField(fieldKey, fieldProps.attributes);
- default:
- return;
- }
-}
-
-function getEmailAddressField({
- attributes,
- hasTicket,
- hasEmail,
- activeCommIdentifierType,
- isProgressiveSignUp,
-}: FieldDeterminationProps): Field | undefined {
- if (isProgressiveSignUp) {
- // If there is no ticket, or there is a ticket along with an email, and email address is enabled,
- // we have to show it in the SignUp form
- const show = (!hasTicket || (hasTicket && hasEmail)) && attributes.email_address.enabled;
-
- if (!show) {
- return;
- }
-
- // If we are in the case of Email OR Phone, determine if the initial input has to be the email address
- // based on the active identifier type.
- if (emailOrPhone(attributes, isProgressiveSignUp) && activeCommIdentifierType !== 'emailAddress') {
- return;
- }
-
- return {
- required: attributes.email_address.required,
- disabled: !!hasTicket && !!hasEmail,
- };
- }
-
- const show =
- (!hasTicket || (hasTicket && hasEmail)) &&
- attributes.email_address.enabled &&
- attributes.email_address.used_for_first_factor &&
- activeCommIdentifierType == 'emailAddress';
-
- if (!show) {
- return;
- }
-
- return {
- required: true, // as far as the FE is concerned the email address is required, if shown
- disabled: !!hasTicket && !!hasEmail,
- };
-}
-
-function getPhoneNumberField({
- attributes,
- hasTicket,
- activeCommIdentifierType,
- isProgressiveSignUp,
-}: FieldDeterminationProps): Field | undefined {
- if (isProgressiveSignUp) {
- // If there is no ticket and phone number is enabled, we have to show it in the SignUp form
- const show = attributes.phone_number.enabled;
-
- if (!show) {
- return;
- }
-
- // If we are in the case of Email OR Phone, determine if the initial input has to be the phone number
- // based on the active identifier type.
- if (emailOrPhone(attributes, isProgressiveSignUp) && activeCommIdentifierType !== 'phoneNumber') {
- return;
- }
-
- return {
- required: attributes.phone_number.required,
- };
- }
-
- const show =
- !hasTicket &&
- attributes.phone_number.enabled &&
- attributes.phone_number.used_for_first_factor &&
- activeCommIdentifierType == 'phoneNumber';
-
- if (!show) {
- return;
- }
-
- return {
- required: true, // as far as the FE is concerned the phone number is required, if shown
- };
-}
-
-// Currently, password is always enabled so only show if required
-function getPasswordField(attributes: Attributes): Field | undefined {
- const show = attributes.password.enabled && attributes.password.required;
-
- if (!show) {
- return;
- }
-
- return {
- required: attributes.password.required,
- };
-}
-
-function getTicketField(hasTicket?: boolean): Field | undefined {
- if (!hasTicket) {
- return;
- }
-
- return {
- required: true,
- };
-}
-
-function getGenericField(fieldKey: FieldKey, attributes: Attributes): Field | undefined {
- const attrKey = camelToSnake(fieldKey);
-
- // @ts-ignore
- if (!attributes[attrKey].enabled) {
- return;
- }
-
- return {
- // @ts-ignore
- required: attributes[attrKey].required,
- };
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/SignUp/util.ts b/packages/clerk-js/src/ui.retheme/components/SignUp/util.ts
deleted file mode 100644
index 343a9bcaccc..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/SignUp/util.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../../../utils/completeSignUpFlow';
diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButton.tsx b/packages/clerk-js/src/ui.retheme/components/UserButton/UserButton.tsx
deleted file mode 100644
index 6115e3bb7bb..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButton.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import { useId } from 'react';
-
-import { getFullName, getIdentifier } from '../../../utils/user';
-import { useUserButtonContext, withCoreUserGuard } from '../../contexts';
-import { descriptors, Flex, Flow, Text } from '../../customizables';
-import { Popover, withCardStateProvider, withFloatingTree } from '../../elements';
-import { usePopover } from '../../hooks';
-import { UserButtonPopover } from './UserButtonPopover';
-import { UserButtonTrigger } from './UserButtonTrigger';
-
-const _UserButton = withFloatingTree(() => {
- const { defaultOpen } = useUserButtonContext();
- const { floating, reference, styles, toggle, isOpen, nodeId, context } = usePopover({
- defaultOpen,
- placement: 'bottom-end',
- offset: 8,
- });
-
- const userButtonMenuId = useId();
-
- return (
-
-
-
-
-
-
-
-
-
- );
-});
-
-const UserButtonTopLevelIdentifier = () => {
- const { user } = useUser();
-
- const { showName } = useUserButtonContext();
- if (!user) {
- return null;
- }
- return showName ? (
-
- {getFullName(user) || getIdentifier(user)}
-
- ) : null;
-};
-
-export const UserButton = withCoreUserGuard(withCardStateProvider(_UserButton));
diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonPopover.tsx b/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonPopover.tsx
deleted file mode 100644
index b61e418b5e2..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonPopover.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import { useSession, useUser } from '@clerk/shared/react';
-import type { ActiveSessionResource } from '@clerk/types';
-import React from 'react';
-
-import { useEnvironment, useUserButtonContext } from '../../contexts';
-import { descriptors, Icon, localizationKeys } from '../../customizables';
-import { Action, Actions, PopoverCard, PreviewButton, RootBox, SecondaryActions, UserPreview } from '../../elements';
-import { Add, CheckmarkFilled, SignOut, SwitchArrowRight } from '../../icons';
-import type { PropsOfComponent } from '../../styledSystem';
-import { MultiSessionActions, SingleSessionActions } from './SessionActions';
-import { useMultisessionActions } from './useMultisessionActions';
-
-type UserButtonPopoverProps = { close: () => void } & PropsOfComponent;
-
-export const UserButtonPopover = React.forwardRef((props, ref) => {
- const { close, ...rest } = props;
- const { session } = useSession() as { session: ActiveSessionResource };
- const { authConfig } = useEnvironment();
- const { user } = useUser();
- const {
- handleAddAccountClicked,
- handleManageAccountClicked,
- handleSessionClicked,
- handleSignOutAllClicked,
- handleSignOutSessionClicked,
- otherSessions,
- } = useMultisessionActions({ ...useUserButtonContext(), actionCompleteCallback: close, user });
-
- const addAccountButton = (
- ({
- backgroundColor: t.colors.$colorBackground,
- })}
- iconSx={t => ({
- color: t.colors.$blackAlpha400,
- width: t.sizes.$9,
- height: t.sizes.$6,
- })}
- />
- );
-
- const signOutAllButton = (
- ({
- padding: t.space.$2,
- borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha200}`,
- })}
- >
- ({
- color: t.colors.$blackAlpha700,
- padding: `${t.space.$2} ${t.space.$3}`,
- borderBottom: 'none',
- borderRadius: t.radii.$lg,
- })}
- />
-
- );
-
- const sessionActions = (
-
- {otherSessions.map(session => (
- ({
- height: t.sizes.$20,
- borderRadius: 0,
- borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}`,
- backgroundColor: t.colors.$colorBackground,
- })}
- onClick={handleSessionClicked(session)}
- role='menuitem'
- >
-
-
- ))}
- {addAccountButton}
-
- );
-
- return (
-
-
-
- ({
- padding: `${t.space.$4} ${t.space.$5}`,
- })}
- icon={
- ({ width: t.sizes.$3x5, height: t.sizes.$3x5 })}
- />
- }
- iconSx={t => ({ left: 'unset', right: 0, color: t.colors.$primary500 })}
- />
- {authConfig.singleSessionMode ? (
-
- ) : (
- <>
-
- {sessionActions}
- >
- )}
-
-
- {!authConfig.singleSessionMode && signOutAllButton}
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonTrigger.tsx b/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonTrigger.tsx
deleted file mode 100644
index e16b3807069..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserButton/UserButtonTrigger.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import { forwardRef } from 'react';
-
-import { Button, descriptors } from '../../customizables';
-import { UserAvatar, withAvatarShimmer } from '../../elements';
-import type { PropsOfComponent } from '../../styledSystem';
-
-type UserButtonTriggerProps = PropsOfComponent & {
- isOpen: boolean;
-};
-
-export const UserButtonTrigger = withAvatarShimmer(
- forwardRef((props, ref) => {
- const { sx, ...rest } = props;
- const { user } = useUser();
-
- 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/components/UserProfile/ConnectedAccountsPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/ConnectedAccountsPage.tsx
deleted file mode 100644
index 5f3357cc407..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/ConnectedAccountsPage.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { ExternalAccountResource, OAuthProvider, OAuthStrategy } from '@clerk/types';
-import React from 'react';
-
-import { appendModalState } from '../../../utils';
-import { useWizard, Wizard } from '../../common';
-import { useUserProfileContext } from '../../contexts';
-import { Col, Image, localizationKeys, Text } from '../../customizables';
-import {
- ArrowBlockButton,
- ContentPage,
- FormButtonContainer,
- NavigateToFlowStartButton,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useEnabledThirdPartyProviders } from '../../hooks';
-import { useRouter } from '../../router';
-import { handleError, sleep } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const ConnectedAccountsPage = withCardStateProvider(() => {
- const title = localizationKeys('userProfile.connectedAccountPage.title');
- const { user } = useUser();
-
- const { params } = useRouter();
- const { id } = params || {};
-
- const ref = React.useRef(user?.externalAccounts.find(a => a.id === id));
- const wizard = useWizard({ defaultStep: ref.current ? 1 : 0 });
-
- // TODO: Better handling of success redirect
- return (
-
-
-
-
- );
-});
-
-const AddConnectedAccount = () => {
- const card = useCardState();
- const { user } = useUser();
- const { navigate } = useRouter();
- const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders();
- const { additionalOAuthScopes, componentName, mode } = useUserProfileContext();
- const isModal = mode === 'modal';
-
- const enabledStrategies = strategies.filter(s => s.startsWith('oauth')) as OAuthStrategy[];
- const connectedStrategies = user?.verifiedExternalAccounts.map(a => `oauth_${a.provider}`) as OAuthStrategy[];
-
- const unconnectedStrategies = enabledStrategies.filter(provider => {
- return !connectedStrategies.includes(provider);
- });
-
- const connect = (strategy: OAuthStrategy) => {
- const socialProvider = strategy.replace('oauth_', '') as OAuthProvider;
- const redirectUrl = isModal
- ? appendModalState({ url: window.location.href, componentName, socialProvider: socialProvider })
- : window.location.href;
- const additionalScopes = additionalOAuthScopes ? additionalOAuthScopes[socialProvider] : [];
-
- // TODO: Decide if we should keep using this strategy
- // If yes, refactor and cleanup:
- card.setLoading(strategy);
- user
- ?.createExternalAccount({
- strategy: strategy,
- redirectUrl,
- additionalScopes,
- })
- .then(res => {
- if (res.verification?.externalVerificationRedirectURL) {
- void navigate(res.verification.externalVerificationRedirectURL.href);
- }
- })
- .catch(err => handleError(err, [], card.setError))
- .finally(() => {
- void sleep(2000).then(() => card.setIdle);
- });
- };
-
- return (
-
-
-
- {unconnectedStrategies.map(strategy => (
- connect(strategy)}
- isLoading={card.loadingMetadata === strategy}
- isDisabled={card.isLoading}
- leftIcon={
- ({ width: theme.sizes.$5 })}
- />
- }
- textLocalizationKey={localizationKeys('userProfile.connectedAccountPage.socialButtonsBlockButton', {
- provider: strategyToDisplayData[strategy].name,
- })}
- />
- ))}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/ConnectedAccountsSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/ConnectedAccountsSection.tsx
deleted file mode 100644
index a42a2d8ebaa..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/ConnectedAccountsSection.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { ExternalAccountResource, OAuthProvider, OAuthScope, OAuthStrategy } from '@clerk/types';
-
-import { appendModalState } from '../../../utils';
-import { useUserProfileContext } from '../../contexts';
-import { Badge, Col, descriptors, Flex, Image, localizationKeys } from '../../customizables';
-import { ProfileSection, useCardState, UserPreview } from '../../elements';
-import { useEnabledThirdPartyProviders } from '../../hooks';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-import { AddBlockButton } from './UserProfileBlockButtons';
-
-export const ConnectedAccountsSection = () => {
- const { user } = useUser();
- const { navigate } = useRouter();
- if (!user) {
- return null;
- }
- const accounts = [
- ...user.verifiedExternalAccounts,
- ...user.unverifiedExternalAccounts.filter(a => a.verification?.error),
- ];
-
- return (
-
- {accounts.map(account => (
-
- ))}
- navigate(`connected-account`)}
- />
-
- );
-};
-
-const ConnectedAccountAccordion = ({ account }: { account: ExternalAccountResource }) => {
- const card = useCardState();
- const { user } = useUser();
- const { navigate } = useRouter();
- const router = useRouter();
- const { providerToDisplayData } = useEnabledThirdPartyProviders();
- const error = account.verification?.error?.longMessage;
- const label = account.username || account.emailAddress;
- const defaultOpen = !!router.urlStateParam?.componentName;
- const { additionalOAuthScopes, componentName, mode } = useUserProfileContext();
- const isModal = mode === 'modal';
- const visitedProvider = account.provider === router.urlStateParam?.socialProvider;
- const additionalScopes = findAdditionalScopes(account, additionalOAuthScopes);
- const reauthorizationRequired = additionalScopes.length > 0 && account.approvedScopes != '';
- const title = !reauthorizationRequired
- ? localizationKeys('userProfile.start.connectedAccountsSection.title__connectionFailed')
- : localizationKeys('userProfile.start.connectedAccountsSection.title__reauthorize');
- const subtitle = !reauthorizationRequired
- ? (error as any)
- : localizationKeys('userProfile.start.connectedAccountsSection.subtitle__reauthorize');
- const actionLabel = !reauthorizationRequired
- ? localizationKeys('userProfile.start.connectedAccountsSection.actionLabel__connectionFailed')
- : localizationKeys('userProfile.start.connectedAccountsSection.actionLabel__reauthorize');
-
- const handleOnClick = async () => {
- const redirectUrl = isModal ? appendModalState({ url: window.location.href, componentName }) : window.location.href;
-
- try {
- let response: ExternalAccountResource;
- if (reauthorizationRequired) {
- response = await account.reauthorize({ additionalScopes, redirectUrl });
- } else {
- if (!user) {
- throw Error('user is not defined');
- }
-
- response = await user.createExternalAccount({
- strategy: account.verification!.strategy as OAuthStrategy,
- redirectUrl,
- additionalScopes,
- });
- }
-
- await navigate(response.verification!.externalVerificationRedirectURL?.href || '');
- } catch (err) {
- handleError(err, [], card.setError);
- }
- };
-
- return (
- ({ width: theme.sizes.$4 })}
- />
- }
- title={
-
- {`${providerToDisplayData[account.provider].name} ${label ? `(${label})` : ''}`}
- {(error || reauthorizationRequired) && (
-
- )}
-
- }
- >
-
- ({ width: theme.sizes.$4 })}
- />
- }
- />
- {(error || reauthorizationRequired) && (
-
- )}
-
- navigate(`connected-account/${account.id}/remove`)}
- />
-
-
- );
-};
-
-function findAdditionalScopes(
- account: ExternalAccountResource,
- scopes?: Partial>,
-): string[] {
- if (!scopes) {
- return [];
- }
-
- const additionalScopes = scopes[account.provider] || [];
- const currentScopes = account.approvedScopes.split(' ');
- const missingScopes = additionalScopes.filter(scope => !currentScopes.includes(scope));
- if (missingScopes.length === 0) {
- return [];
- }
-
- return additionalScopes;
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/DeletePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/DeletePage.tsx
deleted file mode 100644
index 007839ccf19..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/DeletePage.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useClerk, useUser } from '@clerk/shared/react';
-
-import { useEnvironment } from '../../contexts';
-import { localizationKeys, Text } from '../../customizables';
-import { ContentPage, Form, FormButtons, useCardState, withCardStateProvider } from '../../elements';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const DeletePage = withCardStateProvider(() => {
- const card = useCardState();
- const environment = useEnvironment();
- const router = useRouter();
- const { user } = useUser();
- const clerk = useClerk();
-
- const deleteUser = async () => {
- try {
- if (!user) {
- throw Error('user is not defined');
- }
-
- await user.delete();
- if (clerk.client.activeSessions.length > 0) {
- await router.navigate(environment.displayConfig.afterSignOutOneUrl);
- } else {
- await router.navigate(environment.displayConfig.afterSignOutAllUrl);
- }
- } catch (e) {
- handleError(e, [], card.setError);
- }
- };
-
- const confirmationField = useFormControl('deleteConfirmation', '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__confirmDeletion'),
- isRequired: true,
- placeholder: localizationKeys('formFieldInputPlaceholder__confirmDeletionUserAccount'),
- });
-
- const canSubmit = confirmationField.value === 'Delete account';
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/DeleteSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/DeleteSection.tsx
deleted file mode 100644
index b1029b97d95..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/DeleteSection.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Button, Col, Flex, localizationKeys, Text } from '../../customizables';
-import { ProfileSection } from '../../elements';
-import { useRouter } from '../../router';
-
-export const DeleteSection = () => {
- const { navigate } = useRouter();
-
- return (
-
- ({ marginTop: t.space.$2, marginLeft: t.space.$6 })}
- >
-
-
-
-
- navigate(`delete`)}
- localizationKey={localizationKeys('userProfile.start.dangerSection.deleteAccountButton')}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailPage.tsx
deleted file mode 100644
index 40d448671ab..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailPage.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { EmailAddressResource } from '@clerk/types';
-import React from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { localizationKeys, Text } from '../../customizables';
-import { ContentPage, Form, FormButtons, SuccessPage, useCardState, withCardStateProvider } from '../../elements';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-import { emailLinksEnabledForInstance } from './utils';
-import { VerifyWithCode } from './VerifyWithCode';
-import { VerifyWithLink } from './VerifyWithLink';
-
-export const EmailPage = withCardStateProvider(() => {
- const title = localizationKeys('userProfile.emailAddressPage.title');
- const card = useCardState();
- const { user } = useUser();
- const environment = useEnvironment();
- const preferEmailLinks = emailLinksEnabledForInstance(environment);
-
- const { params } = useRouter();
- const { id } = params || {};
-
- const emailAddressRef = React.useRef(user?.emailAddresses.find(a => a.id === id));
- const wizard = useWizard({
- defaultStep: emailAddressRef.current ? 1 : 0,
- onNextStep: () => card.setError(undefined),
- });
-
- const emailField = useFormControl('emailAddress', '', {
- type: 'email',
- label: localizationKeys('formFieldLabel__emailAddress'),
- placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress'),
- isRequired: true,
- });
-
- const canSubmit = emailField.value.length > 1 && user?.username !== emailField.value;
-
- const addEmail = async (e: React.FormEvent) => {
- e.preventDefault();
- return user
- ?.createEmailAddress({ email: emailField.value })
- .then(res => {
- emailAddressRef.current = res;
- wizard.nextStep();
- })
- .catch(e => handleError(e, [emailField], card.setError));
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {preferEmailLinks ? (
-
- ) : (
- emailAddressRef.current?.prepareVerification({ strategy: 'email_code' })}
- />
- )}
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailSection.tsx
deleted file mode 100644
index c0251a9488b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/EmailSection.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { EmailAddressResource } from '@clerk/types';
-
-import { Badge, Col, localizationKeys } from '../../customizables';
-import { ProfileSection, useCardState } from '../../elements';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-import { AddBlockButton } from './UserProfileBlockButtons';
-import { primaryIdentificationFirst } from './utils';
-
-export const EmailsSection = () => {
- const { navigate } = useRouter();
- const { user } = useUser();
-
- return (
-
- {user?.emailAddresses.sort(primaryIdentificationFirst(user.primaryEmailAddressId)).map(email => (
-
- ))}
-
- navigate('email-address')}
- />
-
- );
-};
-
-const EmailAccordion = ({ email }: { email: EmailAddressResource }) => {
- const card = useCardState();
- const { user } = useUser();
- const { navigate } = useRouter();
- const isPrimary = user?.primaryEmailAddressId === email.id;
- const isVerified = email.verification.status === 'verified';
- const setPrimary = () => {
- return user!.update({ primaryEmailAddressId: email.id }).catch(e => handleError(e, [], card.setError));
- };
-
- return (
-
- {isPrimary && }
- {!isVerified && (
-
- )}
- >
- }
- >
-
- {isPrimary && isVerified && (
-
- )}
- {isPrimary && !isVerified && (
- navigate(`email-address/${email.id}`)}
- />
- )}
- {!isPrimary && isVerified && (
-
- )}
- {!isPrimary && !isVerified && (
- navigate(`email-address/${email.id}`)}
- />
- )}
- navigate(`email-address/${email.id}/remove`)}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/EnterpriseAccountsSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/EnterpriseAccountsSection.tsx
deleted file mode 100644
index f3c51222b3c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/EnterpriseAccountsSection.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { SamlAccountResource } from '@clerk/types';
-
-import { Badge, Col, descriptors, Flex, Image, localizationKeys } from '../../customizables';
-import { ProfileSection, UserPreview } from '../../elements';
-import { useSaml } from '../../hooks';
-import { useRouter } from '../../router';
-import { UserProfileAccordion } from './UserProfileAccordion';
-
-export const EnterpriseAccountsSection = () => {
- const { user } = useUser();
-
- return (
-
- {user?.samlAccounts.map(account => (
-
- ))}
-
- );
-};
-
-const EnterpriseAccountAccordion = ({ account }: { account: SamlAccountResource }) => {
- const router = useRouter();
- const { getSamlProviderLogoUrl, getSamlProviderName } = useSaml();
- const error = account.verification?.error?.longMessage;
- const label = account.emailAddress;
- const providerName = getSamlProviderName(account.provider);
- const providerLogoUrl = getSamlProviderLogoUrl(account.provider);
-
- return (
- ({ width: theme.sizes.$4 })}
- />
- }
- title={
-
- {`${providerName} ${label ? `(${label})` : ''}`}
- {error && (
-
- )}
-
- }
- >
-
- ({ width: theme.sizes.$4 })}
- />
- }
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/LinkButtonWithDescription.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/LinkButtonWithDescription.tsx
deleted file mode 100644
index 427eb738f2d..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/LinkButtonWithDescription.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import type { LocalizationKey } from '../../customizables';
-import { Button, Col, Flex, Text, useLocalizations } from '../../customizables';
-import { useLoadingStatus } from '../../hooks';
-import type { PropsOfComponent } from '../../styledSystem';
-
-type LinkButtonWithTextDescriptionProps = Omit, 'title'> & {
- title: LocalizationKey;
- titleLabel?: React.ReactNode;
- subtitle: LocalizationKey;
- actionLabel?: LocalizationKey;
- onClick?: (e: React.MouseEvent) => Promise;
-};
-
-export const LinkButtonWithDescription = (props: LinkButtonWithTextDescriptionProps) => {
- const { title, subtitle, actionLabel, titleLabel, onClick: onClickProp, ...rest } = props;
- const status = useLoadingStatus();
- const { t } = useLocalizations();
-
- const onClick = (e: React.MouseEvent) => {
- status.setLoading();
- if (onClickProp) {
- (onClickProp?.(e) as any)?.finally(() => status.setIdle());
- }
- };
-
- return (
-
-
-
-
- {titleLabel}
-
-
-
- {actionLabel && (
-
- )}
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeAccordion.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeAccordion.tsx
deleted file mode 100644
index 515e19b05ed..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeAccordion.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Col, Icon, localizationKeys, useLocalizations } from '../../customizables';
-import { DotCircle } from '../../icons';
-import { useRouter } from '../../router';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-
-export const MfaBackupCodeAccordion = () => {
- const { navigate } = useRouter();
- const { t } = useLocalizations();
-
- return (
- ({ color: theme.colors.$blackAlpha700 })}
- />
- }
- title={t(localizationKeys('userProfile.start.mfaSection.backupCodes.headerTitle'))}
- >
-
- navigate('multi-factor/backup_code/add')}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeCreatePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeCreatePage.tsx
deleted file mode 100644
index 1c897287308..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeCreatePage.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { BackupCodeResource } from '@clerk/types';
-import React from 'react';
-
-import { descriptors, localizationKeys, Text } from '../../customizables';
-import {
- ContentPage,
- FormButtonContainer,
- FullHeightLoader,
- NavigateToFlowStartButton,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { handleError } from '../../utils';
-import { MfaBackupCodeList } from './MfaBackupCodeList';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const MfaBackupCodeCreatePage = withCardStateProvider(() => {
- const { user } = useUser();
- const card = useCardState();
- const [backupCode, setBackupCode] = React.useState(undefined);
-
- React.useEffect(() => {
- if (backupCode) {
- return;
- }
-
- void user
- ?.createBackupCode()
- .then((backupCode: BackupCodeResource) => setBackupCode(backupCode))
- .catch(err => handleError(err, [], card.setError));
- }, []);
-
- if (card.error) {
- return (
-
- );
- }
-
- return (
-
- {!backupCode ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
- >
- )}
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeList.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeList.tsx
deleted file mode 100644
index 45d720ca665..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeList.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { getIdentifier } from '../../../utils/user';
-import { PrintableComponent, usePrintable } from '../../common';
-import { useEnvironment } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
-import { Button, Col, Flex, Grid, Heading, localizationKeys, Text } from '../../customizables';
-import { useClipboard } from '../../hooks';
-import { mqu } from '../../styledSystem';
-import { MfaBackupCodeTile } from './MfaBackupCodeTile';
-
-type MfaBackupCodeListProps = {
- subtitle: LocalizationKey;
- backupCodes?: string[];
-};
-
-export const MfaBackupCodeList = (props: MfaBackupCodeListProps) => {
- const { subtitle, backupCodes } = props;
- const { applicationName } = useEnvironment().displayConfig;
- const { user } = useUser();
-
- const { print, printableProps } = usePrintable();
- const { onCopy, hasCopied } = useClipboard(backupCodes?.join(',') || '');
-
- if (!user) {
- return null;
- }
- const userIdentifier = getIdentifier(user);
-
- const onDownloadTxtFile = () => {
- const element = document.createElement('a');
- const file = new Blob([txtFileContent(backupCodes, applicationName, userIdentifier)], {
- type: 'text/plain',
- });
- element.href = URL.createObjectURL(file);
- element.download = `${applicationName}_backup_codes.txt`;
- document.body.appendChild(element);
- element.click();
- };
-
- if (!backupCodes) {
- return null;
- }
-
- return (
- <>
-
-
-
-
- ({
- gridTemplateColumns: `repeat(5, minmax(${t.sizes.$12}, 1fr))`,
- [mqu.sm]: {
- gridTemplateColumns: `repeat(2, minmax(${t.sizes.$12}, 1fr))`,
- },
- })}
- >
- {backupCodes.map((code, i) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
- Your backup codes for {applicationName} account {userIdentifier}:
-
-
- {backupCodes.map((code, i) => (
-
- ))}
-
-
- >
- );
-};
-
-function txtFileContent(backupCodes: string[] | undefined, applicationName: string, userIdentifier: string): string {
- const sanitizedBackupCodes = backupCodes?.join('\n');
- return `These are your backup codes for ${applicationName} account ${userIdentifier}.\nStore them securely and keep them secret. Each code can only be used once.\n\n${sanitizedBackupCodes}`;
-}
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodePage.tsx
deleted file mode 100644
index 4ddcfbeb853..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodePage.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useWizard, Wizard } from '../../common';
-import { Button, descriptors, localizationKeys, Text } from '../../customizables';
-import { ContentPage, FormButtonContainer, NavigateToFlowStartButton, withCardStateProvider } from '../../elements';
-import { MfaBackupCodeCreatePage } from './MfaBackupCodeCreatePage';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const MfaBackupCodePage = withCardStateProvider(() => {
- const wizard = useWizard();
-
- return (
-
-
-
-
-
- );
-});
-
-type AddBackupCodeProps = {
- onContinue: () => void;
-};
-
-const AddBackupCode = (props: AddBackupCodeProps) => {
- const { onContinue } = props;
-
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeTile.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeTile.tsx
deleted file mode 100644
index f768ff168b1..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaBackupCodeTile.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Flex, Text } from '../../customizables';
-
-export const MfaBackupCodeTile = (props: { code: string }) => {
- const { code } = props;
-
- return (
- ({
- backgroundColor: t.colors.$blackAlpha50,
- padding: `${t.space.$1} ${t.space.$4}`,
- borderRadius: t.radii.$sm,
- })}
- >
- {code}
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPage.tsx
deleted file mode 100644
index 8401ab413f3..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPage.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { VerificationStrategy } from '@clerk/types';
-import React from 'react';
-
-import { useEnvironment } from '../../contexts';
-import { Col, Grid, localizationKeys, Text } from '../../customizables';
-import {
- ContentPage,
- FormButtonContainer,
- NavigateToFlowStartButton,
- TileButton,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { AuthApp, DotCircle, Mobile } from '../../icons';
-import { mqu } from '../../styledSystem';
-import { MfaBackupCodePage } from './MfaBackupCodePage';
-import { MfaPhoneCodePage } from './MfaPhoneCodePage';
-import { MfaTOTPPage } from './MfaTOTPPage';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-import { getSecondFactorsAvailableToAdd } from './utils';
-
-export const MfaPage = withCardStateProvider(() => {
- const card = useCardState();
- const {
- userSettings: { attributes },
- } = useEnvironment();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const title = localizationKeys('userProfile.mfaPage.title');
- const [selectedMethod, setSelectedMethod] = React.useState();
-
- // Calculate second factors available to add on first use only
- const secondFactorsAvailableToAdd = React.useMemo(() => getSecondFactorsAvailableToAdd(attributes, user), []);
-
- React.useEffect(() => {
- if (secondFactorsAvailableToAdd.length === 0) {
- card.setError('There are no second factors available to add');
- }
- }, []);
-
- if (card.error) {
- return (
-
- );
- }
-
- // If there is only an available method or one has been selected, render the dedicated page instead
- if (secondFactorsAvailableToAdd.length === 1 || selectedMethod) {
- return ;
- }
-
- return (
-
-
-
- ({
- gridTemplateColumns: `repeat(3, minmax(${t.sizes.$24}, 1fr))`,
- gridAutoRows: t.sizes.$24,
- [mqu.sm]: {
- gridTemplateColumns: `repeat(2, minmax(${t.sizes.$24}, 1fr))`,
- },
- })}
- >
- {secondFactorsAvailableToAdd.map((method, i) => (
-
- ))}
-
-
-
-
-
-
-
- );
-});
-
-type MfaPageIfSingleOrCurrentProps = {
- method: string;
-};
-
-const MfaPageIfSingleOrCurrent = (props: MfaPageIfSingleOrCurrentProps) => {
- const { method } = props;
-
- switch (method) {
- case 'phone_code':
- return ;
- case 'totp':
- return ;
- case 'backup_code':
- return ;
- default:
- return null;
- }
-};
-
-type MfaAvailableMethodToAddProps = {
- method: string;
- setSelectedMethod: (method: VerificationStrategy | undefined) => void;
-};
-
-const MfaAvailableMethodToAdd = (props: MfaAvailableMethodToAddProps) => {
- const { method, setSelectedMethod } = props;
-
- let icon: React.ComponentType;
- let text: string;
- if (method === 'phone_code') {
- icon = Mobile;
- text = 'SMS code';
- } else if (method === 'totp') {
- icon = AuthApp;
- text = 'Authenticator application';
- } else if (method === 'backup_code') {
- icon = DotCircle;
- text = 'Backup code';
- } else {
- return null;
- }
-
- return (
- setSelectedMethod(method)}
- >
- {text}
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodeAccordion.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodeAccordion.tsx
deleted file mode 100644
index 3e4990cd35c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodeAccordion.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { PhoneNumberResource } from '@clerk/types';
-
-import { Badge, Col, Icon, localizationKeys } from '../../customizables';
-import { FormattedPhoneNumberText, useCardState } from '../../elements';
-import { Mobile } from '../../icons';
-import { useRouter } from '../../router';
-import { handleError } from '../../utils';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-
-// TODO(MFA): Until we implement 'Set default' across MFA factors,
-// we allow toggling the default status of a phone only if TOTP is not enabled
-
-type MfaPhoneCodeAccordionProps = {
- phone: PhoneNumberResource;
- showTOTP: boolean;
-};
-
-export const MfaPhoneCodeAccordion = ({ phone, showTOTP }: MfaPhoneCodeAccordionProps) => {
- const { navigate } = useRouter();
- const card = useCardState();
-
- const isDefault = !showTOTP && phone.defaultSecondFactor;
-
- return (
- ({ color: theme.colors.$blackAlpha700 })}
- />
- }
- title={
- <>
- SMS Code
- >
- }
- badge={isDefault ? : undefined}
- >
-
- {!showTOTP && (
- phone.makeDefaultSecondFactor().catch(err => handleError(err, [], card.setError))}
- />
- )}
-
- navigate(`multi-factor/${phone.id}/remove`)}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodePage.tsx
deleted file mode 100644
index 4e2472774f3..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaPhoneCodePage.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { PhoneNumberResource } from '@clerk/types';
-import React from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import type { LocalizationKey } from '../../customizables';
-import { Col, localizationKeys, Text } from '../../customizables';
-import {
- ArrowBlockButton,
- ContentPage,
- FormButtonContainer,
- NavigateToFlowStartButton,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { getFlagEmojiFromCountryIso, handleError, parsePhoneString, stringToFormattedPhoneString } from '../../utils';
-import { MfaBackupCodeList } from './MfaBackupCodeList';
-import { AddPhone, VerifyPhone } from './PhonePage';
-import { AddBlockButton } from './UserProfileBlockButtons';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const MfaPhoneCodePage = withCardStateProvider(() => {
- const ref = React.useRef();
- const wizard = useWizard({ defaultStep: 2 });
-
- return (
-
-
-
- wizard.goToStep(0)}
- onUnverifiedPhoneClick={phone => {
- ref.current = phone;
- wizard.goToStep(1);
- }}
- title={localizationKeys('userProfile.mfaPhoneCodePage.title')}
- resourceRef={ref}
- />
-
- }
- Breadcrumbs={UserProfileBreadcrumbs}
- />
-
- );
-});
-
-type AddMfaProps = {
- onAddPhoneClick: React.MouseEventHandler;
- onUnverifiedPhoneClick: (phone: PhoneNumberResource) => void;
- onSuccess: () => void;
- title: LocalizationKey;
- resourceRef: React.MutableRefObject;
-};
-
-const AddMfa = (props: AddMfaProps) => {
- const { onSuccess, title, onAddPhoneClick, onUnverifiedPhoneClick, resourceRef } = props;
- const card = useCardState();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const availableMethods = user.phoneNumbers.filter(p => !p.reservedForSecondFactor);
-
- const enableMfa = async (phone: PhoneNumberResource) => {
- if (phone.verification.status !== 'verified') {
- return onUnverifiedPhoneClick(phone);
- }
-
- card.setLoading(phone.id);
- try {
- await phone.setReservedForSecondFactor({ reserved: true }).then(() => {
- resourceRef.current = phone;
- onSuccess();
- });
- } catch (err) {
- handleError(err, [], card.setError);
- } finally {
- card.setIdle();
- }
- };
-
- return (
-
-
-
- {availableMethods.map(phone => {
- const formattedPhone = stringToFormattedPhoneString(phone.phoneNumber);
- const flag = getFlagEmojiFromCountryIso(parsePhoneString(phone.phoneNumber).iso);
-
- return (
- enableMfa(phone)}
- isLoading={card.loadingMetadata === phone.id}
- isDisabled={card.isLoading}
- leftIcon={
- ({ fontSize: theme.fontSizes.$sm })}
- >
- {flag}
-
- }
- >
- {formattedPhone}
-
- );
- })}
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaSection.tsx
deleted file mode 100644
index 40244d1ddaa..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaSection.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { useEnvironment } from '../../contexts';
-import { localizationKeys } from '../../customizables';
-import { ProfileSection } from '../../elements';
-import { useRouter } from '../../router';
-import { MfaBackupCodeAccordion } from './MfaBackupCodeAccordion';
-import { MfaPhoneCodeAccordion } from './MfaPhoneCodeAccordion';
-import { MfaTOTPAccordion } from './MfaTOTPAccordion';
-import { AddBlockButton } from './UserProfileBlockButtons';
-import { defaultFirst, getSecondFactors, getSecondFactorsAvailableToAdd } from './utils';
-
-export const MfaSection = () => {
- const { navigate } = useRouter();
- const {
- userSettings: { attributes },
- } = useEnvironment();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const secondFactors = getSecondFactors(attributes);
- const secondFactorsAvailableToAdd = getSecondFactorsAvailableToAdd(attributes, user);
-
- const showTOTP = secondFactors.includes('totp') && user.totpEnabled;
- const showBackupCode = secondFactors.includes('backup_code') && user.backupCodeEnabled;
-
- const mfaPhones = user.phoneNumbers
- .filter(ph => ph.verification.status === 'verified')
- .filter(ph => ph.reservedForSecondFactor)
- .sort(defaultFirst);
-
- return (
-
- {showTOTP && }
-
- {secondFactors.includes('phone_code') &&
- mfaPhones.map(phone => (
-
- ))}
-
- {showBackupCode && }
-
- {secondFactorsAvailableToAdd.length > 0 && (
- navigate('multi-factor')}
- />
- )}
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPAccordion.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPAccordion.tsx
deleted file mode 100644
index 8c5815e68fd..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPAccordion.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Badge, Col, Icon, localizationKeys, useLocalizations } from '../../customizables';
-import { AuthApp } from '../../icons';
-import { useRouter } from '../../router';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-
-// TODO(MFA): Until we implement 'Set default' across MFA factors, we treat TOTP as the default, if enabled
-
-export const MfaTOTPAccordion = () => {
- const { navigate } = useRouter();
- const { t } = useLocalizations();
-
- return (
- ({ color: theme.colors.$blackAlpha700 })}
- />
- }
- title={t(localizationKeys('userProfile.start.mfaSection.totp.headerTitle'))}
- badge={}
- >
-
-
-
- navigate(`multi-factor/totp/remove`)}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPPage.tsx
deleted file mode 100644
index 83fcec5287d..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/MfaTOTPPage.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { TOTPResource } from '@clerk/types';
-import React from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import { localizationKeys } from '../../customizables';
-import { SuccessPage, withCardStateProvider } from '../../elements';
-import { AddAuthenticatorApp } from './AddAuthenticatorApp';
-import { MfaBackupCodeList } from './MfaBackupCodeList';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-import { VerifyTOTP } from './VerifyTOTP';
-
-export const MfaTOTPPage = withCardStateProvider(() => {
- const wizard = useWizard();
- const ref = React.useRef();
-
- return (
-
-
-
-
-
-
- }
- Breadcrumbs={UserProfileBreadcrumbs}
- />
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordPage.tsx
deleted file mode 100644
index c56852a0714..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordPage.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import { useSession, useUser } from '@clerk/shared/react';
-import { useRef } from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { localizationKeys, useLocalizations } from '../../customizables';
-import {
- ContentPage,
- Form,
- FormButtonContainer,
- FormButtons,
- InformationBox,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useConfirmPassword, useNavigateToFlowStart } from '../../hooks';
-import { createPasswordError, handleError, useFormControl } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-const generateSuccessPageText = (userHasPassword: boolean, sessionSignOut: boolean) => {
- const localizedTexts = [];
-
- if (userHasPassword) {
- localizedTexts.push(localizationKeys('userProfile.passwordPage.changePasswordSuccessMessage'));
- } else {
- localizedTexts.push(localizationKeys('userProfile.passwordPage.successMessage'));
- }
-
- if (sessionSignOut) {
- localizedTexts.push(localizationKeys('userProfile.passwordPage.sessionsSignedOutSuccessMessage'));
- }
-
- return localizedTexts;
-};
-
-export const PasswordPage = withCardStateProvider(() => {
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { session } = useSession();
- const title = user.passwordEnabled
- ? localizationKeys('userProfile.passwordPage.changePasswordTitle')
- : localizationKeys('userProfile.passwordPage.title');
- const card = useCardState();
- const wizard = useWizard();
- const { navigateToFlowStart } = useNavigateToFlowStart();
-
- const passwordEditDisabled = user.samlAccounts.some(sa => sa.active);
-
- // Ensure that messages will not use the updated state of User after a password has been set or changed
- const successPagePropsRef = useRef[0]>({
- title: localizationKeys('userProfile.passwordPage.title'),
- });
-
- const currentPasswordField = useFormControl('currentPassword', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__currentPassword'),
- isRequired: true,
- });
-
- const {
- userSettings: { passwordSettings },
- } = useEnvironment();
-
- const passwordField = useFormControl('newPassword', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__newPassword'),
- isRequired: true,
- validatePassword: true,
- buildErrorMessage: errors => createPasswordError(errors, { t, locale, passwordSettings }),
- });
-
- const confirmField = useFormControl('confirmPassword', '', {
- type: 'password',
- label: localizationKeys('formFieldLabel__confirmPassword'),
- isRequired: true,
- });
-
- const sessionsField = useFormControl('signOutOfOtherSessions', '', {
- type: 'checkbox',
- label: localizationKeys('formFieldLabel__signOutOfOtherSessions'),
- defaultChecked: true,
- });
-
- const { setConfirmPasswordFeedback, isPasswordMatch } = useConfirmPassword({
- passwordField,
- confirmPasswordField: confirmField,
- });
-
- const { t, locale } = useLocalizations();
-
- const canSubmit =
- (user.passwordEnabled ? currentPasswordField.value && isPasswordMatch : isPasswordMatch) &&
- passwordField.value &&
- confirmField.value;
-
- const validateForm = () => {
- if (passwordField.value) {
- setConfirmPasswordFeedback(confirmField.value);
- }
- };
-
- const updatePassword = async () => {
- const opts = {
- newPassword: passwordField.value,
- signOutOfOtherSessions: sessionsField.checked,
- currentPassword: user.passwordEnabled ? currentPasswordField.value : undefined,
- } satisfies Parameters[0];
-
- try {
- successPagePropsRef.current = {
- title: user.passwordEnabled
- ? localizationKeys('userProfile.passwordPage.changePasswordTitle')
- : localizationKeys('userProfile.passwordPage.title'),
- text: generateSuccessPageText(user.passwordEnabled, !!sessionsField.checked),
- Breadcrumbs: UserProfileBreadcrumbs,
- };
-
- await user.updatePassword(opts);
- wizard.nextStep();
- } catch (e) {
- handleError(e, [currentPasswordField, passwordField, confirmField], card.setError);
- }
- };
-
- return (
-
-
- {passwordEditDisabled && }
-
-
- {/* For password managers */}
-
- {user.passwordEnabled && (
-
-
-
- )}
-
-
-
-
- {
- if (e.target.value) {
- setConfirmPasswordFeedback(e.target.value);
- }
- return confirmField.props.onChange(e);
- }}
- isRequired
- isDisabled={passwordEditDisabled}
- />
-
-
-
-
- {passwordEditDisabled ? (
-
-
-
- ) : (
-
- )}
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordSection.tsx
deleted file mode 100644
index 4ebf9b571ef..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/PasswordSection.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { localizationKeys, Text } from '../../customizables';
-import { ProfileSection } from '../../elements';
-import { Pencil } from '../../icons';
-import { useRouter } from '../../router';
-import { AddBlockButton } from './UserProfileBlockButtons';
-
-export const PasswordSection = () => {
- const { navigate } = useRouter();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { passwordEnabled } = user;
-
- const navigateToPage = () => {
- return navigate('password');
- };
-
- return (
-
- {passwordEnabled && ({ padding: `${t.space.$2} ${t.space.$4}` })}>••••••••••}
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/PhonePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/PhonePage.tsx
deleted file mode 100644
index dade4fe7231..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/PhonePage.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { PhoneNumberResource } from '@clerk/types';
-import React from 'react';
-
-import { useWizard, Wizard } from '../../common';
-import type { LocalizationKey } from '../../customizables';
-import { localizationKeys, Text } from '../../customizables';
-import { ContentPage, Form, FormButtons, SuccessPage, useCardState, withCardStateProvider } from '../../elements';
-import { useRouter } from '../../router';
-import { handleError, useFormControl } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-import { VerifyWithCode } from './VerifyWithCode';
-
-export const PhonePage = withCardStateProvider(() => {
- const { user } = useUser();
-
- const { params } = useRouter();
- const { id } = params || {};
-
- const phoneNumberRef = React.useRef(user?.phoneNumbers.find(a => a.id === id));
- const wizard = useWizard({ defaultStep: phoneNumberRef.current ? 1 : 0 });
-
- return (
-
-
-
-
-
- );
-});
-
-type AddPhoneProps = {
- title: LocalizationKey;
- resourceRef: React.MutableRefObject;
- onSuccess: () => void;
-};
-
-export const AddPhone = (props: AddPhoneProps) => {
- const { title, onSuccess, resourceRef } = props;
- const card = useCardState();
- const { user } = useUser();
-
- const phoneField = useFormControl('phoneNumber', '', {
- type: 'tel',
- label: localizationKeys('formFieldLabel__phoneNumber'),
- isRequired: true,
- });
-
- const canSubmit = phoneField.value.length > 1 && user?.username !== phoneField.value;
-
- const addPhone = async (e: React.FormEvent) => {
- e.preventDefault();
- return user
- ?.createPhoneNumber({ phoneNumber: phoneField.value })
- .then(res => {
- resourceRef.current = res;
- onSuccess();
- })
- .catch(e => handleError(e, [phoneField], card.setError));
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const VerifyPhone = (props: AddPhoneProps) => {
- const { title, onSuccess, resourceRef } = props;
-
- return (
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/PhoneSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/PhoneSection.tsx
deleted file mode 100644
index ee0202edfe7..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/PhoneSection.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { PhoneNumberResource } from '@clerk/types';
-
-import { Badge, Col, localizationKeys, Text } from '../../customizables';
-import { ProfileSection, useCardState } from '../../elements';
-import { useRouter } from '../../router';
-import { getFlagEmojiFromCountryIso, handleError, parsePhoneString, stringToFormattedPhoneString } from '../../utils';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-import { AddBlockButton } from './UserProfileBlockButtons';
-import { primaryIdentificationFirst } from './utils';
-
-export const PhoneSection = () => {
- const { user } = useUser();
- const { navigate } = useRouter();
-
- return (
-
- {user?.phoneNumbers.sort(primaryIdentificationFirst(user.primaryPhoneNumberId)).map(phone => (
-
- ))}
-
- navigate('phone-number')}
- />
-
- );
-};
-
-const PhoneAccordion = ({ phone }: { phone: PhoneNumberResource }) => {
- const card = useCardState();
- const { user } = useUser();
- const { navigate } = useRouter();
-
- if (!user) {
- return null;
- }
-
- const isPrimary = user.primaryPhoneNumberId === phone.id;
- const isVerified = phone.verification.status === 'verified';
- const setPrimary = () => {
- return user.update({ primaryPhoneNumberId: phone.id }).catch(e => handleError(e, [], card.setError));
- };
-
- const formattedPhone = stringToFormattedPhoneString(phone.phoneNumber);
- const flag = getFlagEmojiFromCountryIso(parsePhoneString(phone.phoneNumber).iso);
-
- return (
- ({ fontSize: theme.fontSizes.$sm })}
- >
- {flag}
-
- }
- title={formattedPhone}
- badge={
- <>
- {isPrimary && }
- {!isVerified && (
-
- )}
- >
- }
- >
-
- {isPrimary && isVerified && (
-
- )}
- {isPrimary && !isVerified && (
- navigate(`phone-number/${phone.id}`)}
- />
- )}
- {!isPrimary && isVerified && (
-
- )}
- {!isPrimary && !isVerified && (
- navigate(`phone-number/${phone.id}`)}
- />
- )}
- navigate(`phone-number/${phone.id}/remove`)}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/ProfilePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/ProfilePage.tsx
deleted file mode 100644
index f04149986df..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/ProfilePage.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import React from 'react';
-
-import { isDefaultImage } from '../../../utils';
-import { useWizard, Wizard } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { localizationKeys } from '../../customizables';
-import {
- ContentPage,
- Form,
- FormButtons,
- InformationBox,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { handleError, useFormControl } from '../../utils';
-import { UserProfileAvatarUploader } from './UserProfileAvatarUploader';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const ProfilePage = withCardStateProvider(() => {
- const title = localizationKeys('userProfile.profilePage.title');
- const card = useCardState();
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const [avatarChanged, setAvatarChanged] = React.useState(false);
- const { first_name, last_name } = useEnvironment().userSettings.attributes;
- const showFirstName = first_name.enabled;
- const showLastName = last_name.enabled;
- const userFirstName = user.firstName || '';
- const userLastName = user.lastName || '';
-
- const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
-
- const firstNameField = useFormControl('firstName', user.firstName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__firstName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__firstName'),
- isRequired: last_name.required,
- });
- const lastNameField = useFormControl('lastName', user.lastName || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__lastName'),
- placeholder: localizationKeys('formFieldInputPlaceholder__lastName'),
- isRequired: last_name.required,
- });
-
- const userInfoChanged =
- (showFirstName && firstNameField.value !== userFirstName) || (showLastName && lastNameField.value !== userLastName);
- const optionalFieldsChanged = userInfoChanged || avatarChanged;
-
- const hasRequiredFields = (showFirstName && first_name.required) || (showLastName && last_name.required);
- const requiredFieldsFilled =
- hasRequiredFields && !!lastNameField.value && !!firstNameField.value && optionalFieldsChanged;
-
- const nameEditDisabled = user.samlAccounts.some(sa => sa.active);
-
- const onSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- return (
- userInfoChanged
- ? user.update({ firstName: firstNameField.value, lastName: lastNameField.value })
- : Promise.resolve()
- )
- .then(() => {
- wizard.nextStep();
- })
- .catch(err => {
- handleError(err, [firstNameField, lastNameField], card.setError);
- });
- };
-
- const uploadAvatar = (file: File) => {
- return user
- .setProfileImage({ file })
- .then(() => {
- setAvatarChanged(true);
- card.setIdle();
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- const onAvatarRemove = () => {
- void user
- .setProfileImage({ file: null })
- .then(() => {
- setAvatarChanged(true);
- card.setIdle();
- })
- .catch(err => handleError(err, [], card.setError));
- };
-
- return (
-
-
- {nameEditDisabled && }
-
-
-
- {showFirstName && (
-
-
-
- )}
- {showLastName && (
-
-
-
- )}
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/RemoveResourcePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/RemoveResourcePage.tsx
deleted file mode 100644
index eccccbc34f6..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/RemoveResourcePage.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import React from 'react';
-
-import { RemoveResourcePage } from '../../common';
-import { localizationKeys } from '../../customizables';
-import { useEnabledThirdPartyProviders } from '../../hooks';
-import { useRouter } from '../../router';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const RemoveEmailPage = () => {
- const { user } = useUser();
- const { id } = useRouter().params;
- const resource = user?.emailAddresses.find(e => e.id === id);
- const ref = React.useRef(resource?.emailAddress);
-
- if (!ref.current) {
- return null;
- }
-
- return (
- Promise.resolve(resource?.destroy())}
- Breadcrumbs={UserProfileBreadcrumbs}
- />
- );
-};
-
-export const RemovePhonePage = () => {
- const { user } = useUser();
- const { id } = useRouter().params;
- const resource = user?.phoneNumbers.find(e => e.id === id);
- const ref = React.useRef(resource?.phoneNumber);
-
- if (!ref.current) {
- return null;
- }
-
- return (
- Promise.resolve(resource?.destroy())}
- Breadcrumbs={UserProfileBreadcrumbs}
- />
- );
-};
-
-export const RemoveConnectedAccountPage = () => {
- const { user } = useUser();
- const { id } = useRouter().params;
- const resource = user?.externalAccounts.find(e => e.id === id);
- const ref = React.useRef(resource?.provider);
- const { providerToDisplayData } = useEnabledThirdPartyProviders();
-
- if (!ref.current) {
- return null;
- }
-
- return (
- Promise.resolve(resource?.destroy())}
- Breadcrumbs={UserProfileBreadcrumbs}
- />
- );
-};
-
-export const RemoveWeb3WalletPage = () => {
- const { user } = useUser();
- const { id } = useRouter().params;
- const resource = user?.web3Wallets.find(e => e.id === id);
- const ref = React.useRef(resource?.web3Wallet);
-
- if (!ref.current) {
- return null;
- }
-
- return (
- Promise.resolve(resource?.destroy())}
- Breadcrumbs={UserProfileBreadcrumbs}
- />
- );
-};
-
-export const RemoveMfaPhoneCodePage = () => {
- const { user } = useUser();
- const { id } = useRouter().params;
- // TODO: This logic will need to change when we add more 2fa methods
- const resource = user?.phoneNumbers.find(e => e.id === id);
- const ref = React.useRef(resource?.phoneNumber);
-
- if (!ref.current) {
- return null;
- }
-
- return (
- Promise.resolve(resource?.setReservedForSecondFactor({ reserved: false }))}
- Breadcrumbs={UserProfileBreadcrumbs}
- />
- );
-};
-
-export const RemoveMfaTOTPPage = () => {
- const { user } = useUser();
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/RootPage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/RootPage.tsx
deleted file mode 100644
index 68022cb3b3e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/RootPage.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { useEnvironment } from '../../contexts';
-import { Col, descriptors, localizationKeys } from '../../customizables';
-import { CardAlert, Header, useCardState, withCardStateProvider } from '../../elements';
-import { NavbarMenuButtonRow } from '../../elements/Navbar';
-import { ActiveDevicesSection } from './ActiveDevicesSection';
-import { ConnectedAccountsSection } from './ConnectedAccountsSection';
-import { DeleteSection } from './DeleteSection';
-import { EmailsSection } from './EmailSection';
-import { EnterpriseAccountsSection } from './EnterpriseAccountsSection';
-import { MfaSection } from './MfaSection';
-import { PasswordSection } from './PasswordSection';
-import { PhoneSection } from './PhoneSection';
-import { UsernameSection } from './UsernameSection';
-import { UserProfileSection } from './UserProfileSection';
-import { getSecondFactors } from './utils';
-import { Web3Section } from './Web3Section';
-
-export const RootPage = withCardStateProvider(() => {
- const { attributes, saml, social, instanceIsPasswordBased } = useEnvironment().userSettings;
- const card = useCardState();
- const { user } = useUser();
- const showUsername = attributes.username.enabled;
- const showEmail = attributes.email_address.enabled;
- const showPhone = attributes.phone_number.enabled;
- const showConnectedAccounts = social && Object.values(social).filter(p => p.enabled).length > 0;
- const showSamlAccounts = saml && saml.enabled && user && user.samlAccounts.length > 0;
- const showWeb3 = attributes.web3_wallet.enabled;
- const showPassword = instanceIsPasswordBased;
- const showMfa = getSecondFactors(attributes).length > 0;
- const showDelete = user?.deleteSelfEnabled;
-
- return (
- ({ gap: t.space.$8 })}
- >
-
- {card.error}
-
-
- ({ marginBottom: t.space.$4 })}
- textVariant='h2'
- />
-
-
-
- {showUsername && }
- {showEmail && }
- {showPhone && }
- {showConnectedAccounts && }
- {showSamlAccounts && }
- {showWeb3 && }
-
-
-
-
-
- {showPassword && }
- {showMfa && }
-
- {showDelete && }
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfile.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfile.tsx
deleted file mode 100644
index 8a586db1fbf..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfile.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import type { UserProfileModalProps, UserProfileProps } from '@clerk/types';
-import React from 'react';
-
-import { withRedirectToHomeUserGuard } from '../../common';
-import { ComponentContext, withCoreUserGuard } from '../../contexts';
-import { Flow } from '../../customizables';
-import { ProfileCard, withCardStateProvider } from '../../elements';
-import { Route, Switch } from '../../router';
-import { mqu } from '../../styledSystem';
-import type { UserProfileCtx } from '../../types';
-import { UserProfileNavbar } from './UserProfileNavbar';
-import { UserProfileRoutes } from './UserProfileRoutes';
-import { VerificationSuccessPage } from './VerifyWithLink';
-
-const _UserProfile = (_: UserProfileProps) => {
- return (
-
-
-
- {/* PublicRoutes */}
-
-
-
-
-
-
-
-
-
- );
-};
-
-const AuthenticatedRoutes = withCoreUserGuard(() => {
- const contentRef = React.useRef(null);
- return (
- ({
- display: 'grid',
- gridTemplateColumns: '1fr 2.5fr',
- [mqu.md]: {
- display: 'block',
- },
- height: t.sizes.$176,
- overflow: 'hidden',
- })}
- >
-
-
-
-
- );
-});
-
-export const UserProfile = withRedirectToHomeUserGuard(withCardStateProvider(_UserProfile));
-
-export const UserProfileModal = (props: UserProfileModalProps): JSX.Element => {
- const userProfileProps: UserProfileCtx = {
- ...props,
- routing: 'virtual',
- componentName: 'UserProfile',
- mode: 'modal',
- };
-
- return (
-
-
- {/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAccordion.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAccordion.tsx
deleted file mode 100644
index 7718b4ff718..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAccordion.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useUserProfileContext } from '../../contexts';
-import { AccordionItem } from '../../elements';
-import type { PropsOfComponent } from '../../styledSystem';
-
-export const UserProfileAccordion = (props: PropsOfComponent) => {
- const isModal = useUserProfileContext().mode === 'modal';
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAvatarUploader.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAvatarUploader.tsx
deleted file mode 100644
index cc648023282..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileAvatarUploader.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { UserResource } from '@clerk/types';
-
-import type { AvatarUploaderProps } from '../../elements';
-import { AvatarUploader, UserAvatar } from '../../elements';
-import { localizationKeys } from '../../localization';
-
-export const UserProfileAvatarUploader = (
- props: Omit & { user: Partial },
-) => {
- const { user, ...rest } = props;
- return (
- theme.sizes.$12}
- {...user}
- />
- }
- />
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileBlockButtons.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileBlockButtons.tsx
deleted file mode 100644
index 37ef9b97279..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileBlockButtons.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../../common/BlockButtons';
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileNavbar.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileNavbar.tsx
deleted file mode 100644
index 3b747b16dd1..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileNavbar.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-
-import { useUserProfileContext } from '../../contexts';
-import { Breadcrumbs, NavBar, NavbarContextProvider } from '../../elements';
-import { localizationKeys } from '../../localization';
-import type { PropsOfComponent } from '../../styledSystem';
-
-export const UserProfileNavbar = (
- props: React.PropsWithChildren, 'contentRef'>>,
-) => {
- const { pages } = useUserProfileContext();
-
- return (
-
-
- {props.children}
-
- );
-};
-
-export const UserProfileBreadcrumbs = (props: Pick, 'title'>) => {
- const { pages } = useUserProfileContext();
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileRoutes.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileRoutes.tsx
deleted file mode 100644
index a9b293e3a30..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileRoutes.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { CustomPageContentContainer } from '../../common/CustomPageContentContainer';
-import { USER_PROFILE_NAVBAR_ROUTE_ID } from '../../constants';
-import { useUserProfileContext } from '../../contexts';
-import { ProfileCardContent } from '../../elements';
-import { Route, Switch } from '../../router';
-import type { PropsOfComponent } from '../../styledSystem';
-import { ConnectedAccountsPage } from './ConnectedAccountsPage';
-import { DeletePage } from './DeletePage';
-import { EmailPage } from './EmailPage';
-import { MfaBackupCodeCreatePage } from './MfaBackupCodeCreatePage';
-import { MfaPage } from './MfaPage';
-import { PasswordPage } from './PasswordPage';
-import { PhonePage } from './PhonePage';
-import { ProfilePage } from './ProfilePage';
-import {
- RemoveConnectedAccountPage,
- RemoveEmailPage,
- RemoveMfaPhoneCodePage,
- RemoveMfaTOTPPage,
- RemovePhonePage,
- RemoveWeb3WalletPage,
-} from './RemoveResourcePage';
-import { RootPage } from './RootPage';
-import { UsernamePage } from './UsernamePage';
-import { Web3Page } from './Web3Page';
-
-export const UserProfileRoutes = (props: PropsOfComponent) => {
- const { pages } = useUserProfileContext();
- const isAccountPageRoot =
- pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT ||
- pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.SECURITY;
-
- const customPageRoutesWithContents = pages.contents?.map((customPage, index) => {
- const shouldFirstCustomItemBeOnRoot = !isAccountPageRoot && index === 0;
- return (
-
-
-
- );
- });
-
- return (
-
-
- {customPageRoutesWithContents}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/**/}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileSection.tsx
deleted file mode 100644
index 0ac8d9f0c1f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UserProfileSection.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { Button, descriptors, Flex, localizationKeys } from '../../customizables';
-import { ProfileSection, UserPreview } from '../../elements';
-
-export const UserProfileSection = () => {
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
-
- return (
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernamePage.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernamePage.tsx
deleted file mode 100644
index ada84c70bb4..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernamePage.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { useWizard, Wizard } from '../../common';
-import { useEnvironment } from '../../contexts';
-import { localizationKeys } from '../../customizables';
-import { ContentPage, Form, FormButtons, SuccessPage, useCardState, withCardStateProvider } from '../../elements';
-import { handleError, useFormControl } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const UsernamePage = withCardStateProvider(() => {
- const { user } = useUser();
-
- if (!user) {
- return null;
- }
-
- const { userSettings } = useEnvironment();
- const card = useCardState();
- const wizard = useWizard();
- const usernameField = useFormControl('username', user.username || '', {
- type: 'text',
- label: localizationKeys('formFieldLabel__username'),
- placeholder: localizationKeys('formFieldInputPlaceholder__username'),
- });
-
- const isUsernameRequired = userSettings.attributes.username.required;
-
- const canSubmit =
- (isUsernameRequired ? usernameField.value.length > 1 : true) && user.username !== usernameField.value;
-
- const updatePassword = async () => {
- try {
- await user.update({ username: usernameField.value });
- wizard.nextStep();
- } catch (e) {
- handleError(e, [usernameField], card.setError);
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernameSection.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernameSection.tsx
deleted file mode 100644
index 16ece6ae83d..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/UsernameSection.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-
-import { localizationKeys, Text } from '../../customizables';
-import { ProfileSection } from '../../elements';
-import { Pencil } from '../../icons';
-import { useRouter } from '../../router';
-import { AddBlockButton } from './UserProfileBlockButtons';
-
-export const UsernameSection = () => {
- const { user } = useUser();
- const { navigate } = useRouter();
-
- if (!user) {
- return null;
- }
-
- return (
-
- {user.username && ({ padding: `${t.space.$2} ${t.space.$4}` })}>{user.username}}
- navigate('username')}
- leftIcon={user.username ? Pencil : undefined}
- textLocalizationKey={
- user.username
- ? localizationKeys('userProfile.start.usernameSection.primaryButton__changeUsername')
- : localizationKeys('userProfile.start.usernameSection.primaryButton__setUsername')
- }
- />
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyTOTP.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyTOTP.tsx
deleted file mode 100644
index 5c55a181038..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyTOTP.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { TOTPResource } from '@clerk/types';
-import React from 'react';
-
-import { Col, descriptors, localizationKeys } from '../../customizables';
-import { ContentPage, Form, FormButtonContainer, NavigateToFlowStartButton, useFieldOTP } from '../../elements';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-type VerifyTOTPProps = {
- onVerified: () => void;
- resourceRef: React.MutableRefObject;
-};
-
-export const VerifyTOTP = (props: VerifyTOTPProps) => {
- const { onVerified, resourceRef } = props;
- const { user } = useUser();
-
- const otp = useFieldOTP({
- onCodeEntryFinished: (code, resolve, reject) => {
- user
- ?.verifyTOTP({ code })
- .then((totp: TOTPResource) => resolve(totp))
- .catch(reject);
- },
- onResolve: a => {
- resourceRef.current = a;
- onVerified();
- },
- });
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithCode.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithCode.tsx
deleted file mode 100644
index 8dc2cb2ae8c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithCode.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { EmailAddressResource, PhoneNumberResource } from '@clerk/types';
-import React from 'react';
-
-import { descriptors, localizationKeys } from '../../customizables';
-import { Form, FormButtonContainer, NavigateToFlowStartButton, useCardState, useFieldOTP } from '../../elements';
-import { handleError } from '../../utils';
-
-type VerifyWithCodeProps = {
- nextStep: () => void;
- identification?: EmailAddressResource | PhoneNumberResource;
- identifier?: string;
- prepareVerification?: () => Promise | undefined;
-};
-
-export const VerifyWithCode = (props: VerifyWithCodeProps) => {
- const card = useCardState();
- const { nextStep, identification, identifier, prepareVerification } = props;
-
- const prepare = () => {
- return prepareVerification?.()?.catch(err => handleError(err, [], card.setError));
- };
-
- const otp = useFieldOTP({
- onCodeEntryFinished: (code, resolve, reject) => {
- identification
- ?.attemptVerification({ code: code })
- .then(() => resolve())
- .catch(reject);
- },
- onResendCodeClicked: prepare,
- onResolve: nextStep,
- });
-
- React.useEffect(() => {
- void prepare();
- }, []);
-
- return (
- <>
-
-
-
-
- >
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithLink.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithLink.tsx
deleted file mode 100644
index ea4d60d527a..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/VerifyWithLink.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { EmailAddressResource } from '@clerk/types';
-import React from 'react';
-
-import { EmailLinkStatusCard } from '../../common';
-import { buildEmailLinkRedirectUrl } from '../../common/redirects';
-import { useEnvironment, useUserProfileContext } from '../../contexts';
-import { descriptors, localizationKeys } from '../../customizables';
-import { FormButtonContainer, NavigateToFlowStartButton, useCardState, VerificationLink } from '../../elements';
-import { useEmailLink } from '../../hooks';
-import { handleError } from '../../utils';
-
-type VerifyWithLinkProps = {
- email: EmailAddressResource;
- nextStep: () => void;
-};
-
-export const VerifyWithLink = (props: VerifyWithLinkProps) => {
- const { email, nextStep } = props;
- const card = useCardState();
- const profileContext = useUserProfileContext();
- const { startEmailLinkFlow } = useEmailLink(email);
- const { displayConfig } = useEnvironment();
-
- React.useEffect(() => {
- startVerification();
- }, []);
-
- function startVerification() {
- /**
- * The following workaround is used in order to make magic links work when the
- * is used as a modal. In modals, the routing is virtual. For
- * magic links the flow needs to end by invoking the /verify path of the
- * that renders the . So, we use the userProfileUrl that
- * defaults to Clerk Hosted Pages /user as a fallback.
- */
- const { routing } = profileContext;
- const baseUrl = routing === 'virtual' ? displayConfig.userProfileUrl : '';
-
- const redirectUrl = buildEmailLinkRedirectUrl(profileContext, baseUrl);
- startEmailLinkFlow({ redirectUrl })
- .then(() => nextStep())
- .catch(err => handleError(err, [], card.setError));
- }
-
- return (
- <>
-
-
-
-
- >
- );
-};
-
-export const VerificationSuccessPage = () => {
- return (
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Page.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Page.tsx
deleted file mode 100644
index 8470733fd7e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Page.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { Web3Strategy, Web3WalletResource } from '@clerk/types';
-import React from 'react';
-
-import { generateSignatureWithMetamask, getMetamaskIdentifier } from '../../../utils/web3';
-import { useWizard, Wizard } from '../../common';
-import { Col, descriptors, Image, localizationKeys, Text } from '../../customizables';
-import {
- ArrowBlockButton,
- ContentPage,
- FormButtonContainer,
- NavigateToFlowStartButton,
- SuccessPage,
- useCardState,
- withCardStateProvider,
-} from '../../elements';
-import { useEnabledThirdPartyProviders } from '../../hooks';
-import { useRouter } from '../../router';
-import { getFieldError, handleError } from '../../utils';
-import { UserProfileBreadcrumbs } from './UserProfileNavbar';
-
-export const Web3Page = withCardStateProvider(() => {
- const { user } = useUser();
-
- const { params } = useRouter();
- const { id } = params || {};
-
- const ref = React.useRef(user?.web3Wallets.find(a => a.id === id));
- const wizard = useWizard({ defaultStep: ref.current ? 1 : 0 });
-
- return (
-
-
-
-
- );
-});
-
-const AddWeb3Wallet = (props: { nextStep: () => void }) => {
- const { nextStep } = props;
- const card = useCardState();
- const { user } = useUser();
- const { strategyToDisplayData } = useEnabledThirdPartyProviders();
-
- // TODO: This logic is very similar to AddConnectedAccount but only metamask is supported right now
- // const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[];
- // const connectedStrategies = user.web3Wallets.map(w => w.web3Wallet) as OAuthStrategy[];
- const unconnectedStrategies: Web3Strategy[] =
- user?.web3Wallets.filter(w => w.verification?.status === 'verified').length === 0
- ? ['web3_metamask_signature']
- : [];
- const connect = async (strategy: Web3Strategy) => {
- try {
- card.setLoading(strategy);
- const identifier = await getMetamaskIdentifier();
-
- if (!user) {
- throw new Error('user is not defined');
- }
-
- let web3Wallet = await user.createWeb3Wallet({ web3Wallet: identifier });
- web3Wallet = await web3Wallet.prepareVerification({ strategy: 'web3_metamask_signature' });
- const nonce = web3Wallet.verification.nonce as string;
- const signature = await generateSignatureWithMetamask({ identifier, nonce });
- await web3Wallet.attemptVerification({ signature });
- card.setIdle();
- nextStep();
- } catch (err) {
- card.setIdle();
- console.log(err);
- const fieldError = getFieldError(err);
- if (fieldError) {
- card.setError(fieldError.longMessage);
- } else {
- handleError(err, [], card.setError);
- }
- }
- };
-
- return (
-
-
-
- {unconnectedStrategies.map(strategy => (
- connect(strategy)}
- isLoading={card.loadingMetadata === strategy}
- isDisabled={card.isLoading}
- leftIcon={
- ({ width: theme.sizes.$5 })}
- />
- }
- >
- {`Connect ${strategyToDisplayData[strategy].name} wallet`}
-
- ))}
-
-
-
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Section.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Section.tsx
deleted file mode 100644
index 1548a9e8239..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/Web3Section.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useUser } from '@clerk/shared/react';
-import type { Web3WalletResource } from '@clerk/types';
-
-import { Col, Flex, Image, localizationKeys } from '../../customizables';
-import { ProfileSection } from '../../elements';
-import { useEnabledThirdPartyProviders } from '../../hooks';
-import { useRouter } from '../../router';
-import { LinkButtonWithDescription } from './LinkButtonWithDescription';
-import { UserProfileAccordion } from './UserProfileAccordion';
-import { AddBlockButton } from './UserProfileBlockButtons';
-
-export const Web3Section = () => {
- const { user } = useUser();
- const { navigate } = useRouter();
-
- return (
-
- {user?.web3Wallets.map(wallet => (
-
- ))}
- navigate(`web3-wallet`)}
- textLocalizationKey={localizationKeys('userProfile.start.web3WalletsSection.primaryButton')}
- />
-
- );
-};
-
-const Web3WalletAccordion = ({ wallet }: { wallet: Web3WalletResource }) => {
- const { navigate } = useRouter();
- const { strategyToDisplayData } = useEnabledThirdPartyProviders();
- const strategy = wallet.verification.strategy as keyof typeof strategyToDisplayData;
-
- return (
-
- ({ width: theme.sizes.$4 })}
- />
- {strategyToDisplayData[strategy].name} ({wallet.web3Wallet})
-
- }
- >
-
- navigate(`web3-wallet/${wallet.id}/remove`)}
- />
-
-
- );
-};
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ConnectedAccountsPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ConnectedAccountsPage.test.tsx
deleted file mode 100644
index b86d8f9840e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ConnectedAccountsPage.test.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { ExternalAccountResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { ConnectedAccountsPage } from '../ConnectedAccountsPage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withSocialProvider({ provider: 'google' });
- f.withUser({});
-});
-
-describe('ConnectedAccountsPage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /add connected account/i });
- });
-
- it('shows the "connect account" button', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- expect(screen.getByText(/connect google account/i).closest('button')).not.toBeNull();
- });
-
- describe('Actions', () => {
- it('calls the appropriate function upon pressing the "connect" button', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.createExternalAccount.mockResolvedValue({} as ExternalAccountResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByText(/connect google account/i));
- expect(fixtures.clerk.user?.createExternalAccount).toHaveBeenCalledWith({
- redirectUrl: window.location.href,
- strategy: 'oauth_google',
- additionalScopes: [],
- });
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/EmailPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/EmailPage.test.tsx
deleted file mode 100644
index 469a591fae4..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/EmailPage.test.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { EmailPage } from '../EmailPage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withEmailAddress();
- f.withUser({ email_addresses: [{ email_address: 'test@clerk.com' }] });
-});
-
-describe('EmailPage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /add email address/i });
- });
-
- describe('Inputs', () => {
- it('shows the input field for the new email address', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByLabelText(/email address/i);
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page upon pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('continue button is disabled by default', async () => {
- const { wrapper } = await createFixtures(initConfig);
- render(, { wrapper });
-
- expect(screen.getByText(/continue/i, { exact: false }).closest('button')).toHaveAttribute('disabled');
- });
-
- it('calls the appropriate function if continue is pressed', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
- fixtures.clerk.user!.createEmailAddress.mockReturnValueOnce(Promise.resolve({} as any));
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/email address/i), 'test+2@clerk.com');
- await userEvent.click(screen.getByText(/continue/i));
- expect(fixtures.clerk.user?.createEmailAddress).toHaveBeenCalledWith({ email: 'test+2@clerk.com' });
- });
- });
-
- it.todo('Test for verification of added email');
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaBackupCodeCreatePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaBackupCodeCreatePage.test.tsx
deleted file mode 100644
index 88001d59c6c..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaBackupCodeCreatePage.test.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { BackupCodeResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { MfaBackupCodeCreatePage } from '../MfaBackupCodeCreatePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
-});
-
-describe('MfaBackupCodeCreatePage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.createBackupCode.mockResolvedValueOnce({} as BackupCodeResource);
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.createBackupCode).toHaveBeenCalled());
- expect(await screen.findByText(/finish/i)).toBeInTheDocument(); //wait for state to be updated
- });
-
- it('shows the title', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.createBackupCode.mockResolvedValueOnce({} as BackupCodeResource);
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.createBackupCode).toHaveBeenCalled());
- expect(await screen.findByText(/finish/i)).toBeInTheDocument(); //wait for state to be updated
-
- screen.getByRole('heading', { name: /add backup code verification/i });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing finish', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.createBackupCode.mockResolvedValueOnce({} as BackupCodeResource);
- const { userEvent } = render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.createBackupCode).toHaveBeenCalled());
- expect(await screen.findByText(/finish/i)).toBeInTheDocument(); //wait for state to be updated
-
- await userEvent.click(screen.getByRole('button', { name: /finish/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
- });
-
- it.todo('Test the copy all/download/print buttons');
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaPage.test.tsx
deleted file mode 100644
index 35e7ebaa868..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/MfaPage.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import type { PhoneNumberResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { MfaPage } from '../MfaPage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true });
- f.withUser({ phone_numbers: [{ phone_number: '+306911111111', id: 'id' }] });
-});
-
-describe('MfaPage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getAllByText('Add SMS code verification');
- });
-
- describe('Actions', () => {
- it('shows the phone number of the user with a button', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- const phoneNumberEl = screen.getByText('+30 691 1111111');
- expect(phoneNumberEl.closest('button')).not.toBeNull();
- });
-
- it('shows the "add a phone number" button', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- const phoneNumberEl = screen.getByText(/add a phone number/i);
- expect(phoneNumberEl.closest('button')).not.toBeNull();
- });
-
- it('navigates to the root page upon clicking cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toBeCalledWith('/');
- });
-
- it('renders the "add a phone number" page upon clicking the button', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByText(/add a phone number/i));
- screen.getByLabelText(/phone number/i);
- });
-
- it('renders the "enabled" page upon clicking the button', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- //@ts-expect-error
- fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor = jest
- .fn()
- .mockResolvedValue({} as PhoneNumberResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByText(/\+30 691 1111111/i));
- expect(await screen.findByText(/enabled/i)).toBeInTheDocument();
- expect(fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor).toHaveBeenCalledWith({ reserved: true });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PasswordPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PasswordPage.test.tsx
deleted file mode 100644
index 1d4f7998e5e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PasswordPage.test.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-import type { UserResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-
-import { fireEvent, render, screen, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { runFakeTimers } from '../../../utils/test/runFakeTimers';
-import { ResetPassword } from '../../SignIn/ResetPassword';
-import { PasswordPage } from '../PasswordPage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withUser({});
-});
-
-const changePasswordConfig = createFixtures.config(f => {
- f.withUser({ password_enabled: true });
-});
-
-describe('PasswordPage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /Set password/i });
- expect(screen.queryByRole(/current password/i)).not.toBeInTheDocument();
- });
-
- it('shows setup of changing password', async () => {
- const { wrapper } = await createFixtures(changePasswordConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /change password/i });
- screen.getByLabelText(/current password/i);
- });
-
- it('renders a hidden identifier field', async () => {
- const identifier = 'test@clerk.com';
- const { wrapper } = await createFixtures(f => {
- f.startSignInWithEmailAddress({ identifier });
- });
- render(, { wrapper });
-
- const identifierField: HTMLInputElement = screen.getByTestId('hidden-identifier');
- expect(identifierField.value).toBe(identifier);
- });
-
- describe('with SAML', () => {
- it('prevents adding a password if user has active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withUser({
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: true,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByLabelText(/new password/i)).toBeDisabled();
- expect(screen.getByLabelText(/confirm password/i)).toBeDisabled();
- expect(screen.getByRole('checkbox', { name: /sign out of all other devices/i })).toBeDisabled();
-
- expect(
- screen.getByText(
- 'Your password can currently not be edited because you can sign in only via the enterprise connection.',
- ),
- ).toBeInTheDocument();
- });
-
- it('does not prevent adding a password if user has no active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withUser({
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: false,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByLabelText(/new password/i)).not.toBeDisabled();
- expect(screen.getByLabelText(/confirm password/i)).not.toBeDisabled();
- expect(screen.getByRole('checkbox', { name: /sign out of all other devices/i })).not.toBeDisabled();
-
- expect(
- screen.queryByText(
- 'Your password can currently not be edited because you can sign in only via the enterprise connection.',
- ),
- ).not.toBeInTheDocument();
- });
-
- it('prevents changing a password if user has active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withUser({
- password_enabled: true,
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: true,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByLabelText(/current password/i)).toBeDisabled();
- expect(screen.getByLabelText(/new password/i)).toBeDisabled();
- expect(screen.getByLabelText(/confirm password/i)).toBeDisabled();
- expect(screen.getByRole('checkbox', { name: /sign out of all other devices/i })).toBeDisabled();
-
- expect(
- screen.getByText(
- 'Your password can currently not be edited because you can sign in only via the enterprise connection.',
- ),
- ).toBeInTheDocument();
- });
-
- it('does not prevent changing a password if user has no active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withUser({
- password_enabled: true,
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: false,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByLabelText(/current password/i)).not.toBeDisabled();
- expect(screen.getByLabelText(/new password/i)).not.toBeDisabled();
- expect(screen.getByLabelText(/confirm password/i)).not.toBeDisabled();
- expect(screen.getByRole('checkbox', { name: /sign out of all other devices/i })).not.toBeDisabled();
-
- expect(
- screen.queryByText(
- 'Your password can currently not be edited because you can sign in only via the enterprise connection.',
- ),
- ).not.toBeInTheDocument();
- });
- });
-
- describe('Actions', () => {
- it('calls the appropriate function upon pressing continue and finish', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.update.mockResolvedValue({} as UserResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/new password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.updatePassword).toHaveBeenCalledWith({
- newPassword: 'testtest',
- signOutOfOtherSessions: true,
- });
-
- expect(await screen.findByText(/has been set/i));
- expect(await screen.findByText(/signed out/i));
- await userEvent.click(screen.getByRole('button', { name: /finish/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('updates passwords and leave other sessions intact', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.update.mockResolvedValue({} as UserResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/new password/i), 'testtest');
- await userEvent.type(screen.getByLabelText(/confirm password/i), 'testtest');
- await userEvent.click(screen.getByRole('checkbox', { name: /sign out of all other devices/i }));
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.updatePassword).toHaveBeenCalledWith({
- newPassword: 'testtest',
- signOutOfOtherSessions: false,
- });
- });
-
- it('results in error if the password is too small', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/new password/i), 'test');
- const confirmField = screen.getByLabelText(/confirm password/i);
- await userEvent.type(confirmField, 'test');
- fireEvent.blur(confirmField);
- await waitFor(() => {
- screen.getByText(/or more/i);
- });
- });
- });
-
- it('results in error if the passwords do not match and persists', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr');
- const confirmField = screen.getByLabelText(/confirm password/i);
- await userEvent.type(confirmField, 'testrwerrwqrwe');
- fireEvent.blur(confirmField);
- await waitFor(() => {
- screen.getByText(`Passwords don't match.`);
- });
-
- await userEvent.clear(confirmField);
- await waitFor(() => {
- screen.getByText(`Passwords don't match.`);
- });
- });
- }, 10000);
-
- it(`Displays "Password match" when password match and removes it if they stop`, async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- await runFakeTimers(async () => {
- const { userEvent } = render(, { wrapper });
- const passwordField = screen.getByLabelText(/new password/i);
-
- await userEvent.type(passwordField, 'testewrewr');
- const confirmField = screen.getByLabelText(/confirm password/i);
- await waitFor(() => {
- expect(screen.queryByText(`Passwords match.`)).not.toBeInTheDocument();
- });
-
- await userEvent.type(confirmField, 'testewrewr');
- await waitFor(() => {
- expect(screen.getByText(`Passwords match.`)).toBeInTheDocument();
- });
-
- await userEvent.type(confirmField, 'testrwerrwqrwe');
- await waitFor(() => {
- expect(screen.queryByText(`Passwords match.`)).not.toBeVisible();
- });
-
- await userEvent.type(passwordField, 'testrwerrwqrwe');
- fireEvent.blur(confirmField);
- await waitFor(() => {
- screen.getByText(`Passwords match.`);
- });
- });
- }, 10000);
-
- it('navigates to the root page upon pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PhonePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PhonePage.test.tsx
deleted file mode 100644
index 00cbaf6aa34..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/PhonePage.test.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { PhonePage } from '../PhonePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withPhoneNumber();
- f.withUser({ email_addresses: ['test@clerk.com'] });
-});
-
-describe('PhonePage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /add phone number/i });
- });
-
- describe('Inputs', () => {
- it('shows the input field for the new phone number', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByLabelText(/phone number/i);
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page upon pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('continue button is disabled by default', async () => {
- const { wrapper } = await createFixtures(initConfig);
- render(, { wrapper });
-
- expect(screen.getByText(/continue/i, { exact: false }).closest('button')).toHaveAttribute('disabled');
- });
-
- it('calls the appropriate function if continue is pressed', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
- fixtures.clerk.user?.createPhoneNumber.mockReturnValueOnce(Promise.resolve({} as any));
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/phone number/i), '6911111111');
- await userEvent.click(screen.getByText(/continue/i));
- expect(fixtures.clerk.user?.createPhoneNumber).toHaveBeenCalledWith({ phoneNumber: '+16911111111' }); //default is +1
- });
- });
-
- it.todo('Test for verification of added phone number');
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ProfilePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ProfilePage.test.tsx
deleted file mode 100644
index f5430473ff6..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/ProfilePage.test.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-import type { ImageResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { ProfilePage } from '../ProfilePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-describe('ProfilePage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /Update Profile/i });
- });
-
- describe('First and last name', () => {
- it('first and last name inputs exists if name is enabled', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- first_name: 'F',
- last_name: 'L',
- });
- });
- render(, { wrapper });
-
- const firstNameInput: HTMLInputElement = screen.getByLabelText(/first name/i);
- const lastNameInput: HTMLInputElement = screen.getByLabelText(/last name/i);
- expect(firstNameInput.value).toBe('F');
- expect(lastNameInput.value).toBe('L');
- });
- });
-
- describe('with SAML', () => {
- it('disables the first & last name inputs if user has active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
- const firstName = 'George';
- const lastName = 'Clerk';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withName();
- f.withUser({
- first_name: firstName,
- last_name: lastName,
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: true,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByRole('textbox', { name: 'First name' })).toBeDisabled();
- expect(screen.getByRole('textbox', { name: 'Last name' })).toBeDisabled();
-
- screen.getByText('Your profile information has been provided by the enterprise connection and cannot be edited.');
- });
-
- it('does not disable the first & last name inputs if user has no active enterprise connections', async () => {
- const emailAddress = 'george@jungle.com';
- const firstName = 'George';
- const lastName = 'Clerk';
-
- const config = createFixtures.config(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withName();
- f.withUser({
- first_name: firstName,
- last_name: lastName,
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- active: false,
- email_address: emailAddress,
- },
- ],
- });
- });
-
- const { wrapper } = await createFixtures(config);
-
- render(, { wrapper });
-
- expect(screen.getByRole('textbox', { name: 'First name' })).not.toBeDisabled();
- expect(screen.getByRole('textbox', { name: 'Last name' })).not.toBeDisabled();
-
- expect(
- screen.queryByText(
- 'Your profile information has been provided by the enterprise connection and cannot be edited.',
- ),
- ).not.toBeInTheDocument();
- });
- });
-
- describe('Profile image', () => {
- it('shows the image', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUser({
- email_addresses: ['test@clerk.com'],
- image_url: 'https://clerk.com',
- first_name: 'F',
- last_name: 'L',
- });
- });
- render(, { wrapper });
-
- screen.getByRole('img', { name: 'F L' });
- });
-
- it('clicking "Upload image" opens the "Upload" section', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- const { userEvent } = render(, { wrapper });
-
- expect(screen.queryByText(/select file/i)).toBeNull();
- await userEvent.click(screen.getByText(/upload image/i));
- screen.getByText(/select file/i);
- });
-
- it('clicking "Remove image" calls the appropriate function', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({
- email_addresses: ['test@clerk.com'],
- image_url:
- 'https://img.clerkstage.dev/70726f78792f68747470733a2f2f696d616765732e6c636c636c65726b2e636f6d2f75706c6f616465642f696d675f324f4559646f346e575263766579536c6a366b7775757a336e79472e6a706567',
- });
- });
- fixtures.clerk.user?.setProfileImage.mockReturnValueOnce(Promise.resolve({} as ImageResource));
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByText(/remove image/i));
- expect(fixtures.clerk.user?.setProfileImage).toHaveBeenCalledWith({ file: null });
- });
-
- it.skip('"Remove image" is not shown when a default image exists', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({
- email_addresses: ['test@clerk.com'],
- image_url:
- 'https://img.clerkstage.dev/64656661756c742f696e735f3248326461375851494c494b727555654e464967456b73396878362f757365725f3249454d6b59705573514465427162327564677843717565345757?initials=GD',
- });
- });
- fixtures.clerk.user?.setProfileImage.mockReturnValueOnce(Promise.resolve({} as ImageResource));
- render(, { wrapper });
-
- expect(screen.queryByText(/remove image/i)).not.toBeInTheDocument();
- });
- });
-
- describe('Form Buttons', () => {
- it('form buttons appear as expected', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- render(, { wrapper });
-
- screen.getByRole('button', { name: /cancel/i });
- screen.getByRole('button', { name: /continue/i });
- });
-
- it('pressing cancel navigates to the root page', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('pressing cancel navigates to the root page', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('continue button is disabled by default', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- render(, { wrapper });
-
- expect(screen.getByText(/continue/i, { exact: false }).closest('button')).toHaveAttribute('disabled');
- });
-
- it("continue button is enabled after changing an input field's value", async () => {
- const { wrapper } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- });
- });
- const { userEvent } = render(, { wrapper });
-
- expect(screen.getByText(/continue/i, { exact: false }).closest('button')).toHaveAttribute('disabled');
- await userEvent.type(screen.getByText(/First name/i), 'George');
- expect(screen.getByText(/continue/i, { exact: false }).closest('button')).not.toHaveAttribute('disabled');
- });
-
- it('calls the appropriate function if continue is pressed', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withName();
- f.withUser({
- email_addresses: ['test@clerk.com'],
- first_name: 'F',
- last_name: 'L',
- });
- });
- fixtures.clerk.user?.update.mockReturnValueOnce(Promise.resolve({} as any));
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/first name/i), 'George');
- await userEvent.type(screen.getByLabelText(/last name/i), 'Clerk');
- await userEvent.click(screen.getByText(/continue/i));
- expect(fixtures.clerk.user?.update).toHaveBeenCalledWith({ firstName: 'FGeorge', lastName: 'LClerk' });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveConnectedAccountPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveConnectedAccountPage.test.tsx
deleted file mode 100644
index c73a9001665..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveConnectedAccountPage.test.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RemoveConnectedAccountPage } from '../RemoveResourcePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withSocialProvider({ provider: 'google' });
- f.withUser({ external_accounts: [{ provider: 'google', id: 'id' }] });
-});
-
-describe('RemoveConnectedAccountPage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /remove connected account/i });
- });
-
- describe('User information', () => {
- it('references the external account of the user in the message', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByText(/google/i);
- screen.getByText(/will be removed/i);
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('calls the appropriate function upon pressing continue', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- fixtures.clerk.user?.externalAccounts[0].destroy.mockResolvedValue();
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.externalAccounts[0].destroy).toHaveBeenCalled();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveEmailPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveEmailPage.test.tsx
deleted file mode 100644
index 2742c3dff03..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveEmailPage.test.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RemoveEmailPage } from '../RemoveResourcePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withEmailAddress();
- f.withUser({ email_addresses: [{ email_address: 'test@clerk.com', id: 'id' }] });
-});
-
-describe('RemoveEmailPage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /remove email address/i });
- });
-
- describe('User information', () => {
- it('references the email of the user in the message', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByText(/test@clerk.com/);
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('calls the appropriate function upon pressing continue', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- fixtures.clerk.user?.emailAddresses[0].destroy.mockResolvedValue();
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.emailAddresses[0].destroy).toHaveBeenCalled();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaPhoneCodePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaPhoneCodePage.test.tsx
deleted file mode 100644
index 8cd3cdf7716..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaPhoneCodePage.test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RemoveMfaPhoneCodePage } from '../RemoveResourcePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withPhoneNumber();
- f.withUser({ phone_numbers: [{ phone_number: '+306911111111', id: 'id' }] });
-});
-
-describe('RemoveMfaPhoneCodePage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title and phone number', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /remove two-step verification/i });
- screen.getByText(/\+306911111111/);
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('calls the appropriate function upon pressing continue', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- //@ts-expect-error
- fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor = jest.fn().mockResolvedValue({});
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor).toHaveBeenCalledWith({ reserved: false });
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaTOTPPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaTOTPPage.test.tsx
deleted file mode 100644
index afc6d7a728e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemoveMfaTOTPPage.test.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { DeletedObjectResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RemoveMfaTOTPPage } from '../RemoveResourcePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
-});
-
-describe('RemoveMfaTOTPPAge', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /remove two-step verification/i });
- screen.getByText(/authenticator/i);
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('calls the appropriate function when pressing continue', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.disableTOTP.mockResolvedValueOnce({} as DeletedObjectResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.disableTOTP).toHaveBeenCalled();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemovePhonePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemovePhonePage.test.tsx
deleted file mode 100644
index 890b0a8ea70..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RemovePhonePage.test.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RemovePhonePage } from '../RemoveResourcePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withPhoneNumber();
- f.withUser({ phone_numbers: [{ phone_number: '+30 691 1111111', id: 'id' }] });
-});
-
-describe('RemovePhonePage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /remove phone number/i });
- });
-
- describe('User information', () => {
- it('references the phone number of the user in the message', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- render(, { wrapper });
-
- screen.getByText(/\+30 691 1111111/);
- });
- });
-
- describe('Form buttons', () => {
- it('navigates to the root page when pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('calls the appropriate function upon pressing continue', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.router.params.id = 'id';
- fixtures.clerk.user?.phoneNumbers[0].destroy.mockResolvedValue();
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.phoneNumbers[0].destroy).toHaveBeenCalled();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RootPage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RootPage.test.tsx
deleted file mode 100644
index c6911994a3e..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/RootPage.test.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import type { SessionWithActivitiesResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen, waitFor } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { RootPage } from '../RootPage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-describe('RootPage', () => {
- it('renders the component', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- fixtures.clerk.user?.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- });
-
- describe('Sections', () => {
- it('shows the bigger sections', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getAllByText(/Account/i);
- screen.getAllByText(/Security/i);
- });
-
- it('shows the profile section along with the identifier of the user and has a button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'], first_name: 'George', last_name: 'Clerk' });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Profile/i);
- const button = screen.getByText('George Clerk');
- expect(button.closest('button')).not.toBeNull();
- });
-
- it('shows the profile section along with the identifier of the user and has a button', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'], first_name: 'George', last_name: 'Clerk' });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Profile/i);
- const button = screen.getByText('George Clerk');
- expect(button.closest('button')).not.toBeNull();
- });
-
- it('shows the email addresses section with the email addresses of the user and has appropriate buttons', async () => {
- const emails = ['test@clerk.com', 'test2@clerk.com'];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withUser({
- email_addresses: emails,
- first_name: 'George',
- last_name: 'Clerk',
- });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Email addresses/i);
- const emailButtons: HTMLElement[] = [];
- emails.forEach(email => {
- emailButtons.push(screen.getByText(email));
- });
- emailButtons.forEach(emailButton => {
- expect(emailButton.closest('button')).not.toBeNull();
- });
- });
-
- it('shows the phone numbers section with the phone numbers of the user and has appropriate buttons', async () => {
- const numbers = ['+30 691 1111111', '+30 692 2222222'];
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withPhoneNumber();
- f.withUser({
- phone_numbers: numbers,
- first_name: 'George',
- last_name: 'Clerk',
- });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Phone numbers/i);
- const numberButtons: HTMLElement[] = [];
- numbers.forEach(number => {
- numberButtons.push(screen.getByText(number));
- });
- numberButtons.forEach(numberButton => {
- expect(numberButton.closest('button')).not.toBeNull();
- });
- });
-
- it('shows the connected accounts of the user and has appropriate buttons', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withSocialProvider({ provider: 'google' });
- f.withUser({
- external_accounts: [{ provider: 'google', email_address: 'testgoogle@clerk.com' }],
- first_name: 'George',
- last_name: 'Clerk',
- });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Connected Accounts/i);
- screen.getByText(/testgoogle@clerk.com/i);
- const externalAccountButton = screen.getByText(/google/i);
- expect(externalAccountButton.closest('button')).not.toBeNull();
- });
-
- it('shows the enterprise accounts of the user', async () => {
- const emailAddress = 'george@jungle.com';
- const firstName = 'George';
- const lastName = 'Clerk';
-
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withEmailAddress();
- f.withSaml();
- f.withUser({
- email_addresses: [emailAddress],
- saml_accounts: [
- {
- id: 'samlacc_foo',
- provider: 'saml_okta',
- email_address: emailAddress,
- first_name: firstName,
- last_name: lastName,
- },
- ],
- first_name: firstName,
- last_name: lastName,
- });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([]));
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Enterprise Accounts/i);
- screen.getByText(/Okta Workforce/i);
- });
-
- it('shows the active devices of the user and has appropriate buttons', async () => {
- const { wrapper, fixtures } = await createFixtures(f => {
- f.withSocialProvider({ provider: 'google' });
- f.withUser({
- external_accounts: ['google'],
- first_name: 'George',
- last_name: 'Clerk',
- });
- });
- fixtures.clerk.user!.getSessions.mockReturnValue(
- Promise.resolve([
- {
- pathRoot: '/me/sessions',
- id: 'sess_2HyQfBh8wRJUbpvCtPNllWdsHFK',
- status: 'active',
- expireAt: '2022-12-01T01:55:44.636Z',
- abandonAt: '2022-12-24T01:55:44.636Z',
- lastActiveAt: '2022-11-24T12:11:49.328Z',
- latestActivity: {
- id: 'sess_activity_2HyQwElm529O5NDL1KNpJAGWVJZ',
- deviceType: 'Macintosh',
- browserName: 'Chrome',
- browserVersion: '107.0.0.0',
- country: 'Greece',
- city: 'Athens',
- isMobile: false,
- },
- actor: null,
- } as any as SessionWithActivitiesResource,
- ]),
- );
-
- render(, { wrapper });
- await waitFor(() => expect(fixtures.clerk.user?.getSessions).toHaveBeenCalled());
- screen.getByText(/Active Devices/i);
- expect(await screen.findByText(/Chrome/i)).toBeInTheDocument();
- screen.getByText(/107.0.0.0/i);
- screen.getByText(/Athens/i);
- screen.getByText(/Greece/i);
- const externalAccountButton = await screen.findByText(/Macintosh/i);
- expect(externalAccountButton.closest('button')).not.toBeNull();
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UserProfile.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UserProfile.test.tsx
deleted file mode 100644
index f379005c76b..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UserProfile.test.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { CustomPage } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { UserProfile } from '../UserProfile';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-describe('UserProfile', () => {
- describe('Navigation', () => {
- it('includes buttons for the bigger sections', async () => {
- const { wrapper } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.com'] });
- });
-
- render(, { wrapper });
- const accountElements = screen.getAllByText(/Account/i);
- expect(accountElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- const securityElements = screen.getAllByText(/Security/i);
- expect(securityElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- });
-
- it('includes custom nav items', async () => {
- const { wrapper, props } = await createFixtures(f => {
- f.withUser({ email_addresses: ['test@clerk.dev'] });
- });
-
- const customPages: CustomPage[] = [
- {
- label: 'Custom1',
- url: 'custom1',
- mount: () => undefined,
- unmount: () => undefined,
- mountIcon: () => undefined,
- unmountIcon: () => undefined,
- },
- {
- label: 'ExternalLink',
- url: '/link',
- mountIcon: () => undefined,
- unmountIcon: () => undefined,
- },
- ];
-
- props.setProps({ customPages });
- render(, { wrapper });
- const accountElements = screen.getAllByText(/Account/i);
- expect(accountElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- const securityElements = screen.getAllByText(/Security/i);
- expect(securityElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- const customElements = screen.getAllByText(/Custom1/i);
- expect(customElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- const externalElements = screen.getAllByText(/ExternalLink/i);
- expect(externalElements.some(el => el.tagName.toUpperCase() === 'BUTTON')).toBe(true);
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UsernamePage.test.tsx b/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UsernamePage.test.tsx
deleted file mode 100644
index 495607c0f6f..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/__tests__/UsernamePage.test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { UserResource } from '@clerk/types';
-import { describe, it } from '@jest/globals';
-import React from 'react';
-
-import { render, screen } from '../../../../testUtils';
-import { bindCreateFixtures } from '../../../utils/test/createFixtures';
-import { UsernamePage } from '../UsernamePage';
-
-const { createFixtures } = bindCreateFixtures('UserProfile');
-
-const initConfig = createFixtures.config(f => {
- f.withUser({ username: 'georgeclerk' });
-});
-
-describe('UsernamePage', () => {
- it('renders the component', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
- });
-
- it('shows the title', async () => {
- const { wrapper } = await createFixtures(initConfig);
-
- render(, { wrapper });
-
- screen.getByRole('heading', { name: /Update username/i });
- });
-
- describe('Actions', () => {
- it('calls the appropriate function upon pressing continue and finish', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- fixtures.clerk.user?.update.mockResolvedValue({} as UserResource);
- const { userEvent } = render(, { wrapper });
-
- await userEvent.type(screen.getByLabelText(/username/i), 'test');
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- expect(fixtures.clerk.user?.update).toHaveBeenCalledWith({ username: 'georgeclerktest' });
-
- expect(await screen.findByText(/updated/i));
- await userEvent.click(screen.getByRole('button', { name: /finish/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
-
- it('navigates to the root page upon pressing cancel', async () => {
- const { wrapper, fixtures } = await createFixtures(initConfig);
-
- const { userEvent } = render(, { wrapper });
-
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- expect(fixtures.router.navigate).toHaveBeenCalledWith('/');
- });
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/index.ts b/packages/clerk-js/src/ui.retheme/components/UserProfile/index.ts
deleted file mode 100644
index 884472841f1..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './UserProfile';
diff --git a/packages/clerk-js/src/ui.retheme/components/UserProfile/utils.ts b/packages/clerk-js/src/ui.retheme/components/UserProfile/utils.ts
deleted file mode 100644
index 2b653e822bb..00000000000
--- a/packages/clerk-js/src/ui.retheme/components/UserProfile/utils.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { Attributes, EnvironmentResource, PhoneNumberResource, UserResource } from '@clerk/types';
-
-type IDable = { id: string };
-
-export const primaryIdentificationFirst = (primaryId: string | null) => (val1: IDable, val2: IDable) => {
- return primaryId === val1.id ? -1 : primaryId === val2.id ? 1 : 0;
-};
-
-export const currentSessionFirst = (id: string) => (a: IDable) => a.id === id ? -1 : 1;
-
-export const defaultFirst = (a: PhoneNumberResource) => (a.defaultSecondFactor ? -1 : 1);
-
-export function emailLinksEnabledForInstance(env: EnvironmentResource): boolean {
- const { userSettings } = env;
- const { email_address } = userSettings.attributes;
- return email_address.enabled && email_address.verifications.includes('email_link');
-}
-
-export function getSecondFactors(attributes: Attributes): string[] {
- const secondFactors: string[] = [];
-
- Object.entries(attributes).forEach(([, attr]) => {
- attr.used_for_second_factor ? secondFactors.push(...attr.second_factors) : null;
- });
-
- return secondFactors;
-}
-
-export function getSecondFactorsAvailableToAdd(attributes: Attributes, user: UserResource): string[] {
- let sfs = getSecondFactors(attributes);
-
- // If user.totp_enabled, skip totp from the list of choices
- if (user.totpEnabled) {
- sfs = sfs.filter(f => f !== 'totp');
- }
-
- // Remove backup codes if already enabled or user doesn't have another MFA method added
- if (user.backupCodeEnabled || !user.twoFactorEnabled) {
- sfs = sfs.filter(f => f !== 'backup_code');
- }
-
- return sfs;
-}
diff --git a/packages/clerk-js/src/ui.retheme/constants.ts b/packages/clerk-js/src/ui.retheme/constants.ts
deleted file mode 100644
index 17d1dea5121..00000000000
--- a/packages/clerk-js/src/ui.retheme/constants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const USER_PROFILE_NAVBAR_ROUTE_ID = {
- ACCOUNT: 'account',
- SECURITY: 'security',
-};
-
-export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = {
- MEMBERS: 'members',
- SETTINGS: 'settings',
-};
diff --git a/packages/clerk-js/src/ui.retheme/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/ClerkUIComponentsContext.tsx
deleted file mode 100644
index 5b0d548996e..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/ClerkUIComponentsContext.tsx
+++ /dev/null
@@ -1,469 +0,0 @@
-import { useClerk } from '@clerk/shared/react';
-import { snakeToCamel } from '@clerk/shared/underscore';
-import type { OrganizationResource, UserResource } from '@clerk/types';
-import React, { useMemo } from 'react';
-
-import { SIGN_IN_INITIAL_VALUE_KEYS, SIGN_UP_INITIAL_VALUE_KEYS } from '../../core/constants';
-import { buildAuthQueryString, buildURL, createDynamicParamParser, pickRedirectionProp } from '../../utils';
-import { useEnvironment, useOptions } from '../contexts';
-import type { NavbarRoute } from '../elements';
-import type { ParsedQs } from '../router';
-import { useRouter } from '../router';
-import type {
- AvailableComponentCtx,
- CreateOrganizationCtx,
- OrganizationListCtx,
- OrganizationProfileCtx,
- OrganizationSwitcherCtx,
- SignInCtx,
- SignUpCtx,
- UserButtonCtx,
- UserProfileCtx,
-} from '../types';
-import type { CustomPageContent } from '../utils';
-import { createOrganizationProfileCustomPages, createUserProfileCustomPages } from '../utils';
-
-const populateParamFromObject = createDynamicParamParser({ regex: /:(\w+)/ });
-
-export const ComponentContext = React.createContext(null);
-
-const getInitialValuesFromQueryParams = (queryString: string, params: string[]) => {
- const props: Record = {};
- const searchParams = new URLSearchParams(queryString);
- searchParams.forEach((value, key) => {
- if (params.includes(key) && typeof value === 'string') {
- props[snakeToCamel(key)] = value;
- }
- });
-
- return props;
-};
-
-export type SignUpContextType = SignUpCtx & {
- navigateAfterSignUp: () => any;
- queryParams: ParsedQs;
- signInUrl: string;
- secondFactorUrl: string;
- authQueryString: string | null;
-};
-
-export const useSignUpContext = (): SignUpContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as SignUpCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
- const { queryParams, queryString } = useRouter();
- const options = useOptions();
- const clerk = useClerk();
-
- const initialValuesFromQueryParams = useMemo(
- () => getInitialValuesFromQueryParams(queryString, SIGN_UP_INITIAL_VALUE_KEYS),
- [],
- );
-
- if (componentName !== 'SignUp') {
- throw new Error('Clerk: useSignUpContext called outside of the mounted SignUp component.');
- }
-
- const afterSignUpUrl = clerk.buildUrlWithAuth(
- pickRedirectionProp('afterSignUpUrl', {
- queryParams,
- ctx,
- options,
- }) || '/',
- );
-
- const afterSignInUrl = clerk.buildUrlWithAuth(
- pickRedirectionProp('afterSignInUrl', {
- queryParams,
- ctx,
- options,
- }) || '/',
- );
-
- const navigateAfterSignUp = () => navigate(afterSignUpUrl);
-
- let signInUrl = pickRedirectionProp('signInUrl', { ctx, options, displayConfig }, false);
-
- // Add query strings to the sign in URL
- const authQs = buildAuthQueryString({
- afterSignInUrl: afterSignInUrl,
- afterSignUpUrl: afterSignUpUrl,
- displayConfig: displayConfig,
- });
-
- // Todo: Look for a better way than checking virtual
- if (authQs && ctx.routing != 'virtual') {
- signInUrl += `#/?${authQs}`;
- }
-
- // TODO: Avoid building this url again to remove duplicate code. Get it from window.Clerk instead.
- const secondFactorUrl = buildURL({ base: signInUrl, hashPath: '/factor-two' }, { stringify: true });
-
- return {
- ...ctx,
- componentName,
- signInUrl,
- secondFactorUrl,
- afterSignUpUrl,
- afterSignInUrl,
- navigateAfterSignUp,
- queryParams,
- initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
- authQueryString: authQs,
- };
-};
-
-export type SignInContextType = SignInCtx & {
- navigateAfterSignIn: () => any;
- queryParams: ParsedQs;
- signUpUrl: string;
- signUpContinueUrl: string;
- authQueryString: string | null;
-};
-
-export const useSignInContext = (): SignInContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as SignInCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
- const { queryParams, queryString } = useRouter();
- const options = useOptions();
- const clerk = useClerk();
-
- const initialValuesFromQueryParams = useMemo(
- () => getInitialValuesFromQueryParams(queryString, SIGN_IN_INITIAL_VALUE_KEYS),
- [],
- );
-
- if (componentName !== 'SignIn') {
- throw new Error('Clerk: useSignInContext called outside of the mounted SignIn component.');
- }
-
- const afterSignUpUrl = clerk.buildUrlWithAuth(
- pickRedirectionProp('afterSignUpUrl', {
- queryParams,
- ctx,
- options,
- }) || '/',
- );
-
- const afterSignInUrl = clerk.buildUrlWithAuth(
- pickRedirectionProp('afterSignInUrl', {
- queryParams,
- ctx,
- options,
- }) || '/',
- );
-
- const navigateAfterSignIn = () => navigate(afterSignInUrl);
-
- let signUpUrl = pickRedirectionProp('signUpUrl', { ctx, options, displayConfig }, false);
-
- // Add query strings to the sign in URL
- const authQs = buildAuthQueryString({
- afterSignInUrl: afterSignInUrl,
- afterSignUpUrl: afterSignUpUrl,
- displayConfig: displayConfig,
- });
- if (authQs && ctx.routing !== 'virtual') {
- signUpUrl += `#/?${authQs}`;
- }
-
- const signUpContinueUrl = buildURL({ base: signUpUrl, hashPath: '/continue' }, { stringify: true });
-
- return {
- ...ctx,
- componentName,
- signUpUrl,
- afterSignInUrl,
- afterSignUpUrl,
- navigateAfterSignIn,
- signUpContinueUrl,
- queryParams,
- initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
- authQueryString: authQs,
- };
-};
-
-type PagesType = {
- routes: NavbarRoute[];
- contents: CustomPageContent[];
- pageToRootNavbarRouteMap: Record;
-};
-
-export type UserProfileContextType = UserProfileCtx & {
- queryParams: ParsedQs;
- authQueryString: string | null;
- pages: PagesType;
-};
-
-export const useUserProfileContext = (): UserProfileContextType => {
- const { componentName, customPages, ...ctx } = (React.useContext(ComponentContext) || {}) as UserProfileCtx;
- const { queryParams } = useRouter();
-
- if (componentName !== 'UserProfile') {
- throw new Error('Clerk: useUserProfileContext called outside of the mounted UserProfile component.');
- }
-
- const pages = createUserProfileCustomPages(customPages || []);
-
- return {
- ...ctx,
- pages,
- componentName,
- queryParams,
- authQueryString: '',
- };
-};
-
-export const useUserButtonContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as UserButtonCtx;
- const clerk = useClerk();
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
- const options = useOptions();
-
- if (componentName !== 'UserButton') {
- throw new Error('Clerk: useUserButtonContext called outside of the mounted UserButton component.');
- }
-
- const signInUrl = pickRedirectionProp('signInUrl', { ctx, options, displayConfig }, false);
- const userProfileUrl = ctx.userProfileUrl || displayConfig.userProfileUrl;
-
- const afterMultiSessionSingleSignOutUrl = ctx.afterMultiSessionSingleSignOutUrl || displayConfig.afterSignOutOneUrl;
- const navigateAfterMultiSessionSingleSignOut = () => clerk.redirectWithAuth(afterMultiSessionSingleSignOutUrl);
-
- const afterSignOutUrl = ctx.afterSignOutUrl || '/';
- const navigateAfterSignOut = () => navigate(afterSignOutUrl);
-
- const afterSwitchSessionUrl = ctx.afterSwitchSessionUrl || displayConfig.afterSwitchSessionUrl;
- const navigateAfterSwitchSession = () => navigate(afterSwitchSessionUrl);
-
- const userProfileMode = !!ctx.userProfileUrl && !ctx.userProfileMode ? 'navigation' : ctx.userProfileMode;
-
- return {
- ...ctx,
- componentName,
- navigateAfterMultiSessionSingleSignOut,
- navigateAfterSignOut,
- navigateAfterSwitchSession,
- signInUrl,
- userProfileUrl,
- afterMultiSessionSingleSignOutUrl,
- afterSignOutUrl,
- afterSwitchSessionUrl,
- userProfileMode: userProfileMode || 'modal',
- };
-};
-
-export const useOrganizationSwitcherContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as OrganizationSwitcherCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
-
- if (componentName !== 'OrganizationSwitcher') {
- throw new Error('Clerk: useOrganizationSwitcherContext called outside OrganizationSwitcher.');
- }
-
- const afterCreateOrganizationUrl = ctx.afterCreateOrganizationUrl || displayConfig.afterCreateOrganizationUrl;
- const afterLeaveOrganizationUrl = ctx.afterLeaveOrganizationUrl || displayConfig.afterLeaveOrganizationUrl;
-
- const navigateCreateOrganization = () => navigate(ctx.createOrganizationUrl || displayConfig.createOrganizationUrl);
- const navigateOrganizationProfile = () =>
- navigate(ctx.organizationProfileUrl || displayConfig.organizationProfileUrl);
-
- const navigateAfterSelectOrganizationOrPersonal = ({
- organization,
- user,
- }: {
- organization?: OrganizationResource;
- user?: UserResource;
- }) => {
- if (typeof ctx.afterSelectPersonalUrl === 'function' && user) {
- return navigate(ctx.afterSelectPersonalUrl(user));
- }
-
- if (typeof ctx.afterSelectOrganizationUrl === 'function' && organization) {
- return navigate(ctx.afterSelectOrganizationUrl(organization));
- }
-
- if (ctx.afterSelectPersonalUrl && user) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterSelectPersonalUrl as string,
- entity: user,
- });
- return navigate(parsedUrl);
- }
-
- if (ctx.afterSelectOrganizationUrl && organization) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterSelectOrganizationUrl as string,
- entity: organization,
- });
- return navigate(parsedUrl);
- }
-
- return Promise.resolve();
- };
-
- const navigateAfterSelectOrganization = (organization: OrganizationResource) =>
- navigateAfterSelectOrganizationOrPersonal({ organization });
-
- const navigateAfterSelectPersonal = (user: UserResource) => navigateAfterSelectOrganizationOrPersonal({ user });
-
- const organizationProfileMode =
- !!ctx.organizationProfileUrl && !ctx.organizationProfileMode ? 'navigation' : ctx.organizationProfileMode;
-
- const createOrganizationMode =
- !!ctx.createOrganizationUrl && !ctx.createOrganizationMode ? 'navigation' : ctx.createOrganizationMode;
-
- return {
- ...ctx,
- hidePersonal: ctx.hidePersonal || false,
- organizationProfileMode: organizationProfileMode || 'modal',
- createOrganizationMode: createOrganizationMode || 'modal',
- afterCreateOrganizationUrl,
- afterLeaveOrganizationUrl,
- navigateOrganizationProfile,
- navigateCreateOrganization,
- navigateAfterSelectOrganization,
- navigateAfterSelectPersonal,
- componentName,
- };
-};
-
-export const useOrganizationListContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as unknown as OrganizationListCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
-
- if (componentName !== 'OrganizationList') {
- throw new Error('Clerk: useOrganizationListContext called outside OrganizationList.');
- }
-
- const afterCreateOrganizationUrl = ctx.afterCreateOrganizationUrl || displayConfig.afterCreateOrganizationUrl;
-
- const navigateAfterCreateOrganization = (organization: OrganizationResource) => {
- if (typeof ctx.afterCreateOrganizationUrl === 'function') {
- return navigate(ctx.afterCreateOrganizationUrl(organization));
- }
-
- if (ctx.afterCreateOrganizationUrl) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterCreateOrganizationUrl,
- entity: organization,
- });
- return navigate(parsedUrl);
- }
-
- return navigate(displayConfig.afterCreateOrganizationUrl);
- };
-
- const navigateAfterSelectOrganizationOrPersonal = ({
- organization,
- user,
- }: {
- organization?: OrganizationResource;
- user?: UserResource;
- }) => {
- if (typeof ctx.afterSelectPersonalUrl === 'function' && user) {
- return navigate(ctx.afterSelectPersonalUrl(user));
- }
-
- if (typeof ctx.afterSelectOrganizationUrl === 'function' && organization) {
- return navigate(ctx.afterSelectOrganizationUrl(organization));
- }
-
- if (ctx.afterSelectPersonalUrl && user) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterSelectPersonalUrl as string,
- entity: user,
- });
- return navigate(parsedUrl);
- }
-
- if (ctx.afterSelectOrganizationUrl && organization) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterSelectOrganizationUrl as string,
- entity: organization,
- });
- return navigate(parsedUrl);
- }
-
- return Promise.resolve();
- };
-
- const navigateAfterSelectOrganization = (organization: OrganizationResource) =>
- navigateAfterSelectOrganizationOrPersonal({ organization });
- const navigateAfterSelectPersonal = (user: UserResource) => navigateAfterSelectOrganizationOrPersonal({ user });
-
- return {
- ...ctx,
- afterCreateOrganizationUrl,
- skipInvitationScreen: ctx.skipInvitationScreen || false,
- hidePersonal: ctx.hidePersonal || false,
- navigateAfterCreateOrganization,
- navigateAfterSelectOrganization,
- navigateAfterSelectPersonal,
- componentName,
- };
-};
-
-export type OrganizationProfileContextType = OrganizationProfileCtx & {
- pages: PagesType;
- navigateAfterLeaveOrganization: () => Promise;
-};
-
-export const useOrganizationProfileContext = (): OrganizationProfileContextType => {
- const { componentName, customPages, ...ctx } = (React.useContext(ComponentContext) || {}) as OrganizationProfileCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
-
- if (componentName !== 'OrganizationProfile') {
- throw new Error('Clerk: useOrganizationProfileContext called outside OrganizationProfile.');
- }
-
- const pages = createOrganizationProfileCustomPages(customPages || []);
-
- const navigateAfterLeaveOrganization = () =>
- navigate(ctx.afterLeaveOrganizationUrl || displayConfig.afterLeaveOrganizationUrl);
-
- return {
- ...ctx,
- pages,
- navigateAfterLeaveOrganization,
- componentName,
- };
-};
-
-export const useCreateOrganizationContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as CreateOrganizationCtx;
- const { navigate } = useRouter();
- const { displayConfig } = useEnvironment();
-
- if (componentName !== 'CreateOrganization') {
- throw new Error('Clerk: useCreateOrganizationContext called outside CreateOrganization.');
- }
-
- const navigateAfterCreateOrganization = (organization: OrganizationResource) => {
- if (typeof ctx.afterCreateOrganizationUrl === 'function') {
- return navigate(ctx.afterCreateOrganizationUrl(organization));
- }
-
- if (ctx.afterCreateOrganizationUrl) {
- const parsedUrl = populateParamFromObject({
- urlWithParam: ctx.afterCreateOrganizationUrl,
- entity: organization,
- });
- return navigate(parsedUrl);
- }
-
- return navigate(displayConfig.afterCreateOrganizationUrl);
- };
-
- return {
- ...ctx,
- skipInvitationScreen: ctx.skipInvitationScreen || false,
- navigateAfterCreateOrganization,
- componentName,
- };
-};
diff --git a/packages/clerk-js/src/ui.retheme/contexts/CoreClerkContextWrapper.tsx b/packages/clerk-js/src/ui.retheme/contexts/CoreClerkContextWrapper.tsx
deleted file mode 100644
index 78b186bba95..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/CoreClerkContextWrapper.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import {
- ClerkInstanceContext,
- ClientContext,
- OrganizationProvider,
- SessionContext,
- UserContext,
-} from '@clerk/shared/react';
-import type { Clerk, LoadedClerk, Resources } from '@clerk/types';
-import React from 'react';
-
-import { assertClerkSingletonExists } from './utils';
-
-type CoreClerkContextWrapperProps = {
- clerk: Clerk;
- children: React.ReactNode;
- swrConfig?: any;
-};
-
-type CoreClerkContextProviderState = Resources;
-
-export function CoreClerkContextWrapper(props: CoreClerkContextWrapperProps): JSX.Element | null {
- // TODO: Revise Clerk and LoadedClerk
- const clerk = props.clerk as LoadedClerk;
- assertClerkSingletonExists(clerk);
-
- const [state, setState] = React.useState({
- client: clerk.client,
- session: clerk.session,
- user: clerk.user,
- organization: clerk.organization,
- });
-
- React.useEffect(() => {
- return clerk.addListener(e => setState({ ...e }));
- }, []);
-
- const { client, session, user, organization } = state;
- const clerkCtx = React.useMemo(() => ({ value: clerk }), []);
- const clientCtx = React.useMemo(() => ({ value: client }), [client]);
- const sessionCtx = React.useMemo(() => ({ value: session }), [session]);
- const userCtx = React.useMemo(() => ({ value: user }), [user]);
- const organizationCtx = React.useMemo(
- () => ({
- value: { organization: organization },
- }),
- [organization],
- );
-
- return (
-
-
-
-
- {props.children}
-
-
-
-
- );
-}
diff --git a/packages/clerk-js/src/ui.retheme/contexts/CoreClientContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/CoreClientContext.tsx
deleted file mode 100644
index 3212a6f8ac5..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/CoreClientContext.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { assertContextExists, ClientContext, useClientContext } from '@clerk/shared/react';
-import type { SignInResource, SignUpResource } from '@clerk/types';
-
-export function useCoreSignIn(): SignInResource {
- const ctx = useClientContext();
- assertContextExists(ctx, ClientContext);
- return ctx.signIn;
-}
-
-export function useCoreSignUp(): SignUpResource {
- const ctx = useClientContext();
- assertContextExists(ctx, ClientContext);
- return ctx.signUp;
-}
diff --git a/packages/clerk-js/src/ui.retheme/contexts/CoreSessionContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/CoreSessionContext.tsx
deleted file mode 100644
index 169cff73a0d..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/CoreSessionContext.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useSessionContext } from '@clerk/shared/react';
-import React from 'react';
-
-export function withCoreSessionSwitchGuard(Component: React.ComponentType
): React.ComponentType
{
- const Hoc = (props: P) => {
- const session = useSessionContext();
-
- /**
- * Avoid simply checking if session is falsy, checking against undefined is preferable as it means that clerk has not loaded yet
- */
- if (typeof session === 'undefined') {
- return null;
- }
-
- return ;
- };
-
- const displayName = Component.displayName || Component.name || 'Component';
- Component.displayName = displayName;
- Hoc.displayName = displayName;
- return Hoc;
-}
diff --git a/packages/clerk-js/src/ui.retheme/contexts/CoreUserContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/CoreUserContext.tsx
deleted file mode 100644
index 94a32ca1715..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/CoreUserContext.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useUserContext } from '@clerk/shared/react';
-import React from 'react';
-
-export function withCoreUserGuard
(Component: React.ComponentType
): React.ComponentType
{
- const Hoc = (props: P) => {
- const user = useUserContext();
-
- if (!user) {
- return null;
- }
-
- return ;
- };
-
- const displayName = Component.displayName || Component.name || 'Component';
- Component.displayName = displayName;
- Hoc.displayName = displayName;
- return Hoc;
-}
diff --git a/packages/clerk-js/src/ui.retheme/contexts/EnvironmentContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/EnvironmentContext.tsx
deleted file mode 100644
index e903ff5f5bb..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/EnvironmentContext.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { EnvironmentResource } from '@clerk/types';
-import * as React from 'react';
-
-import { assertContextExists } from './utils';
-
-const EnvironmentContext = React.createContext(null);
-
-interface EnvironmentProviderProps {
- children: React.ReactNode;
- value: EnvironmentResource;
-}
-
-function EnvironmentProvider({ children, value }: EnvironmentProviderProps): JSX.Element {
- return {children};
-}
-
-function useEnvironment(): EnvironmentResource {
- const context = React.useContext(EnvironmentContext);
- assertContextExists(context, 'EnvironmentProvider');
- return context;
-}
-
-export { EnvironmentProvider, useEnvironment };
diff --git a/packages/clerk-js/src/ui.retheme/contexts/OptionsContext.tsx b/packages/clerk-js/src/ui.retheme/contexts/OptionsContext.tsx
deleted file mode 100644
index e822bf1847d..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/OptionsContext.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { ClerkOptions } from '@clerk/types';
-import React from 'react';
-
-export const OptionsContext = React.createContext({});
-
-interface OptionsProviderProps {
- children: React.ReactNode;
- value: ClerkOptions;
-}
-
-function OptionsProvider({ children, value }: OptionsProviderProps): JSX.Element {
- return {children};
-}
-
-function useOptions(): ClerkOptions {
- const context = React.useContext(OptionsContext);
- if (context === undefined) {
- throw new Error('useOptions must be used within an OptionsContext');
- }
- return context;
-}
-
-export { OptionsProvider, useOptions };
diff --git a/packages/clerk-js/src/ui.retheme/contexts/index.ts b/packages/clerk-js/src/ui.retheme/contexts/index.ts
deleted file mode 100644
index b5aae8b35da..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export * from './CoreUserContext';
-export * from './ClerkUIComponentsContext';
-export * from './EnvironmentContext';
-export * from './OptionsContext';
-export * from './CoreSessionContext';
-export * from './CoreClientContext';
-export * from './CoreClerkContextWrapper';
diff --git a/packages/clerk-js/src/ui.retheme/contexts/utils.ts b/packages/clerk-js/src/ui.retheme/contexts/utils.ts
deleted file mode 100644
index 43cf7f45c0b..00000000000
--- a/packages/clerk-js/src/ui.retheme/contexts/utils.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Clerk } from '@clerk/types';
-
-import { clerkCoreErrorContextProviderNotFound, clerkCoreErrorNoClerkSingleton } from '../../core/errors';
-
-export function assertClerkSingletonExists(clerk: Clerk | undefined): asserts clerk is Clerk {
- if (!clerk) {
- clerkCoreErrorNoClerkSingleton();
- }
-}
-
-export function assertContextExists(contextVal: unknown, providerName: string): asserts contextVal {
- if (!contextVal) {
- clerkCoreErrorContextProviderNotFound(providerName);
- }
-}
diff --git a/packages/clerk-js/src/ui.retheme/customizables/AppearanceContext.tsx b/packages/clerk-js/src/ui.retheme/customizables/AppearanceContext.tsx
deleted file mode 100644
index 201edc9cab8..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/AppearanceContext.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { createContextAndHook } from '@clerk/shared/react';
-import React from 'react';
-
-import { useDeepEqualMemo } from '../hooks';
-import type { AppearanceCascade, ParsedAppearance } from './parseAppearance';
-import { parseAppearance } from './parseAppearance';
-
-type AppearanceContextValue = ParsedAppearance;
-
-const [AppearanceContext, useAppearance] = createContextAndHook('AppearanceContext');
-
-type AppearanceProviderProps = React.PropsWithChildren;
-
-const AppearanceProvider = (props: AppearanceProviderProps) => {
- const ctxValue = useDeepEqualMemo(() => {
- const value = parseAppearance(props);
-
- return { value };
- }, [props.appearance, props.globalAppearance]);
-
- return {props.children};
-};
-
-export { AppearanceProvider, useAppearance };
diff --git a/packages/clerk-js/src/ui.retheme/customizables/Flow.tsx b/packages/clerk-js/src/ui.retheme/customizables/Flow.tsx
deleted file mode 100644
index e11afb50871..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/Flow.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-
-import type { FlowMetadata } from '../elements';
-import { FlowMetadataProvider, InvisibleRootBox, useFlowMetadata } from '../elements';
-import { InternalThemeProvider } from '../styledSystem';
-import { generateFlowClassname } from './classGeneration';
-import { descriptors } from './index';
-
-type FlowRootProps = React.PropsWithChildren & FlowMetadata;
-
-const Root = (props: FlowRootProps) => {
- return (
-
-
-
-
-
- );
-};
-
-type FlowPartProps = React.PropsWithChildren>;
-
-const Part = (props: FlowPartProps) => {
- const { flow } = useFlowMetadata();
- return (
-
- {props.children}
-
- );
-};
-
-export const Flow = {
- Root,
- Part,
-};
diff --git a/packages/clerk-js/src/ui.retheme/customizables/__tests__/elementDescriptors.test.tsx b/packages/clerk-js/src/ui.retheme/customizables/__tests__/elementDescriptors.test.tsx
deleted file mode 100644
index 44d42122732..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/__tests__/elementDescriptors.test.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-// eslint-disable-next-line simple-import-sort/imports
-import { render, screen } from '../../../testUtils';
-import React from 'react';
-
-import { Box, descriptors } from '..';
-import { AppearanceProvider } from '../AppearanceContext';
-
-describe('Targetable classes', () => {
- it('default stable class is added to the element ', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-socialButtonsIconButton');
- });
-
- it('id related stable classname is added to the element', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-socialButtonsIconButton__google');
- });
-
- it('setting an undefined elementId should add no more classses', () => {
- const wrapper = ({ children }) => {children};
-
- const { rerender } = render(
- ,
- { wrapper },
- );
-
- const classes = screen.getByTestId('test').className;
-
- rerender(
- ,
- );
-
- const classesWithElementId = screen.getByTestId('test').className;
-
- expect(classes).toBe(classesWithElementId);
- });
-
- it('loading state related stable classname is added to the element', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-loading');
- });
-
- it('active state related stable classname is added to the element', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-active');
- });
-
- it('error state related stable classname is added to the element', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-error');
- });
-
- it('open state related stable classname is added to the element', () => {
- render(
-
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('cl-open');
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/customizables/__tests__/makeCustomizable.test.tsx b/packages/clerk-js/src/ui.retheme/customizables/__tests__/makeCustomizable.test.tsx
deleted file mode 100644
index 95ca7788fea..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/__tests__/makeCustomizable.test.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-// eslint-disable-next-line simple-import-sort/imports
-import { render, screen } from '../../../testUtils';
-import React from 'react';
-
-import { Box, descriptors } from '..';
-import { AppearanceProvider } from '../AppearanceContext';
-import { knownColors } from './testUtils';
-import { InternalThemeProvider } from '../../styledSystem';
-
-describe('Theme used in sx callback', () => {
- it('styles match the theme/globalAppearance', () => {
- render(
-
-
- ({ backgroundColor: t.colors.$primary500 })}
- data-testid='test'
- />
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.blue);
- });
-
- it('styles match the theme/appearance', () => {
- render(
-
-
- ({ backgroundColor: t.colors.$primary500 })}
- data-testid='test'
- />
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.red);
- });
-
- it('styles match the merged result from globalAppearance and appearance', () => {
- render(
-
-
- ({ backgroundColor: t.colors.$primary500 })}
- data-testid='test'
- />
-
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.red);
- });
-});
-
-describe('Styles for specific elements', () => {
- it('styles propagate to the correct element specified', () => {
- render(
-
-
- ,
- );
- expect(screen.getByTestId('test')).toHaveStyleRule('background-color', 'yellow');
- });
-
- it('styles propagate to the correct element specified, including overriding styles when loading state is applied', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { rerender } = render(
- ,
- {
- wrapper,
- },
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow');
-
- rerender(
- ,
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red');
- });
-
- it('overrides styles when active state is applied', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { rerender } = render(
- ,
- { wrapper },
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow');
-
- rerender(
- ,
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red');
- });
-
- it('overrides styles when error state is applied', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { rerender } = render(
- ,
- {
- wrapper,
- },
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow');
-
- rerender(
- ,
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red');
- });
-
- it('overrides styles when open state is applied', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { rerender } = render(
- ,
- {
- wrapper,
- },
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow');
-
- rerender(
- ,
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red');
- });
-
- it('overrides &:disabled styles when loading state is applied', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { rerender } = render(
- ,
- { wrapper },
- );
-
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow');
-
- rerender(
- ,
- );
- expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red');
- });
-
- it('if a class is provided to the element via appearance, it adds the class to the element', () => {
- render(
-
-
- ,
- ,
- );
-
- expect(screen.getByTestId('test')).toHaveClass('test-class');
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/customizables/__tests__/parseAppearance.test.tsx b/packages/clerk-js/src/ui.retheme/customizables/__tests__/parseAppearance.test.tsx
deleted file mode 100644
index 98ee446c355..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/__tests__/parseAppearance.test.tsx
+++ /dev/null
@@ -1,342 +0,0 @@
-// eslint-disable-next-line simple-import-sort/imports
-import { render } from '../../../testUtils';
-import React from 'react';
-
-import { Box, useAppearance } from '..';
-import { AppearanceProvider } from '../AppearanceContext';
-import { renderHook } from '@testing-library/react';
-import { knownColors } from './testUtils';
-
-const themeAColor = 'blue';
-const themeA = {
- variables: {
- colorPrimary: themeAColor,
- colorDanger: themeAColor,
- colorSuccess: themeAColor,
- colorWarning: themeAColor,
- colorBackground: themeAColor,
- colorInputBackground: themeAColor,
- colorInputText: themeAColor,
- colorText: themeAColor,
- colorTextOnPrimaryBackground: themeAColor,
- colorTextSecondary: themeAColor,
- borderRadius: '1rem',
- fontFamily: 'Comic Sans',
- fontFamilyButtons: 'Comic Sans',
- fontSize: '1rem',
- fontWeight: { normal: 600 },
- fontSmoothing: 'antialiased',
- spacingUnit: 'px',
- },
-} as const;
-
-const themeBColor = 'red';
-const themeB = {
- variables: {
- colorPrimary: themeBColor,
- colorDanger: themeBColor,
- colorSuccess: themeBColor,
- colorWarning: themeBColor,
- colorBackground: themeBColor,
- colorInputBackground: themeBColor,
- colorInputText: themeBColor,
- colorText: themeBColor,
- colorTextOnPrimaryBackground: themeBColor,
- colorTextSecondary: themeBColor,
- borderRadius: '2rem',
- fontFamily: 'Arial',
- fontFamilyButtons: 'Arial',
- fontSize: '2rem',
- fontWeight: { normal: 700 },
- fontSmoothing: 'never',
- spacingUnit: 'px',
- },
-} as const;
-
-describe('AppearanceProvider', () => {
- it('renders the provider', () => {
- render(
-
-
- ,
- );
- });
-});
-
-describe('AppearanceProvider internalTheme flows', () => {
- it('sets the theme correctly from the globalAppearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
-
- expect(result.current.parsedInternalTheme.colors.$primary500).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$danger500).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$success500).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$warning500).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorBackground).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputBackground).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputText).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorText).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextOnPrimaryBackground).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextSecondary).toBe(knownColors[themeAColor]);
- expect(result.current.parsedInternalTheme.radii.$md).toBe(themeA.variables.borderRadius);
- expect(result.current.parsedInternalTheme.fonts.$main).toBe(themeA.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fonts.$buttons).toBe(themeA.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fontSizes.$md).toBe(themeA.variables.fontSize);
- expect(result.current.parsedInternalTheme.fontWeights.$normal).toBe(themeA.variables.fontWeight.normal);
- expect(result.current.parsedInternalTheme.options.$fontSmoothing).toBe(themeA.variables.fontSmoothing);
- expect(result.current.parsedInternalTheme.space.$1).toContain(themeA.variables.spacingUnit);
- });
-
- it('sets the theme correctly from the appearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
-
- expect(result.current.parsedInternalTheme.colors.$primary500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$danger500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$success500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$warning500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputText).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorText).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextOnPrimaryBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextSecondary).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.radii.$md).toBe(themeB.variables.borderRadius);
- expect(result.current.parsedInternalTheme.fonts.$main).toBe(themeB.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fonts.$buttons).toBe(themeB.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fontSizes.$md).toBe(themeB.variables.fontSize);
- expect(result.current.parsedInternalTheme.fontWeights.$normal).toBe(themeB.variables.fontWeight.normal);
- expect(result.current.parsedInternalTheme.options.$fontSmoothing).toBe(themeB.variables.fontSmoothing);
- expect(result.current.parsedInternalTheme.space.$1).toContain(themeB.variables.spacingUnit);
- });
-
- it('merges the globalAppearance with the appearance in the theme', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
-
- expect(result.current.parsedInternalTheme.colors.$primary500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$danger500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$success500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$warning500).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorInputText).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorText).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextOnPrimaryBackground).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.colors.$colorTextSecondary).toBe(knownColors[themeBColor]);
- expect(result.current.parsedInternalTheme.radii.$md).toBe(themeB.variables.borderRadius);
- expect(result.current.parsedInternalTheme.fonts.$main).toBe(themeB.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fonts.$buttons).toBe(themeB.variables.fontFamily);
- expect(result.current.parsedInternalTheme.fontSizes.$md).toBe(themeB.variables.fontSize);
- expect(result.current.parsedInternalTheme.fontWeights.$normal).toBe(themeB.variables.fontWeight.normal);
- expect(result.current.parsedInternalTheme.options.$fontSmoothing).toBe(themeB.variables.fontSmoothing);
- expect(result.current.parsedInternalTheme.space.$1).toContain(themeB.variables.spacingUnit);
- });
-});
-
-describe('AppearanceProvider element flows', () => {
- it('sets the parsedElements correctly from the globalAppearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(themeAColor);
- });
-
- it('sets the parsedElements correctly from the globalAppearance and appearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(themeAColor);
- expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeBColor);
- });
-
- it('sets the parsedElements correctly when a function is passed for the elements', () => {
- const wrapper = ({ children }) => (
- ({
- alert: { backgroundColor: theme.colors.$primary500 },
- }),
- }}
- >
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(knownColors[themeAColor]);
- });
-});
-
-describe('AppearanceProvider layout flows', () => {
- it('sets the parsedLayout correctly from the globalAppearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedLayout.helpPageUrl).toBe('https://example.com/help');
- expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/64x64.png');
- expect(result.current.parsedLayout.logoLinkUrl).toBe('https://example.com/');
- expect(result.current.parsedLayout.privacyPageUrl).toBe('https://example.com/privacy');
- expect(result.current.parsedLayout.termsPageUrl).toBe('https://example.com/terms');
- expect(result.current.parsedLayout.logoPlacement).toBe('inside');
- expect(result.current.parsedLayout.showOptionalFields).toBe(false);
- expect(result.current.parsedLayout.socialButtonsPlacement).toBe('bottom');
- expect(result.current.parsedLayout.socialButtonsVariant).toBe('iconButton');
- });
-
- it('sets the parsedLayout correctly from the appearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedLayout.helpPageUrl).toBe('https://example.com/help');
- expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/64x64.png');
- expect(result.current.parsedLayout.logoLinkUrl).toBe('https://example.com/');
- expect(result.current.parsedLayout.privacyPageUrl).toBe('https://example.com/privacy');
- expect(result.current.parsedLayout.termsPageUrl).toBe('https://example.com/terms');
- expect(result.current.parsedLayout.logoPlacement).toBe('outside');
- expect(result.current.parsedLayout.showOptionalFields).toBe(true);
- expect(result.current.parsedLayout.socialButtonsPlacement).toBe('top');
- expect(result.current.parsedLayout.socialButtonsVariant).toBe('blockButton');
- });
-
- it('sets the parsedLayout correctly from the globalAppearance and appearance prop', () => {
- const wrapper = ({ children }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useAppearance(), { wrapper });
- expect(result.current.parsedLayout.helpPageUrl).toBe('https://second.example.com/help');
- expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/32x32@2.png');
- expect(result.current.parsedLayout.logoLinkUrl).toBe('https://second.example.com/');
- expect(result.current.parsedLayout.privacyPageUrl).toBe('https://second.example.com/privacy');
- expect(result.current.parsedLayout.termsPageUrl).toBe('https://second.example.com/terms');
- expect(result.current.parsedLayout.logoPlacement).toBe('outside');
- expect(result.current.parsedLayout.showOptionalFields).toBe(true);
- expect(result.current.parsedLayout.socialButtonsPlacement).toBe('top');
- expect(result.current.parsedLayout.socialButtonsVariant).toBe('blockButton');
- });
-});
diff --git a/packages/clerk-js/src/ui.retheme/customizables/__tests__/testUtils.ts b/packages/clerk-js/src/ui.retheme/customizables/__tests__/testUtils.ts
deleted file mode 100644
index 3d5dbc28fdb..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/__tests__/testUtils.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const knownColors = {
- blue: 'hsla(240, 100%, 50%, 1)',
- red: 'hsla(0, 100%, 50%, 1)',
-};
diff --git a/packages/clerk-js/src/ui.retheme/customizables/classGeneration.ts b/packages/clerk-js/src/ui.retheme/customizables/classGeneration.ts
deleted file mode 100644
index 6eebf5a93a3..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/classGeneration.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import type { Elements, ElementState } from '@clerk/types';
-
-import type { FlowMetadata } from '../elements/contexts';
-import type { ElementDescriptor, ElementId } from './elementDescriptors';
-import { CLASS_PREFIX } from './elementDescriptors';
-import type { ParsedElements } from './parseAppearance';
-
-const STATE_PROP_TO_CLASSNAME = Object.freeze({
- loading: ` ${CLASS_PREFIX}loading`,
- error: ` ${CLASS_PREFIX}error`,
- open: ` ${CLASS_PREFIX}open`,
- active: ` ${CLASS_PREFIX}active`,
-} as const);
-
-const STATE_PROP_TO_SPECIFICITY = Object.freeze({
- loading: 2,
- error: 2,
- open: 2,
- active: 2,
-} as const);
-
-type PropsWithState = Partial> &
- Record;
-
-export const generateClassName = (
- parsedElements: ParsedElements,
- elemDescriptors: ElementDescriptor[],
- elemId: ElementId | undefined,
- props: PropsWithState | undefined,
-) => {
- const state = getElementState(props);
- let className = '';
- const css: unknown[] = [];
-
- className = addClerkTargettableClassname(className, elemDescriptors);
- className = addClerkTargettableIdClassname(className, elemDescriptors, elemId);
- className = addClerkTargettableStateClass(className, state);
- className = addClerkTargettableRequiredAttribute(className, props as any);
-
- className = addUserProvidedClassnames(className, parsedElements, elemDescriptors, elemId, state);
- addUserProvidedStyleRules(css, parsedElements, elemDescriptors, elemId, state);
- return { className, css };
-};
-
-const addClerkTargettableClassname = (cn: string, elemDescriptors: ElementDescriptor[]) => {
- // We want the most specific classname to be displayed first, so we reverse the order
- for (let i = elemDescriptors.length - 1; i >= 0; i--) {
- cn += elemDescriptors[i].targettableClassname + ' ';
- }
- return cn.trimEnd();
-};
-
-const addClerkTargettableIdClassname = (
- cn: string,
- elemDescriptors: ElementDescriptor[],
- elemId: ElementId | undefined,
-) => {
- if (!elemId) {
- return cn;
- }
- // We want the most specific classname to be displayed first, so we reverse the order
- for (let i = elemDescriptors.length - 1; i >= 0; i--) {
- cn = cn + ' ' + elemDescriptors[i].getTargettableIdClassname(elemId);
- }
- return cn;
-};
-
-const addClerkTargettableStateClass = (cn: string, state: ElementState | undefined) => {
- return state ? cn + STATE_PROP_TO_CLASSNAME[state] : cn;
-};
-
-const addClerkTargettableRequiredAttribute = (cn: string, props: { isRequired?: boolean } | undefined) => {
- return props && props.isRequired ? cn + ` ${CLASS_PREFIX}required` : cn;
-};
-
-export const appendEmojiSeparator = (generatedCn: string, otherCn?: string) => {
- return generatedCn + (otherCn ? ' ' + otherCn : '') + ' 🔒️';
-};
-
-const addUserProvidedStyleRules = (
- css: unknown[],
- parsedElements: ParsedElements,
- elemDescriptors: ElementDescriptor[],
- elemId: ElementId | undefined,
- state: ElementState | undefined,
-) => {
- for (let j = 0; j < elemDescriptors.length; j++) {
- for (let i = 0; i < parsedElements.length; i++) {
- addRulesFromElements(css, parsedElements[i], elemDescriptors[j], elemId, state);
- }
- }
-};
-
-const addUserProvidedClassnames = (
- cn: string,
- parsedElements: ParsedElements,
- elemDescriptors: ElementDescriptor[],
- elemId: ElementId | undefined,
- state: ElementState | undefined,
-) => {
- for (let j = 0; j < elemDescriptors.length; j++) {
- for (let i = 0; i < parsedElements.length; i++) {
- cn = addClassnamesFromElements(cn, parsedElements[i], elemDescriptors[j], elemId, state);
- }
- }
- return cn; //.trimEnd();
-};
-
-const getElementState = (props: PropsWithState | undefined): ElementState | undefined => {
- if (!props) {
- return undefined;
- }
- if (props.isLoading) {
- return 'loading';
- }
- if (props.hasError) {
- return 'error';
- }
- if (props.isOpen) {
- return 'open';
- }
- if (props.isActive) {
- return 'active';
- }
- return undefined;
-};
-
-const addStringClassname = (cn: string, val?: unknown) => (typeof val === 'string' ? cn + ' ' + val : cn);
-
-const addStyleRuleObject = (css: unknown[], val: unknown, specificity = 0) => {
- if (specificity) {
- val && typeof val === 'object' && css.push({ ['&'.repeat(specificity)]: val });
- } else {
- val && typeof val === 'object' && css.push(val);
- }
-};
-
-export const generateFlowClassname = (props: Pick) => {
- return CLASS_PREFIX + props.flow + '-root';
-};
-
-export const generateFlowPartClassname = (props: FlowMetadata) => {
- if (!props.part) {
- return '';
- }
- return CLASS_PREFIX + props.flow + '-' + props.part;
-};
-
-const addClassnamesFromElements = (
- cn: string,
- elements: Elements | undefined,
- elemDescriptor: ElementDescriptor,
- elemId: ElementId | undefined,
- state: ElementState | undefined,
-) => {
- if (!elements) {
- return cn;
- }
-
- type Key = keyof typeof elements;
- cn = addStringClassname(cn, elements[elemDescriptor.objectKey as Key]);
- if (elemId) {
- cn = addStringClassname(cn, elements[elemDescriptor.getObjectKeyWithId(elemId) as Key]);
- }
- if (state) {
- cn = addStringClassname(cn, elements[elemDescriptor.getObjectKeyWithState(state) as Key]);
- }
- if (elemId && state) {
- cn = addStringClassname(cn, elements[elemDescriptor.getObjectKeyWithIdAndState(elemId, state) as Key]);
- }
- return cn;
-};
-
-const addRulesFromElements = (
- css: unknown[],
- elements: Elements | undefined,
- elemDescriptor: ElementDescriptor,
- elemId: ElementId | undefined,
- state: ElementState | undefined,
-) => {
- if (!elements) {
- return;
- }
-
- type Key = keyof typeof elements;
- addStyleRuleObject(css, elements[elemDescriptor.objectKey as Key]);
- if (elemId) {
- addStyleRuleObject(css, elements[elemDescriptor.getObjectKeyWithId(elemId) as Key]);
- }
- if (state) {
- addStyleRuleObject(
- css,
- elements[elemDescriptor.getObjectKeyWithState(state) as Key],
- STATE_PROP_TO_SPECIFICITY[state],
- );
- }
- if (elemId && state) {
- addStyleRuleObject(
- css,
- elements[elemDescriptor.getObjectKeyWithIdAndState(elemId, state) as Key],
- STATE_PROP_TO_SPECIFICITY[state],
- );
- }
-};
diff --git a/packages/clerk-js/src/ui.retheme/customizables/colorOptionToHslaScale.ts b/packages/clerk-js/src/ui.retheme/customizables/colorOptionToHslaScale.ts
deleted file mode 100644
index f9ab3902ed1..00000000000
--- a/packages/clerk-js/src/ui.retheme/customizables/colorOptionToHslaScale.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import type { ColorScale, CssColorOrScale, HslaColor, HslaColorString } from '@clerk/types';
-
-import { colors, fromEntries } from '../utils';
-
-type InternalColorScale = ColorScale & Partial>;
-
-const LIGHT_SHADES = ['50', '100', '200', '300', '400'].reverse();
-const DARK_SHADES = ['600', '700', '800', '900'];
-
-const TARGET_L_50_SHADE = 97;
-const TARGET_L_900_SHADE = 12;
-
-function createEmptyColorScale(): InternalColorScale {
- return {
- '50': undefined,
- '100': undefined,
- '200': undefined,
- '300': undefined,
- '400': undefined,
- '500': undefined,
- '600': undefined,
- '700': undefined,
- '800': undefined,
- '900': undefined,
- };
-}
-
-type WithPrefix, Prefix extends string> = {
- [k in keyof T as `${Prefix}${k & string}`]: T[k];
-};
-
-export const colorOptionToHslaAlphaScale = (
- colorOption: CssColorOrScale | undefined,
- prefix: Prefix,
-): WithPrefix, Prefix> | undefined => {
- return fillUserProvidedScaleWithGeneratedHslaColors(colorOption, prefix, generateFilledAlphaScaleFromBaseHslaColor);
-};
-
-export const colorOptionToHslaLightnessScale = (
- colorOption: CssColorOrScale | undefined,
- prefix: Prefix,
-): WithPrefix, Prefix> | undefined => {
- return fillUserProvidedScaleWithGeneratedHslaColors(colorOption, prefix, generateFilledScaleFromBaseHslaColor);
-};
-
-const fillUserProvidedScaleWithGeneratedHslaColors = (
- colorOption: CssColorOrScale | undefined,
- prefix: Prefix,
- generator: (base: HslaColor) => InternalColorScale,
-): WithPrefix, Prefix> | undefined => {
- if (!colorOption) {
- return undefined;
- }
-
- if (typeof colorOption === 'object' && !colorOption['500']) {
- throw new Error('You need to provide at least the 500 shade');
- }
-
- const userDefinedHslaColorScale = userDefinedColorToHslaColorScale(colorOption);
- const filledHslaColorScale = generator(userDefinedHslaColorScale['500']);
- const merged = mergeFilledIntoUserDefinedScale(filledHslaColorScale, userDefinedHslaColorScale);
- return prefixAndStringifyHslaScale(merged, prefix);
-};
-
-const mergeFilledIntoUserDefinedScale = (
- generated: InternalColorScale,
- userDefined: InternalColorScale