Skip to content

Commit db50c47

Browse files
authored
feat(clerk-js,clerk-react,types): Signal SignUp (#6568)
1 parent 8e66e73 commit db50c47

7 files changed

Lines changed: 121 additions & 12 deletions

File tree

.changeset/tasty-bears-sip.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/clerk-react': minor
4+
'@clerk/types': minor
5+
---
6+
7+
[Experimental] Signal implementation for SignUp

packages/clerk-js/src/core/resources/SignUp.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
SignUpAuthenticateWithWeb3Params,
1717
SignUpCreateParams,
1818
SignUpField,
19+
SignUpFutureResource,
1920
SignUpIdentificationField,
2021
SignUpJSON,
2122
SignUpJSONSnapshot,
@@ -45,6 +46,7 @@ import {
4546
clerkVerifyEmailAddressCalledBeforeCreate,
4647
clerkVerifyWeb3WalletCalledBeforeCreate,
4748
} from '../errors';
49+
import { eventBus } from '../events';
4850
import { BaseResource, ClerkRuntimeError, SignUpVerifications } from './internal';
4951

5052
declare global {
@@ -77,6 +79,21 @@ export class SignUp extends BaseResource implements SignUpResource {
7779
abandonAt: number | null = null;
7880
legalAcceptedAt: number | null = null;
7981

82+
/**
83+
* @experimental This experimental API is subject to change.
84+
*
85+
* An instance of `SignUpFuture`, which has a different API than `SignUp`, intended to be used in custom flows.
86+
*/
87+
__internal_future: SignUpFuture | null = new SignUpFuture(this);
88+
89+
/**
90+
* @internal Only used for internal purposes, and is not intended to be used directly.
91+
*
92+
* This property is used to provide access to underlying Client methods to `SignUpFuture`, which wraps an instance
93+
* of `SignUp`.
94+
*/
95+
__internal_basePost = this._basePost.bind(this);
96+
8097
constructor(data: SignUpJSON | SignUpJSONSnapshot | null = null) {
8198
super();
8299
this.fromJSON(data);
@@ -389,6 +406,8 @@ export class SignUp extends BaseResource implements SignUpResource {
389406
this.web3wallet = data.web3_wallet;
390407
this.legalAcceptedAt = data.legal_accepted_at;
391408
}
409+
410+
eventBus.emit('resource:update', { resource: this });
392411
return this;
393412
}
394413

@@ -449,3 +468,11 @@ export class SignUp extends BaseResource implements SignUpResource {
449468
return false;
450469
}
451470
}
471+
472+
class SignUpFuture implements SignUpFutureResource {
473+
constructor(readonly resource: SignUp) {}
474+
475+
get status() {
476+
return this.resource.status;
477+
}
478+
}

packages/clerk-js/src/core/signals.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { isClerkAPIResponseError } from '@clerk/shared/error';
2-
import type { Errors } from '@clerk/types';
2+
import type { Errors, SignInSignal, SignUpSignal } from '@clerk/types';
33
import { computed, signal } from 'alien-signals';
44

55
import type { SignIn } from './resources/SignIn';
6+
import type { SignUp } from './resources/SignUp';
67

78
export const signInResourceSignal = signal<{ resource: SignIn | null }>({ resource: null });
89
export const signInErrorSignal = signal<{ error: unknown }>({ error: null });
910
export const signInFetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' });
1011

11-
export const signInComputedSignal = computed(() => {
12+
export const signInComputedSignal: SignInSignal = computed(() => {
1213
const signIn = signInResourceSignal().resource;
1314
const error = signInErrorSignal().error;
1415
const fetchStatus = signInFetchSignal().status;
@@ -18,6 +19,20 @@ export const signInComputedSignal = computed(() => {
1819
return { errors, fetchStatus, signIn: signIn ? signIn.__internal_future : null };
1920
});
2021

22+
export const signUpResourceSignal = signal<{ resource: SignUp | null }>({ resource: null });
23+
export const signUpErrorSignal = signal<{ error: unknown }>({ error: null });
24+
export const signUpFetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' });
25+
26+
export const signUpComputedSignal: SignUpSignal = computed(() => {
27+
const signUp = signUpResourceSignal().resource;
28+
const error = signUpErrorSignal().error;
29+
const fetchStatus = signUpFetchSignal().status;
30+
31+
const errors = errorsToParsedErrors(error);
32+
33+
return { errors, fetchStatus, signUp: signUp ? signUp.__internal_future : null };
34+
});
35+
2136
/**
2237
* Converts an error to a parsed errors object that reports the specific fields that the error pertains to. Will put
2338
* generic non-API errors into the global array.

packages/clerk-js/src/core/state.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,29 @@ import { computed, effect } from 'alien-signals';
44
import { eventBus } from './events';
55
import type { BaseResource } from './resources/Base';
66
import { SignIn } from './resources/SignIn';
7-
import { signInComputedSignal, signInErrorSignal, signInFetchSignal, signInResourceSignal } from './signals';
7+
import { SignUp } from './resources/SignUp';
8+
import {
9+
signInComputedSignal,
10+
signInErrorSignal,
11+
signInFetchSignal,
12+
signInResourceSignal,
13+
signUpComputedSignal,
14+
signUpErrorSignal,
15+
signUpFetchSignal,
16+
signUpResourceSignal,
17+
} from './signals';
818

919
export class State implements StateInterface {
1020
signInResourceSignal = signInResourceSignal;
1121
signInErrorSignal = signInErrorSignal;
1222
signInFetchSignal = signInFetchSignal;
1323
signInSignal = signInComputedSignal;
1424

25+
signUpResourceSignal = signUpResourceSignal;
26+
signUpErrorSignal = signUpErrorSignal;
27+
signUpFetchSignal = signUpFetchSignal;
28+
signUpSignal = signUpComputedSignal;
29+
1530
__internal_effect = effect;
1631
__internal_computed = computed;
1732

@@ -25,17 +40,29 @@ export class State implements StateInterface {
2540
if (payload.resource instanceof SignIn) {
2641
this.signInErrorSignal({ error: payload.error });
2742
}
43+
44+
if (payload.resource instanceof SignUp) {
45+
this.signUpResourceSignal({ resource: payload.resource });
46+
}
2847
};
2948

3049
private onResourceUpdated = (payload: { resource: BaseResource }) => {
3150
if (payload.resource instanceof SignIn) {
3251
this.signInResourceSignal({ resource: payload.resource });
3352
}
53+
54+
if (payload.resource instanceof SignUp) {
55+
this.signUpResourceSignal({ resource: payload.resource });
56+
}
3457
};
3558

3659
private onResourceFetch = (payload: { resource: BaseResource; status: 'idle' | 'fetching' }) => {
3760
if (payload.resource instanceof SignIn) {
3861
this.signInFetchSignal({ status: payload.status });
3962
}
63+
64+
if (payload.resource instanceof SignUp) {
65+
this.signUpFetchSignal({ status: payload.status });
66+
}
4067
};
4168
}

packages/react/src/hooks/useClerkSignal.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import type { SignInSignal, SignUpSignal } from '@clerk/types';
12
import { useCallback, useSyncExternalStore } from 'react';
23

34
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
45
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';
56

6-
function useClerkSignal(signal: 'signIn') {
7-
useAssertWrappedByClerkProvider('useSignInSignal');
7+
function useClerkSignal(signal: 'signIn'): ReturnType<SignInSignal> | null;
8+
function useClerkSignal(signal: 'signUp'): ReturnType<SignUpSignal> | null;
9+
function useClerkSignal(signal: 'signIn' | 'signUp'): ReturnType<SignInSignal> | ReturnType<SignUpSignal> | null {
10+
useAssertWrappedByClerkProvider('useClerkSignal');
811

912
const clerk = useIsomorphicClerkContext();
1013

@@ -20,6 +23,10 @@ function useClerkSignal(signal: 'signIn') {
2023
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the state is defined
2124
clerk.__internal_state!.signInSignal();
2225
break;
26+
case 'signUp':
27+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the state is defined
28+
clerk.__internal_state!.signUpSignal();
29+
break;
2330
default:
2431
throw new Error(`Unknown signal: ${signal}`);
2532
}
@@ -36,6 +43,8 @@ function useClerkSignal(signal: 'signIn') {
3643
switch (signal) {
3744
case 'signIn':
3845
return clerk.__internal_state.signInSignal();
46+
case 'signUp':
47+
return clerk.__internal_state.signUpSignal();
3948
default:
4049
throw new Error(`Unknown signal: ${signal}`);
4150
}
@@ -49,3 +58,7 @@ function useClerkSignal(signal: 'signIn') {
4958
export function useSignInSignal() {
5059
return useClerkSignal('signIn');
5160
}
61+
62+
export function useSignUpSignal() {
63+
return useClerkSignal('signUp');
64+
}

packages/types/src/signUp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ export interface SignUpResource extends ClerkResource {
118118
__internal_toSnapshot: () => SignUpJSONSnapshot;
119119
}
120120

121+
export interface SignUpFutureResource {
122+
status: SignUpStatus | null;
123+
}
124+
121125
export type SignUpStatus = 'missing_requirements' | 'complete' | 'abandoned';
122126

123127
export type SignUpField = SignUpAttributeField | SignUpIdentificationField;

packages/types/src/state.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SignInFutureResource } from './signIn';
2+
import type { SignUpFutureResource } from './signUp';
23

34
interface FieldError {
45
code: string;
@@ -25,17 +26,32 @@ export interface Errors {
2526
global: unknown[]; // does not include any errors that could be parsed as a field error
2627
}
2728

29+
export interface SignInSignal {
30+
(): {
31+
errors: Errors;
32+
fetchStatus: 'idle' | 'fetching';
33+
signIn: SignInFutureResource | null;
34+
};
35+
}
36+
37+
export interface SignUpSignal {
38+
(): {
39+
errors: Errors;
40+
fetchStatus: 'idle' | 'fetching';
41+
signUp: SignUpFutureResource | null;
42+
};
43+
}
44+
2845
export interface State {
2946
/**
3047
* A Signal that updates when the underlying `SignIn` resource changes, including errors.
3148
*/
32-
signInSignal: {
33-
(): {
34-
errors: Errors;
35-
fetchStatus: 'idle' | 'fetching';
36-
signIn: SignInFutureResource | null;
37-
};
38-
};
49+
signInSignal: SignInSignal;
50+
51+
/**
52+
* A Signal that updates when the underlying `SignUp` resource changes, including errors.
53+
*/
54+
signUpSignal: SignUpSignal;
3955

4056
/**
4157
* @experimental This experimental API is subject to change.

0 commit comments

Comments
 (0)