diff --git a/integration/README.md b/integration/README.md
index d3ba125f732..46c3f50c6a9 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -368,6 +368,7 @@ Assuming you have a `react-parcel` template defined in `integration/templates`,
```
Here's what each thing is doing:
+
- `setName`: Set internal name
- `useTemplate`: Define which template inside `integration/templates` to use
- `setEnvFormatter`: Define how environment variables should be formatted. The first argument accepts `'public'` and `'private'`. Inside [`envs.ts`](./presets/envs.ts) the environment variables you can use through [`withEnv`](#environment-configs) are defined. Since different frameworks require environment variables to be in different formats (e.g. Next.js wants public env vars to be prefixed with `NEXT_PUBLIC_`) you can use this formatter to change that.
diff --git a/integration/templates/custom-flows-react-vite/src/main.tsx b/integration/templates/custom-flows-react-vite/src/main.tsx
index bff0b63053b..cc6ea6c54f4 100644
--- a/integration/templates/custom-flows-react-vite/src/main.tsx
+++ b/integration/templates/custom-flows-react-vite/src/main.tsx
@@ -7,6 +7,7 @@ import { Home } from './routes/Home';
import { SignIn } from './routes/SignIn';
import { SignUp } from './routes/SignUp';
import { Protected } from './routes/Protected';
+import { Waitlist } from './routes/Waitlist';
// Import your Publishable Key
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
@@ -37,6 +38,10 @@ createRoot(document.getElementById('root')!).render(
path='/sign-up'
element={}
/>
+ }
+ />
}
diff --git a/integration/templates/custom-flows-react-vite/src/routes/Waitlist.tsx b/integration/templates/custom-flows-react-vite/src/routes/Waitlist.tsx
new file mode 100644
index 00000000000..59fd25015de
--- /dev/null
+++ b/integration/templates/custom-flows-react-vite/src/routes/Waitlist.tsx
@@ -0,0 +1,112 @@
+'use client';
+
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { useWaitlist } from '@clerk/react';
+import { NavLink } from 'react-router';
+
+export function Waitlist({ className, ...props }: React.ComponentProps<'div'>) {
+ const { waitlist, errors, fetchStatus } = useWaitlist();
+
+ const handleSubmit = async (formData: FormData) => {
+ const emailAddress = formData.get('emailAddress') as string | null;
+
+ if (!emailAddress) {
+ return;
+ }
+
+ await waitlist.join({ emailAddress });
+ };
+
+ if (waitlist?.id) {
+ return (
+
+
+
+ Successfully joined!
+ You're on the waitlist
+
+
+
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ Join the Waitlist
+ Enter your email address to join the waitlist
+
+
+
+
+
+
+ );
+}
diff --git a/integration/tests/custom-flows/waitlist.test.ts b/integration/tests/custom-flows/waitlist.test.ts
new file mode 100644
index 00000000000..c1147631cd6
--- /dev/null
+++ b/integration/tests/custom-flows/waitlist.test.ts
@@ -0,0 +1,132 @@
+import { parsePublishableKey } from '@clerk/shared/keys';
+import { clerkSetup } from '@clerk/testing/playwright';
+import { expect, test } from '@playwright/test';
+
+import type { Application } from '../../models/application';
+import { appConfigs } from '../../presets';
+import type { FakeUser } from '../../testUtils';
+import { createTestUtils } from '../../testUtils';
+
+test.describe('Custom Flows Waitlist @custom', () => {
+ test.describe.configure({ mode: 'parallel' });
+ let app: Application;
+ let fakeUser: FakeUser;
+
+ test.beforeAll(async () => {
+ app = await appConfigs.customFlows.reactVite.clone().commit();
+ await app.setup();
+ await app.withEnv(appConfigs.envs.withWaitlistdMode);
+ await app.dev();
+
+ const publishableKey = appConfigs.envs.withWaitlistdMode.publicVariables.get('CLERK_PUBLISHABLE_KEY');
+ const secretKey = appConfigs.envs.withWaitlistdMode.privateVariables.get('CLERK_SECRET_KEY');
+ const apiUrl = appConfigs.envs.withWaitlistdMode.privateVariables.get('CLERK_API_URL');
+ const { frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey);
+
+ await clerkSetup({
+ publishableKey,
+ frontendApiUrl,
+ secretKey,
+ // @ts-expect-error
+ apiUrl,
+ dotenv: false,
+ });
+
+ const u = createTestUtils({ app });
+ fakeUser = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ });
+ });
+
+ test.afterAll(async () => {
+ await fakeUser.deleteIfExists();
+ await app.teardown();
+ });
+
+ test('can join waitlist with email', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/waitlist');
+ await u.page.waitForClerkJsLoaded();
+ await expect(u.page.getByText('Join the Waitlist', { exact: true })).toBeVisible();
+
+ const emailInput = u.page.getByTestId('email-input');
+ const submitButton = u.page.getByTestId('submit-button');
+
+ await emailInput.fill(fakeUser.email);
+ await submitButton.click();
+
+ await expect(u.page.getByText('Successfully joined!')).toBeVisible();
+ await expect(u.page.getByText("You're on the waitlist")).toBeVisible();
+ });
+
+ test('renders error with invalid email', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/waitlist');
+ await u.page.waitForClerkJsLoaded();
+ await expect(u.page.getByText('Join the Waitlist', { exact: true })).toBeVisible();
+
+ const emailInput = u.page.getByTestId('email-input');
+ const submitButton = u.page.getByTestId('submit-button');
+
+ await emailInput.fill('invalid-email@com');
+ await submitButton.click();
+
+ await expect(u.page.getByTestId('email-error')).toBeVisible();
+ });
+
+ test('displays loading state while joining', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/waitlist');
+ await u.page.waitForClerkJsLoaded();
+ await expect(u.page.getByText('Join the Waitlist', { exact: true })).toBeVisible();
+
+ const emailInput = u.page.getByTestId('email-input');
+ const submitButton = u.page.getByTestId('submit-button');
+
+ await emailInput.fill(fakeUser.email);
+
+ const submitPromise = submitButton.click();
+
+ // Check that button is disabled during fetch
+ await expect(submitButton).toBeDisabled();
+
+ await submitPromise;
+
+ // Wait for success state
+ await expect(u.page.getByText('Successfully joined!')).toBeVisible();
+ });
+
+ test('can navigate to sign-in from waitlist', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/waitlist');
+ await u.page.waitForClerkJsLoaded();
+ await expect(u.page.getByText('Join the Waitlist', { exact: true })).toBeVisible();
+
+ const signInLink = u.page.getByTestId('sign-in-link');
+ await expect(signInLink).toBeVisible();
+ await signInLink.click();
+
+ await expect(u.page.getByText('Sign in', { exact: true })).toBeVisible();
+ await u.page.waitForURL(/sign-in/);
+ });
+
+ test('waitlist hook provides correct properties', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/waitlist');
+ await u.page.waitForClerkJsLoaded();
+
+ // Check initial state - waitlist resource should be available but empty
+ const emailInput = u.page.getByTestId('email-input');
+ const submitButton = u.page.getByTestId('submit-button');
+
+ await expect(emailInput).toBeVisible();
+ await expect(submitButton).toBeEnabled();
+
+ // Join waitlist
+ await emailInput.fill(fakeUser.email);
+ await submitButton.click();
+
+ // After successful join, the component should show success state
+ await expect(u.page.getByText('Successfully joined!')).toBeVisible();
+ });
+});
diff --git a/packages/clerk-js/src/core/resources/Waitlist.ts b/packages/clerk-js/src/core/resources/Waitlist.ts
index 3dd13ebbbc8..b728d00b991 100644
--- a/packages/clerk-js/src/core/resources/Waitlist.ts
+++ b/packages/clerk-js/src/core/resources/Waitlist.ts
@@ -1,6 +1,8 @@
-import type { JoinWaitlistParams, WaitlistJSON, WaitlistResource } from '@clerk/shared/types';
+import type { JoinWaitlistParams, WaitlistFutureResource, WaitlistJSON, WaitlistResource } from '@clerk/shared/types';
import { unixEpochToDate } from '../../utils/date';
+import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask';
+import { eventBus } from '../events';
import { BaseResource } from './internal';
export class Waitlist extends BaseResource implements WaitlistResource {
@@ -10,7 +12,22 @@ export class Waitlist extends BaseResource implements WaitlistResource {
updatedAt: Date | null = null;
createdAt: Date | null = null;
- constructor(data: WaitlistJSON) {
+ /**
+ * @experimental This experimental API is subject to change.
+ *
+ * An instance of `WaitlistFuture`, which has a different API than `Waitlist`, intended to be used in custom flows.
+ */
+ __internal_future: WaitlistFuture = new WaitlistFuture(this);
+
+ /**
+ * @internal Only used for internal purposes, and is not intended to be used directly.
+ *
+ * This property is used to provide access to underlying Client methods to `WaitlistFuture`, which wraps an instance
+ * of `Waitlist`.
+ */
+ __internal_basePost = this._basePost.bind(this);
+
+ constructor(data: WaitlistJSON | null = null) {
super();
this.fromJSON(data);
}
@@ -23,6 +40,8 @@ export class Waitlist extends BaseResource implements WaitlistResource {
this.id = data.id;
this.updatedAt = unixEpochToDate(data.updated_at);
this.createdAt = unixEpochToDate(data.created_at);
+
+ eventBus.emit('resource:update', { resource: this });
return this;
}
@@ -38,3 +57,28 @@ export class Waitlist extends BaseResource implements WaitlistResource {
return new Waitlist(json);
}
}
+
+class WaitlistFuture implements WaitlistFutureResource {
+ constructor(readonly resource: Waitlist) {}
+
+ get id() {
+ return this.resource.id || undefined;
+ }
+
+ get createdAt() {
+ return this.resource.createdAt;
+ }
+
+ get updatedAt() {
+ return this.resource.updatedAt;
+ }
+
+ async join(params: JoinWaitlistParams): Promise<{ error: unknown }> {
+ return runAsyncResourceTask(this.resource, async () => {
+ await this.resource.__internal_basePost({
+ path: this.resource.pathRoot,
+ body: params,
+ });
+ });
+ }
+}
diff --git a/packages/clerk-js/src/core/signals.ts b/packages/clerk-js/src/core/signals.ts
index 1228e105fe2..17c98abbe7b 100644
--- a/packages/clerk-js/src/core/signals.ts
+++ b/packages/clerk-js/src/core/signals.ts
@@ -1,10 +1,11 @@
import { isClerkAPIResponseError } from '@clerk/shared/error';
-import type { Errors, SignInSignal, SignUpSignal } from '@clerk/shared/types';
+import type { Errors, SignInSignal, SignUpSignal, WaitlistSignal } from '@clerk/shared/types';
import { snakeToCamel } from '@clerk/shared/underscore';
import { computed, signal } from 'alien-signals';
import type { SignIn } from './resources/SignIn';
import type { SignUp } from './resources/SignUp';
+import type { Waitlist } from './resources/Waitlist';
export const signInResourceSignal = signal<{ resource: SignIn | null }>({ resource: null });
export const signInErrorSignal = signal<{ error: unknown }>({ error: null });
@@ -34,6 +35,20 @@ export const signUpComputedSignal: SignUpSignal = computed(() => {
return { errors, fetchStatus, signUp: signUp ? signUp.__internal_future : null };
});
+export const waitlistResourceSignal = signal<{ resource: Waitlist | null }>({ resource: null });
+export const waitlistErrorSignal = signal<{ error: unknown }>({ error: null });
+export const waitlistFetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' });
+
+export const waitlistComputedSignal: WaitlistSignal = computed(() => {
+ const waitlist = waitlistResourceSignal().resource;
+ const error = waitlistErrorSignal().error;
+ const fetchStatus = waitlistFetchSignal().status;
+
+ const errors = errorsToParsedErrors(error);
+
+ return { errors, fetchStatus, waitlist: waitlist ? waitlist.__internal_future : null };
+});
+
/**
* Converts an error to a parsed errors object that reports the specific fields that the error pertains to. Will put
* generic non-API errors into the global array.
diff --git a/packages/clerk-js/src/core/state.ts b/packages/clerk-js/src/core/state.ts
index 82fed3485ac..077d16939da 100644
--- a/packages/clerk-js/src/core/state.ts
+++ b/packages/clerk-js/src/core/state.ts
@@ -5,6 +5,7 @@ import { eventBus } from './events';
import type { BaseResource } from './resources/Base';
import { SignIn } from './resources/SignIn';
import { SignUp } from './resources/SignUp';
+import { Waitlist } from './resources/Waitlist';
import {
signInComputedSignal,
signInErrorSignal,
@@ -14,6 +15,10 @@ import {
signUpErrorSignal,
signUpFetchSignal,
signUpResourceSignal,
+ waitlistComputedSignal,
+ waitlistErrorSignal,
+ waitlistFetchSignal,
+ waitlistResourceSignal,
} from './signals';
export class State implements StateInterface {
@@ -27,6 +32,13 @@ export class State implements StateInterface {
signUpFetchSignal = signUpFetchSignal;
signUpSignal = signUpComputedSignal;
+ waitlistResourceSignal = waitlistResourceSignal;
+ waitlistErrorSignal = waitlistErrorSignal;
+ waitlistFetchSignal = waitlistFetchSignal;
+ waitlistSignal = waitlistComputedSignal;
+
+ private _waitlistInstance: Waitlist | null = null;
+
__internal_effect = effect;
__internal_computed = computed;
@@ -34,6 +46,13 @@ export class State implements StateInterface {
eventBus.on('resource:update', this.onResourceUpdated);
eventBus.on('resource:error', this.onResourceError);
eventBus.on('resource:fetch', this.onResourceFetch);
+
+ this._waitlistInstance = new Waitlist(null);
+ this.waitlistResourceSignal({ resource: this._waitlistInstance });
+ }
+
+ get __internal_waitlist() {
+ return this._waitlistInstance;
}
private onResourceError = (payload: { resource: BaseResource; error: unknown }) => {
@@ -44,6 +63,10 @@ export class State implements StateInterface {
if (payload.resource instanceof SignUp) {
this.signUpErrorSignal({ error: payload.error });
}
+
+ if (payload.resource instanceof Waitlist) {
+ this.waitlistErrorSignal({ error: payload.error });
+ }
};
private onResourceUpdated = (payload: { resource: BaseResource }) => {
@@ -54,6 +77,10 @@ export class State implements StateInterface {
if (payload.resource instanceof SignUp) {
this.signUpResourceSignal({ resource: payload.resource });
}
+
+ if (payload.resource instanceof Waitlist) {
+ this.waitlistResourceSignal({ resource: payload.resource });
+ }
};
private onResourceFetch = (payload: { resource: BaseResource; status: 'idle' | 'fetching' }) => {
@@ -64,5 +91,9 @@ export class State implements StateInterface {
if (payload.resource instanceof SignUp) {
this.signUpFetchSignal({ status: payload.status });
}
+
+ if (payload.resource instanceof Waitlist) {
+ this.waitlistFetchSignal({ status: payload.status });
+ }
};
}
diff --git a/packages/localizations/README.md b/packages/localizations/README.md
index 560fdb73126..dcd5092fc6c 100644
--- a/packages/localizations/README.md
+++ b/packages/localizations/README.md
@@ -66,6 +66,7 @@ We're open to all community contributions! If you'd like to contribute in any wa
1. Open the [`localizations/src/en-US.ts`](https://github.com/clerk/javascript/blob/main/packages/localizations/src/en-US.ts) file and add your new key to the object. `en-US` is the default language. If you feel comfortable adding your message in another language than English, feel free to also edit other files.
1. Use the new localization key inside the component. There are two ways:
+
- The string is inside a component like ``:
```diff
diff --git a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap
index 8b268b93a36..cba4c47026a 100644
--- a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap
+++ b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap
@@ -65,6 +65,7 @@ exports[`root public exports > should not change unexpectedly 1`] = `
"useSignIn",
"useSignUp",
"useUser",
+ "useWaitlist",
]
`;
diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts
index 8beaba1c56f..b2ff54e467d 100644
--- a/packages/react/src/hooks/index.ts
+++ b/packages/react/src/hooks/index.ts
@@ -1,6 +1,6 @@
export { useAuth } from './useAuth';
export { useEmailLink } from './useEmailLink';
-export { useSignIn, useSignUp } from './useClerkSignal';
+export { useSignIn, useSignUp, useWaitlist } from './useClerkSignal';
export {
useClerk,
useOrganization,
diff --git a/packages/react/src/hooks/useClerkSignal.ts b/packages/react/src/hooks/useClerkSignal.ts
index a3aa9cbe7c6..63c5fcdae5f 100644
--- a/packages/react/src/hooks/useClerkSignal.ts
+++ b/packages/react/src/hooks/useClerkSignal.ts
@@ -1,5 +1,5 @@
import { eventMethodCalled } from '@clerk/shared/telemetry';
-import type { SignInSignalValue, SignUpSignalValue } from '@clerk/shared/types';
+import type { SignInSignalValue, SignUpSignalValue, WaitlistSignalValue } from '@clerk/shared/types';
import { useCallback, useSyncExternalStore } from 'react';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
@@ -7,7 +7,10 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
function useClerkSignal(signal: 'signIn'): SignInSignalValue;
function useClerkSignal(signal: 'signUp'): SignUpSignalValue;
-function useClerkSignal(signal: 'signIn' | 'signUp'): SignInSignalValue | SignUpSignalValue {
+function useClerkSignal(signal: 'waitlist'): WaitlistSignalValue;
+function useClerkSignal(
+ signal: 'signIn' | 'signUp' | 'waitlist',
+): SignInSignalValue | SignUpSignalValue | WaitlistSignalValue {
useAssertWrappedByClerkProvider('useClerkSignal');
const clerk = useIsomorphicClerkContext();
@@ -37,6 +40,9 @@ function useClerkSignal(signal: 'signIn' | 'signUp'): SignInSignalValue | SignUp
case 'signUp':
clerk.__internal_state.signUpSignal();
break;
+ case 'waitlist':
+ clerk.__internal_state.waitlistSignal();
+ break;
default:
throw new Error(`Unknown signal: ${signal}`);
}
@@ -51,6 +57,8 @@ function useClerkSignal(signal: 'signIn' | 'signUp'): SignInSignalValue | SignUp
return clerk.__internal_state.signInSignal() as SignInSignalValue;
case 'signUp':
return clerk.__internal_state.signUpSignal() as SignUpSignalValue;
+ case 'waitlist':
+ return clerk.__internal_state.waitlistSignal() as WaitlistSignalValue;
default:
throw new Error(`Unknown signal: ${signal}`);
}
@@ -94,3 +102,20 @@ export function useSignIn() {
export function useSignUp() {
return useClerkSignal('signUp');
}
+
+/**
+ * This hook allows you to access the Signal-based `Waitlist` resource.
+ *
+ * @example
+ * import { useWaitlist } from "@clerk/react/experimental";
+ *
+ * function WaitlistForm() {
+ * const { waitlist, errors, fetchStatus } = useWaitlist();
+ * //
+ * }
+ *
+ * @experimental This experimental API is subject to change.
+ */
+export function useWaitlist() {
+ return useClerkSignal('waitlist');
+}
diff --git a/packages/react/src/stateProxy.ts b/packages/react/src/stateProxy.ts
index dcb8c59cf0e..4696fc2ae76 100644
--- a/packages/react/src/stateProxy.ts
+++ b/packages/react/src/stateProxy.ts
@@ -26,6 +26,7 @@ export class StateProxy implements State {
private readonly signInSignalProxy = this.buildSignInProxy();
private readonly signUpSignalProxy = this.buildSignUpProxy();
+ private readonly waitlistSignalProxy = this.buildWaitlistProxy();
signInSignal() {
return this.signInSignalProxy;
@@ -33,6 +34,16 @@ export class StateProxy implements State {
signUpSignal() {
return this.signUpSignalProxy;
}
+ waitlistSignal() {
+ return this.waitlistSignalProxy;
+ }
+
+ get __internal_waitlist() {
+ if (!inBrowser() || !this.isomorphicClerk.loaded) {
+ return null;
+ }
+ return this.isomorphicClerk.__internal_state.__internal_waitlist;
+ }
private buildSignInProxy() {
const gateProperty = this.gateProperty.bind(this);
@@ -226,6 +237,46 @@ export class StateProxy implements State {
};
}
+ private buildWaitlistProxy() {
+ const gateProperty = this.gateProperty.bind(this);
+ const gateMethod = this.gateMethod.bind(this);
+ const fallbackWaitlistFuture = {
+ id: undefined,
+ createdAt: null,
+ updatedAt: null,
+ join: () => Promise.resolve({ error: null }),
+ };
+ const target = (): typeof fallbackWaitlistFuture => {
+ if (!inBrowser() || !this.isomorphicClerk.loaded) {
+ return fallbackWaitlistFuture;
+ }
+ const state = this.isomorphicClerk.__internal_state;
+ const waitlist = state.__internal_waitlist;
+ if (waitlist && '__internal_future' in waitlist) {
+ return (waitlist as { __internal_future: typeof fallbackWaitlistFuture }).__internal_future;
+ }
+ return fallbackWaitlistFuture;
+ };
+
+ return {
+ errors: defaultErrors(),
+ fetchStatus: 'idle' as const,
+ waitlist: {
+ get id() {
+ return gateProperty(target, 'id', undefined);
+ },
+ get createdAt() {
+ return gateProperty(target, 'createdAt', null);
+ },
+ get updatedAt() {
+ return gateProperty(target, 'updatedAt', null);
+ },
+
+ join: gateMethod(target, 'join'),
+ },
+ };
+ }
+
__internal_effect(_: () => void): () => void {
throw new Error('__internal_effect called before Clerk is loaded');
}
diff --git a/packages/shared/src/compiled/path-to-regexp/index.js b/packages/shared/src/compiled/path-to-regexp/index.js
index 7dbeaca3864..7112d4ae2b8 100644
--- a/packages/shared/src/compiled/path-to-regexp/index.js
+++ b/packages/shared/src/compiled/path-to-regexp/index.js
@@ -45,12 +45,12 @@ function _(r) {
break;
}
if (!u) throw new TypeError('Missing parameter name at '.concat(e));
- (n.push({
+ n.push({
type: 'NAME',
index: e,
value: u,
}),
- (e = t));
+ (e = t);
continue;
}
if (a === '(') {
@@ -74,12 +74,12 @@ function _(r) {
}
if (o) throw new TypeError('Unbalanced pattern at '.concat(e));
if (!m) throw new TypeError('Missing pattern at '.concat(e));
- (n.push({
+ n.push({
type: 'PATTERN',
index: e,
value: m,
}),
- (e = t));
+ (e = t);
continue;
}
n.push({
@@ -147,7 +147,7 @@ function F(r, n) {
C = f('PATTERN');
if (x || C) {
var g = T || '';
- (u.indexOf(g) === -1 && ((p += g), (g = '')),
+ u.indexOf(g) === -1 && ((p += g), (g = '')),
p && (o.push(p), (p = '')),
o.push({
name: x || m++,
@@ -155,7 +155,7 @@ function F(r, n) {
suffix: '',
pattern: C || A(g),
modifier: f('MODIFIER') || '',
- }));
+ });
continue;
}
var i = T || f('ESCAPED_CHAR');
@@ -170,14 +170,14 @@ function F(r, n) {
y = f('NAME') || '',
O = f('PATTERN') || '',
b = d();
- (w('CLOSE'),
+ w('CLOSE'),
o.push({
name: y || (O ? m++ : ''),
pattern: y && !O ? A(g) : O,
prefix: g,
suffix: b,
modifier: f('MODIFIER') || '',
- }));
+ });
continue;
}
w('END');
@@ -240,14 +240,14 @@ function D(r) {
function $(r, n) {
if (!n) return r;
for (var e = /\((?:\?<(.*?)>)?(?!\?)/g, a = 0, u = e.exec(r.source); u; )
- (n.push({
+ n.push({
name: u[1] || a++,
prefix: '',
suffix: '',
modifier: '',
pattern: '',
}),
- (u = e.exec(r.source)));
+ (u = e.exec(r.source));
return r;
}
@@ -316,11 +316,11 @@ function U(r, n, e) {
else x += '(?:'.concat(R).concat(y, ')').concat(i.modifier);
}
}
- if (m) (u || (x += ''.concat(T, '?')), (x += e.endsWith ? '(?='.concat(A, ')') : '$'));
+ if (m) u || (x += ''.concat(T, '?')), (x += e.endsWith ? '(?='.concat(A, ')') : '$');
else {
var b = r[r.length - 1],
l = typeof b == 'string' ? T.indexOf(b[b.length - 1]) > -1 : b === void 0;
- (u || (x += '(?:'.concat(T, '(?=').concat(A, '))?')), l || (x += '(?='.concat(T, '|').concat(A, ')')));
+ u || (x += '(?:'.concat(T, '(?=').concat(A, '))?')), l || (x += '(?='.concat(T, '|').concat(A, ')'));
}
return new RegExp(x, D(e));
}
diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts
index a8fc6103bc9..1d97f7003e1 100644
--- a/packages/shared/src/types/clerk.ts
+++ b/packages/shared/src/types/clerk.ts
@@ -60,7 +60,7 @@ import type { Web3Strategy } from './strategies';
import type { TelemetryCollector } from './telemetry';
import type { UserResource } from './user';
import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils';
-import type { WaitlistResource } from './waitlist';
+import type { JoinWaitlistParams, WaitlistResource } from './waitlist';
type __experimental_CheckoutStatus = 'needs_initialization' | 'needs_confirmation' | 'completed';
@@ -2149,10 +2149,6 @@ export interface ClerkAuthenticateWithWeb3Params {
secondFactorUrl?: string;
}
-export type JoinWaitlistParams = {
- emailAddress: string;
-};
-
export interface AuthenticateWithMetamaskParams {
customNavigate?: (to: string) => Promise;
redirectUrl?: string;
diff --git a/packages/shared/src/types/state.ts b/packages/shared/src/types/state.ts
index 4438d92fe57..457d2cef7aa 100644
--- a/packages/shared/src/types/state.ts
+++ b/packages/shared/src/types/state.ts
@@ -1,5 +1,6 @@
import type { SignInFutureResource } from './signInFuture';
import type { SignUpFutureResource } from './signUpFuture';
+import type { WaitlistFutureResource, WaitlistResource } from './waitlist';
/**
* Represents an error on a specific field.
@@ -128,6 +129,27 @@ export interface SignUpSignal {
(): NullableSignUpSignal;
}
+export interface WaitlistSignalValue {
+ /**
+ * The errors that occurred during the last fetch of the underlying `Waitlist` resource.
+ */
+ errors: Errors;
+ /**
+ * The fetch status of the underlying `Waitlist` resource.
+ */
+ fetchStatus: 'idle' | 'fetching';
+ /**
+ * The underlying `Waitlist` resource.
+ */
+ waitlist: WaitlistFutureResource;
+}
+export type NullableWaitlistSignal = Omit & {
+ waitlist: WaitlistFutureResource | null;
+};
+export interface WaitlistSignal {
+ (): NullableWaitlistSignal;
+}
+
export interface State {
/**
* A Signal that updates when the underlying `SignIn` resource changes, including errors.
@@ -139,6 +161,11 @@ export interface State {
*/
signUpSignal: SignUpSignal;
+ /**
+ * A Signal that updates when the underlying `Waitlist` resource changes, including errors.
+ */
+ waitlistSignal: WaitlistSignal;
+
/**
* An alias for `effect()` from `alien-signals`, which can be used to subscribe to changes from Signals.
*
@@ -155,4 +182,8 @@ export interface State {
* @experimental This experimental API is subject to change.
*/
__internal_computed: (getter: (previousValue?: T) => T) => () => T;
+ /**
+ * An instance of the Waitlist resource.
+ */
+ __internal_waitlist: WaitlistResource | null;
}
diff --git a/packages/shared/src/types/waitlist.ts b/packages/shared/src/types/waitlist.ts
index 8b8fe3a7ee1..016a08cc868 100644
--- a/packages/shared/src/types/waitlist.ts
+++ b/packages/shared/src/types/waitlist.ts
@@ -5,3 +5,29 @@ export interface WaitlistResource extends ClerkResource {
createdAt: Date | null;
updatedAt: Date | null;
}
+
+export interface WaitlistFutureResource {
+ /**
+ * The unique identifier for the waitlist entry. `null` if the user has not joined the waitlist yet.
+ */
+ readonly id?: string;
+
+ /**
+ * The date and time the waitlist entry was created. `null` if the user has not joined the waitlist yet.
+ */
+ readonly createdAt: Date | null;
+
+ /**
+ * The date and time the waitlist entry was last updated. `null` if the user has not joined the waitlist yet.
+ */
+ readonly updatedAt: Date | null;
+
+ /**
+ * Used to join the waitlist with the provided email address.
+ */
+ join: (params: JoinWaitlistParams) => Promise<{ error: unknown }>;
+}
+
+export type JoinWaitlistParams = {
+ emailAddress: string;
+};
diff --git a/packages/tanstack-react-start/package.json b/packages/tanstack-react-start/package.json
index 98cd95e5390..992282d1a32 100644
--- a/packages/tanstack-react-start/package.json
+++ b/packages/tanstack-react-start/package.json
@@ -47,10 +47,6 @@
"types": "./dist/legacy.d.ts",
"default": "./dist/legacy.js"
},
- "./experimental": {
- "types": "./dist/experimental.d.ts",
- "default": "./dist/experimental.js"
- },
"./package.json": "./package.json"
},
"main": "dist/index.js",
diff --git a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap
index c8a1f96ceba..6778dea3902 100644
--- a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap
+++ b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap
@@ -70,6 +70,7 @@ exports[`root public exports > should not change unexpectedly 1`] = `
"useSignIn",
"useSignUp",
"useUser",
+ "useWaitlist",
]
`;
diff --git a/packages/tanstack-react-start/src/experimental.ts b/packages/tanstack-react-start/src/experimental.ts
deleted file mode 100644
index fc3fa4e8f09..00000000000
--- a/packages/tanstack-react-start/src/experimental.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '@clerk/react/experimental';