Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
dfa3a97
chore: update @clerk/clerk-expo and @clerk/types to latest versions
Jan 19, 2026
3caea27
Merge pull request #45 from RonasIT/PRD-2163-update-clerk-version
veliseev93 Jan 19, 2026
b2b1332
feat: add separated auth hooks and simplify types
Jan 20, 2026
88a554d
fix: remove username as auth method
Jan 20, 2026
c18d870
feat: add JSDoc to updated sign-up and sign-in hooks
Jan 21, 2026
b9cdcfc
fix: revert unintentional changes
Jan 21, 2026
9a22787
feat: improve startSignUp params with clerk natives
Jan 22, 2026
73a89e2
chore: remove unused types
Jan 22, 2026
cb5deba
fix: minor fix
Jan 22, 2026
ac95d94
chore: fix clerk examples
Jan 22, 2026
18e8650
Merge pull request #46 from RonasIT/PRD-2164-improve-sign-up-sign-in-…
veliseev93 Jan 22, 2026
de4efe3
feat: enhance authorization flow and loading state management in useA…
Jan 22, 2026
9f6fc76
feat: add useUpdatePassword hook and update types for password manage…
Jan 22, 2026
569ba62
Merge pull request #47 from RonasIT/PRD-2165-clerk-fix-loading-state-…
veliseev93 Jan 26, 2026
b0dbcd2
feat: add use-update-identifier hook and enhance use-add-identifier w…
Jan 26, 2026
cd3d1f9
fix: force reload user model after adding new identifier
Jan 26, 2026
3eaf60d
Merge pull request #48 from RonasIT/PRD-2166-clerk-update-password-hook
veliseev93 Jan 27, 2026
cc41536
Merge branch 'PRD-2150-update-clerk-module' into PRD-2167-update-iden…
Jan 28, 2026
3ce12db
feat: enhance useAddIdentifier and useUpdateIdentifier hooks for bett…
Jan 28, 2026
04d46a1
docs: update documentation for use-update-identifier hook to clarify …
Jan 29, 2026
9229679
Merge pull request #49 from RonasIT/PRD-2167-update-identificator-hook
veliseev93 Feb 2, 2026
0922621
feat: enhance useResetPassword with verification functionality and im…
Feb 2, 2026
d50bd68
fix: minor fix
Feb 2, 2026
14cb5c2
feat: extend useResetPassword to include tokenTemplate parameter and …
Feb 2, 2026
a7aff08
Merge pull request #51 from RonasIT/PRD-2186-update-reset-password-hook
veliseev93 Feb 2, 2026
d022c2c
fix: simplify useResetPassword by removing unnecessary code factor ch…
Feb 11, 2026
e1ce458
feat: update OTP handling to differentiate between sign-up and sign-i…
Feb 19, 2026
275f3dc
fix: minor fix
Feb 19, 2026
807daaf
Merge pull request #52 from RonasIT/PRD-2214-fix-error-with-otp-code
veliseev93 Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,742 changes: 1,492 additions & 250 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@clerk/clerk-expo": "^2.19.6",
"@clerk/types": "^4.101.3",
"@clerk/clerk-expo": "^2.19.18",
"@clerk/types": "^4.101.11",
"@eslint/compat": "^1.4.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.37.0",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/src/features/clerk/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from './use-otp-verification';
export * from './use-auth-with-identifier';
export * from './use-add-identifier';
export * from './use-reset-password';
export * from './use-update-password';
export * from './use-update-identifier';
53 changes: 23 additions & 30 deletions src/lib/src/features/clerk/hooks/use-add-identifier.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
import { isClerkAPIResponseError, useUser } from '@clerk/clerk-expo';
import { EmailAddressResource, PhoneNumberResource } from '@clerk/types';
import { useState } from 'react';
import { ClerkApiError } from '../enums';
import { UseAddIdentifierReturn } from '../types';
import { IdentifierType, UseAddIdentifierReturn } from '../types';

/**
* Hook that provides functionality to add new email or phone number identifiers to a user's account and verify them using verification codes.
*
* @param {IdentifierType} type - Specifies the type of identifier (e.g., 'phone', 'email')
*
* @returns {UseAddIdentifierReturn} Object containing:
* - `createIdentifier` - A function to add a new email or phone number identifier to the user's account and prepare it for verification
* - `verifyCode` - A function to verify a code sent to the identifier, completing the verification process
* - `isCreating` - A boolean indicating whether an identifier is currently being added
* - `isVerifying` - A boolean indicating whether a verification code is currently being processed
*/
export function useAddIdentifier(): UseAddIdentifierReturn {
export function useAddIdentifier(type: IdentifierType): UseAddIdentifierReturn {
const { user } = useUser();
const [identifierResource, setIdentifierResource] = useState<PhoneNumberResource | EmailAddressResource>();
const [isCreating, setIsCreating] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);

const isEmail = type === 'email';

const createIdentifier: UseAddIdentifierReturn['createIdentifier'] = async ({ identifier }) => {
setIsCreating(true);
const isEmail = identifier?.includes('@');

try {
isEmail
? await user?.createEmailAddress({ email: identifier })
: await user?.createPhoneNumber({ phoneNumber: identifier });

await user?.reload();
let resource = isEmail
? user?.emailAddresses.find((a) => a.emailAddress === identifier)
: user?.phoneNumbers.find((a) => a.phoneNumber === identifier);

// If the resource already exists, re-creating it will cause an error,
// so skip the creation step and go to the send verification code flow.
if (!resource) {
resource = isEmail
? await user?.createEmailAddress({ email: identifier })
: await user?.createPhoneNumber({ phoneNumber: identifier });

await user?.reload();
}
await prepareVerification({ isEmail, identifier });

await prepareVerification({ identifier, isEmail });
setIdentifierResource(resource);

return { isSuccess: true, user };
} catch (e) {
if (isClerkAPIResponseError(e)) {
const error = e.errors[0];

if (error?.code === ClerkApiError.FORM_IDENTIFIER_EXIST && !getIdentifierVerified({ identifier, isEmail })) {
await prepareVerification({ identifier, isEmail });

await user?.reload();

return { isSuccess: true, user };
} else {
return { error: e, user };
}
return { error: e, user };
}

return { user, isSuccess: false };
return { isSuccess: false, user };
} finally {
setIsCreating(false);
}
Expand Down Expand Up @@ -82,13 +83,5 @@ export function useAddIdentifier(): UseAddIdentifierReturn {
setIdentifierResource(isEmail ? emailResource : phoneResource);
};

const getIdentifierVerified = ({ identifier, isEmail }: { identifier: string; isEmail: boolean }): boolean => {
const identifierResource = isEmail
? user?.emailAddresses?.find((a) => a.emailAddress === identifier)
: user?.phoneNumbers?.find((a) => a.phoneNumber === identifier);

return identifierResource?.verification?.status === 'verified';
};

return { createIdentifier, verifyCode, isCreating, isVerifying };
}
47 changes: 34 additions & 13 deletions src/lib/src/features/clerk/hooks/use-auth-with-identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IdentifierMethodFor,
StartAuthParams,
StartSignInWithIdentifierReturn,
StartSignUpParams,
StartSignUpWithIdentifierReturn,
UseAuthWithIdentifierReturn,
} from '../types';
Expand Down Expand Up @@ -37,6 +38,8 @@ export function useAuthWithIdentifier<
const { signUp, signIn, setActive } = useClerkResources();
const { sendOtpCode, verifyCode: verifyOtpCode, isVerifying } = useOtpVerification();
const { getSessionToken } = useGetSessionToken();

const [isAuthorizing, setIsAuthorizing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const strategy = method === 'emailAddress' ? 'email_code' : 'phone_code';

Expand Down Expand Up @@ -77,6 +80,7 @@ export function useAuthWithIdentifier<
): Promise<StartSignInWithIdentifierReturn<TVerifyBy> | StartSignUpWithIdentifierReturn<TMethod>> => {
const { identifier, password, tokenTemplate } = params;
const authMethod = isSignUp ? signUp : signIn;

const authAttempt = await authMethod?.create({ username: identifier, password });

if (authAttempt?.status === 'complete' && 'createdSessionId' in authAttempt) {
Expand Down Expand Up @@ -111,7 +115,7 @@ export function useAuthWithIdentifier<
} else if (verifyBy === 'otp') {
try {
await authMethod?.create({ [identifierFieldName]: identifier });
await sendOtpCode(strategy);
await sendOtpCode({ strategy, isSignUp });
} catch (error) {
return { error, signIn, signUp };
}
Expand All @@ -128,8 +132,8 @@ export function useAuthWithIdentifier<
setIsLoading(true);

return method === 'username'
? handleUsernameAuth(params as StartAuthParams<'password'>, true)
: handleEmailPhoneAuth(params, true);
? await handleUsernameAuth(params as StartAuthParams<'password'>, true)
: await handleEmailPhoneAuth(params, true);
} catch (error) {
return { error, signUp, isSuccess: false } as StartSignUpWithIdentifierReturn<TMethod>;
} finally {
Expand All @@ -142,8 +146,8 @@ export function useAuthWithIdentifier<
setIsLoading(true);

return method === 'username'
? handleUsernameAuth(params as StartAuthParams<'password'>, false)
: handleEmailPhoneAuth(params, false);
? await handleUsernameAuth(params as StartAuthParams<'password'>, false)
: await handleEmailPhoneAuth(params, false);
} catch (error) {
return { error, signIn, isSuccess: false } as StartSignInWithIdentifierReturn<TVerifyBy>;
} finally {
Expand All @@ -153,42 +157,59 @@ export function useAuthWithIdentifier<

const startAuthorization: UseAuthWithIdentifierReturn<TVerifyBy, TMethod>['startAuthorization'] = async (params) => {
try {
setIsLoading(true);
const result = await startSignUp(params);
setIsAuthorizing(true);

const result = await startSignUp(params as StartSignUpParams<TVerifyBy>);

if (result?.error && isClerkAPIResponseError(result.error)) {
const error = result.error.errors[0];

if (error?.code === ClerkApiError.FORM_IDENTIFIER_EXIST) {
return await startSignIn(params);
const signInResult = await startSignIn(params);

return { ...signInResult, isSignUp: false };
}
}

return { ...result, isSignUp: true };
} catch (error) {
return { error, signIn, signUp, isSuccess: false } as StartSignInWithIdentifierReturn<TVerifyBy>;
} finally {
setIsLoading(false);
setIsAuthorizing(false);
}
};

const verifyCode = async ({ code, tokenTemplate }: { code: string; tokenTemplate?: string }) => {
return verifyOtpCode({ code, strategy, tokenTemplate });
const verifyCode = async ({
code,
isSignUp,
tokenTemplate,
}: {
code: string;
isSignUp: boolean;
tokenTemplate?: string;
}) => {
setIsLoading(true);

try {
return await verifyOtpCode({ code, strategy, tokenTemplate, isSignUp });
} finally {
setIsLoading(false);
}
};

if (method === 'username') {
return {
startSignIn,
startSignUp,
startAuthorization,
isLoading,
isLoading: isAuthorizing || isLoading,
} as UseAuthWithIdentifierReturn<TVerifyBy, TMethod>;
} else {
return {
startSignIn,
startSignUp,
startAuthorization,
isLoading,
isLoading: isAuthorizing || isLoading,
verifyCode,
isVerifying,
} as UseAuthWithIdentifierReturn<TVerifyBy, TMethod>;
Expand Down
35 changes: 16 additions & 19 deletions src/lib/src/features/clerk/hooks/use-otp-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,27 @@ export function useOtpVerification(): UseOtpVerificationReturn {
await signUp.prepareVerification({ strategy });
};

const sendOtpCode: UseOtpVerificationReturn['sendOtpCode'] = async (strategy) => {
const isSignIn = !!signIn?.id;

if (isSignIn) {
await sendSignInOtpCode(strategy);
} else {
const sendOtpCode: UseOtpVerificationReturn['sendOtpCode'] = async ({ strategy, isSignUp }) => {
if (isSignUp) {
await sendSignUpOtpCode(strategy);
} else {
await sendSignInOtpCode(strategy);
}
};

const verifyCode: UseOtpVerificationReturn['verifyCode'] = async ({ code, strategy, tokenTemplate }) => {
const verifyCode: UseOtpVerificationReturn['verifyCode'] = async ({ code, strategy, tokenTemplate, isSignUp }) => {
try {
setIsVerifying(true);
const isSignIn = !!signIn?.id;

if (isSignIn) {
const completeSignIn = await signIn.attemptFirstFactor({
if (isSignUp) {
const completeSignUp = await signUp?.attemptVerification({
strategy,
code,
});

if (completeSignIn?.status === 'complete') {
await setActive?.({ session: completeSignIn.createdSessionId });
const sessionToken = (await getSessionToken({ tokenTemplate })).sessionToken;
if (completeSignUp?.status === 'complete') {
await setActive?.({ session: completeSignUp.createdSessionId });
const { sessionToken, error } = await getSessionToken({ tokenTemplate });

if (sessionToken) {
return {
Expand All @@ -77,21 +74,21 @@ export function useOtpVerification(): UseOtpVerificationReturn {
}

return {
sessionToken: null,
signIn,
signUp,
error,
isSuccess: false,
};
}
} else {
const completeSignUp = await signUp?.attemptVerification({
const completeSignIn = await signIn?.attemptFirstFactor({
strategy,
code,
});

if (completeSignUp?.status === 'complete') {
await setActive?.({ session: completeSignUp.createdSessionId });
const { sessionToken, error } = await getSessionToken({ tokenTemplate });
if (completeSignIn?.status === 'complete') {
await setActive?.({ session: completeSignIn.createdSessionId });
const sessionToken = (await getSessionToken({ tokenTemplate })).sessionToken;

if (sessionToken) {
return {
Expand All @@ -103,9 +100,9 @@ export function useOtpVerification(): UseOtpVerificationReturn {
}

return {
sessionToken: null,
signIn,
signUp,
error,
isSuccess: false,
};
}
Expand Down
Loading
Loading