Skip to content
Draft
8 changes: 8 additions & 0 deletions .changeset/modern-coins-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@clerk/clerk-js": minor
"@clerk/nextjs": minor
"@clerk/clerk-react": minor
"@clerk/shared": minor
---

Export TaskResetPassword components
10 changes: 10 additions & 0 deletions packages/clerk-js/sandbox/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const AVAILABLE_COMPONENTS = [
'apiKeys',
'oauthConsent',
'taskChooseOrganization',
'taskResetPassword',
] as const;

const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox';
Expand Down Expand Up @@ -99,6 +100,7 @@ const componentControls: Record<(typeof AVAILABLE_COMPONENTS)[number], Component
apiKeys: buildComponentControls('apiKeys'),
oauthConsent: buildComponentControls('oauthConsent'),
taskChooseOrganization: buildComponentControls('taskChooseOrganization'),
taskResetPassword: buildComponentControls('taskResetPassword'),
};

declare global {
Expand Down Expand Up @@ -352,6 +354,14 @@ void (async () => {
},
);
},
'/task-reset-password': () => {
Clerk.mountTaskResetPassword(
app,
componentControls.taskResetPassword.getProps() ?? {
redirectUrlComplete: '/user-profile',
},
);
},
'/open-sign-in': () => {
mountOpenSignInButton(app, componentControls.signIn.getProps() ?? {});
},
Expand Down
21 changes: 21 additions & 0 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import type {
SignUpRedirectOptions,
SignUpResource,
TaskChooseOrganizationProps,
TaskResetPasswordProps,
TasksRedirectOptions,
UnsubscribeCallback,
UserAvatarProps,
Expand Down Expand Up @@ -1424,6 +1425,26 @@ export class Clerk implements ClerkInterface {
void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ node }));
};

public mountTaskResetPassword = (node: HTMLDivElement, props?: TaskResetPasswordProps) => {
this.assertComponentsReady(this.#componentControls);

void this.#componentControls.ensureMounted({ preloadHint: 'TaskResetPassword' }).then(controls =>
controls.mountComponent({
name: 'TaskResetPassword',
appearanceKey: 'taskResetPassword',
node,
props,
}),
);

this.telemetry?.record(eventPrebuiltComponentMounted('TaskResetPassword', props));
};

public unmountTaskResetPassword = (node: HTMLDivElement) => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ node }));
};

/**
* `setActive` can be used to set the active session and/or organization.
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/clerk-js/src/ui/lazyModules/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const componentImportPaths = {
SessionTasks: () => import(/* webpackChunkName: "sessionTasks" */ '../components/SessionTasks'),
TaskChooseOrganization: () =>
import(/* webpackChunkName: "taskChooseOrganization" */ '../components/SessionTasks/tasks/TaskChooseOrganization'),
TaskResetPassword: () =>
import(/* webpackChunkName: "taskResetPassword" */ '../components/SessionTasks/tasks/TaskResetPassword'),
PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans/PlanDetails'),
SubscriptionDetails: () => import(/* webpackChunkName: "subscriptionDetails" */ '../components/SubscriptionDetails'),
APIKeys: () => import(/* webpackChunkName: "apiKeys" */ '../components/APIKeys/APIKeys'),
Expand Down Expand Up @@ -123,6 +125,10 @@ export const TaskChooseOrganization = lazy(() =>
componentImportPaths.TaskChooseOrganization().then(module => ({ default: module.TaskChooseOrganization })),
);

export const TaskResetPassword = lazy(() =>
componentImportPaths.TaskResetPassword().then(module => ({ default: module.TaskResetPassword })),
);

export const PlanDetails = lazy(() =>
componentImportPaths.PlanDetails().then(module => ({ default: module.PlanDetails })),
);
Expand Down Expand Up @@ -172,6 +178,7 @@ export const ClerkComponents = {
OAuthConsent,
SubscriptionDetails,
TaskChooseOrganization,
TaskResetPassword,
};

export type ClerkComponentName = keyof typeof ClerkComponents;
3 changes: 2 additions & 1 deletion packages/clerk-js/src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export type AvailableComponentProps =
| __internal_SubscriptionDetailsProps
| __internal_PlanDetailsProps
| APIKeysProps
| TaskChooseOrganizationProps;
| TaskChooseOrganizationProps
| TaskResetPasswordProps;

type ComponentMode = 'modal' | 'mounted';
type SignInMode = 'modal' | 'redirect';
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/src/client-boundary/uiComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
SignOutButton,
SignUpButton,
TaskChooseOrganization,
TaskResetPassword,
UserAvatar,
UserButton,
Waitlist,
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export {
SignUp,
SignUpButton,
TaskChooseOrganization,
TaskResetPassword,
UserAvatar,
UserButton,
UserProfile,
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
SignIn,
SignUp,
TaskChooseOrganization,
TaskResetPassword,
UserAvatar,
UserButton,
UserProfile,
Expand Down
29 changes: 29 additions & 0 deletions packages/react/src/components/uiComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
SignInProps,
SignUpProps,
TaskChooseOrganizationProps,
TaskResetPasswordProps,
UserAvatarProps,
UserButtonProps,
UserProfileProps,
Expand Down Expand Up @@ -696,3 +697,31 @@ export const TaskChooseOrganization = withClerk(
},
{ component: 'TaskChooseOrganization', renderWhileLoading: true },
);

export const TaskResetPassword = withClerk(
({ clerk, component, fallback, ...props }: WithClerkProp<TaskResetPasswordProps & FallbackProp>) => {
const mountingStatus = useWaitForComponentMount(component);
const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded;

const rendererRootProps = {
...(shouldShowFallback && fallback && { style: { display: 'none' } }),
};

return (
<>
{shouldShowFallback && fallback}
{clerk.loaded && (
<ClerkHostRenderer
component={component}
mount={clerk.mountTaskResetPassword}
unmount={clerk.unmountTaskResetPassword}
updateProps={(clerk as any).__unstable__updateProps}
props={props}
rootProps={rendererRootProps}
/>
)}
</>
);
},
{ component: 'TaskResetPassword', renderWhileLoading: true },
);
23 changes: 22 additions & 1 deletion packages/react/src/isomorphicClerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import type {
SignUpResource,
State,
TaskChooseOrganizationProps,
TaskResetPasswordProps,
TasksRedirectOptions,
UnsubscribeCallback,
UserAvatarProps,
Expand Down Expand Up @@ -150,7 +151,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
private premountAPIKeysNodes = new Map<HTMLDivElement, APIKeysProps | undefined>();
private premountOAuthConsentNodes = new Map<HTMLDivElement, __internal_OAuthConsentProps | undefined>();
private premountTaskChooseOrganizationNodes = new Map<HTMLDivElement, TaskChooseOrganizationProps | undefined>();

private premountTaskResetPasswordNodes = new Map<HTMLDivElement, TaskResetPasswordProps | undefined>();
// A separate Map of `addListener` method calls to handle multiple listeners.
private premountAddListenerCalls = new Map<
ListenerCallback,
Expand Down Expand Up @@ -676,6 +677,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
clerkjs.mountTaskChooseOrganization(node, props);
});

this.premountTaskResetPasswordNodes.forEach((props, node) => {
clerkjs.mountTaskResetPassword(node, props);
});

/**
* Only update status in case `clerk.status` is missing. In any other case, `clerk-js` should be the orchestrator.
*/
Expand Down Expand Up @@ -1218,6 +1223,22 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
}
};

__experimental_mountTaskResetPassword = (node: HTMLDivElement, props?: TaskResetPasswordProps): void => {
if (this.clerkjs && this.loaded) {
this.clerkjs.mountTaskResetPassword(node, props);
} else {
this.premountTaskResetPasswordNodes.set(node, props);
}
};

unmountTaskResetPassword = (node: HTMLDivElement): void => {
if (this.clerkjs && this.loaded) {
this.clerkjs.unmountTaskResetPassword(node);
} else {
this.premountTaskResetPasswordNodes.delete(node);
}
};

addListener = (listener: ListenerCallback): UnsubscribeCallback => {
if (this.clerkjs) {
return this.clerkjs.addListener(listener);
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/types/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,10 @@ export type Appearance<T = Theme> = T &
* Theme overrides that only apply to the `<TaskChooseOrganization />` component
*/
taskChooseOrganization?: T;
/**
* Theme overrides that only apply to the `<TaskResetPassword />` component
*/
taskResetPassword?: T;
/**
* Theme overrides that only apply to the `<EnableOrganizations/>` component
*/
Expand Down
16 changes: 16 additions & 0 deletions packages/shared/src/types/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,22 @@ export interface Clerk {
*/
unmountTaskChooseOrganization: (targetNode: HTMLDivElement) => void;

/**
* Mounts a TaskResetPassword component at the target element.
*
* @param targetNode - Target node to mount the TaskChooseOrganization component.
* @param props - configuration parameters.
*/
mountTaskResetPassword: (targetNode: HTMLDivElement, props?: TaskResetPasswordProps) => void;

/**
* Unmount a TaskResetPassword component from the target element.
* If there is no component mounted at the target node, results in a noop.
*
* @param targetNode - Target node to unmount the TaskChooseOrganization component from.
*/
unmountTaskResetPassword: (targetNode: HTMLDivElement) => void;

/**
* @internal
* Loads Stripe libraries for commerce functionality
Expand Down