Skip to content

Commit 384d1d9

Browse files
authored
fix: handle email already registered (#1910)
1 parent 0da3190 commit 384d1d9

File tree

5 files changed

+97
-69
lines changed

5 files changed

+97
-69
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@cosmjs/tendermint-rpc": "^0.32.1",
5656
"@datadog/browser-logs": "^5.23.3",
5757
"@dydxprotocol/v4-client-js": "3.0.3",
58-
"@dydxprotocol/v4-localization": "1.1.327",
58+
"@dydxprotocol/v4-localization": "1.1.328",
5959
"@dydxprotocol/v4-proto": "^7.0.0-dev.0",
6060
"@emotion/is-prop-valid": "^1.3.0",
6161
"@hugocxl/react-to-image": "^0.0.9",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/turnkey/turnkeyUtils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TurnkeyIframeClient, TurnkeyIndexedDbClient } from '@turnkey/sdk-browser';
22

3+
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
34
import type { TurnkeyWallet } from '@/types/turnkey';
45

56
/**
@@ -31,3 +32,23 @@ export async function getWalletsWithAccountsFromClient(
3132

3233
return walletWithAccounts;
3334
}
35+
36+
export const parseTurnkeyError = (message: string, stringGetter: StringGetterFunction): string => {
37+
if (message.includes('User has already registered using this email')) {
38+
return stringGetter({ key: STRING_KEYS.USER_ALREADY_HAS_TURNKEY });
39+
}
40+
41+
if (
42+
message.includes('unable to decrypt bundle using embedded key') ||
43+
message.includes('Organization ID is not available') ||
44+
message.includes('Unauthenticated desc') ||
45+
message.includes('Organization ID was not found')
46+
) {
47+
return stringGetter({ key: STRING_KEYS.INVALID_TURNKEY_EMAIL_LINK });
48+
}
49+
50+
return stringGetter({
51+
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
52+
params: { ERROR_MESSAGE: message },
53+
});
54+
};

src/providers/TurnkeyAuthProvider.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useSearchParams } from 'react-router-dom';
1010

1111
import { AnalyticsEvents } from '@/constants/analytics';
1212
import { DialogTypes } from '@/constants/dialogs';
13+
import { STRING_KEYS } from '@/constants/localization';
1314
import { ConnectorType, WalletType } from '@/constants/wallets';
1415
import {
1516
GoogleIdTokenPayload,
@@ -20,7 +21,9 @@ import {
2021
} from '@/types/turnkey';
2122

2223
import { useAccounts } from '@/hooks/useAccounts';
24+
import { useStringGetter } from '@/hooks/useStringGetter';
2325

26+
import { appQueryClient } from '@/state/appQueryClient';
2427
import { useAppDispatch, useAppSelector } from '@/state/appTypes';
2528
import { forceOpenDialog, openDialog } from '@/state/dialogs';
2629
import {
@@ -32,6 +35,7 @@ import {
3235
import { getSourceAccount, getTurnkeyEmailOnboardingData } from '@/state/walletSelectors';
3336

3437
import { track } from '@/lib/analytics/analytics';
38+
import { parseTurnkeyError } from '@/lib/turnkey/turnkeyUtils';
3539

3640
import { useTurnkeyWallet } from './TurnkeyWalletProvider';
3741

@@ -67,6 +71,7 @@ export const useTurnkeyAuth = () => useContext(TurnkeyAuthContext)!;
6771

6872
const useTurnkeyAuthContext = () => {
6973
const dispatch = useAppDispatch();
74+
const stringGetter = useStringGetter();
7075
const indexerUrl = useAppSelector(selectIndexerUrl);
7176
const sourceAccount = useAppSelector(getSourceAccount);
7277
const { indexedDbClient, authIframeClient } = useTurnkey();
@@ -125,14 +130,18 @@ const useTurnkeyAuthContext = () => {
125130
if (response.errors && Array.isArray(response.errors)) {
126131
// Handle API-reported errors
127132
const errorMsg = response.errors.map((e: { msg: string }) => e.msg).join(', ');
128-
throw new Error(`useTurnkeyAuth: Backend Error: ${errorMsg}`);
133+
throw new Error(`Backend Error: ${errorMsg}`);
129134
}
130135

131136
if (response.dydxAddress === '') {
132137
setIsNewTurnkeyUser(true);
133138
dispatch(setRequiresAddressUpload(true));
134139
}
135140

141+
if (loginMethod === LoginMethod.OAuth && response.alreadyExists) {
142+
throw new Error('User has already registered using this email');
143+
}
144+
136145
switch (loginMethod) {
137146
case LoginMethod.OAuth:
138147
handleOauthResponse({ response });
@@ -154,7 +163,7 @@ const useTurnkeyAuthContext = () => {
154163
selectWallet(undefined);
155164
logBonsaiError('TurnkeyOnboarding', 'Error during sign-in', { error });
156165
setEmailSignInStatus('error');
157-
setEmailSignInError(error.message);
166+
setEmailSignInError(parseTurnkeyError(error.message, stringGetter));
158167

159168
if (variables.loginMethod === LoginMethod.OAuth) {
160169
const providerName = variables.providerName;
@@ -181,12 +190,6 @@ const useTurnkeyAuthContext = () => {
181190
signinMethod: providerName,
182191
})
183192
);
184-
} else if (variables.loginMethod === LoginMethod.Email) {
185-
track(
186-
AnalyticsEvents.TurnkeyLoginCompleted({
187-
signinMethod: 'email',
188-
})
189-
);
190193
}
191194
},
192195
});
@@ -330,25 +333,21 @@ const useTurnkeyAuthContext = () => {
330333
let errorMessage: string | undefined;
331334

332335
if (error instanceof Error) {
333-
if (
334-
error.message.includes('unable to decrypt bundle using embedded key') ||
335-
error.message.includes('Organization ID is not available') ||
336-
error.message.includes('Unauthenticated desc') ||
337-
error.message.includes('Organization ID was not found')
338-
) {
339-
errorMessage =
340-
'Your email link has expired or you are using a different device/browser than the one used to sign in. Please try again.';
341-
} else {
342-
logBonsaiError('TurnkeyOnboarding', 'error handling email magic link', { error });
343-
errorMessage = error.message;
344-
}
336+
errorMessage = parseTurnkeyError(error.message, stringGetter);
337+
logBonsaiError('TurnkeyOnboarding', 'error handling email magic link', { error });
345338
} else {
346339
logBonsaiError('TurnkeyOnboarding', 'error handling email magic link - unknown', {
347340
error,
348341
});
349342
}
350343

351-
setEmailSignInError(errorMessage ?? 'An unknown error occurred');
344+
setEmailSignInError(
345+
errorMessage ??
346+
stringGetter({
347+
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
348+
params: { ERROR_MESSAGE: stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }) },
349+
})
350+
);
352351
setEmailSignInStatus('error');
353352
} finally {
354353
// Clear token from state after it has been consumed
@@ -370,6 +369,7 @@ const useTurnkeyAuthContext = () => {
370369
searchParams,
371370
setSearchParams,
372371
dispatch,
372+
stringGetter,
373373
]
374374
);
375375

@@ -414,7 +414,7 @@ const useTurnkeyAuthContext = () => {
414414
}, [searchParams, setSearchParams]);
415415

416416
/* ----------------------------- Upload Address ----------------------------- */
417-
const { mutateAsync: sendUploadAddressRequest } = useMutation({
417+
const { mutateAsync: sendUploadAddressRequest, isPending: isUploadingAddress } = useMutation({
418418
mutationFn: async ({
419419
payload,
420420
}: {
@@ -449,6 +449,9 @@ const useTurnkeyAuthContext = () => {
449449
);
450450
logBonsaiError('TurnkeyOnboarding', 'Error posting to upload address', { error });
451451
},
452+
onSuccess: () => {
453+
appQueryClient.invalidateQueries({ queryKey: ['turnkeyWallets'] });
454+
},
452455
});
453456

454457
const uploadAddress = useCallback(
@@ -548,6 +551,7 @@ const useTurnkeyAuthContext = () => {
548551
isLoading: status === 'pending' || emailSignInStatus === 'loading',
549552
isError: status === 'error' || emailSignInStatus === 'error',
550553
needsAddressUpload,
554+
isUploadingAddress,
551555
signInWithOauth,
552556
signInWithOtp,
553557
resetEmailSignInStatus,

src/views/dialogs/TransferDialogs/DepositAddressDialog.tsx

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useCopyValue } from '@/hooks/useCopyValue';
1818
import { useLocaleSeparators } from '@/hooks/useLocaleSeparators';
1919
import { useSimpleUiEnabled } from '@/hooks/useSimpleUiEnabled';
2020
import { useStringGetter } from '@/hooks/useStringGetter';
21+
import { useTurnkeyAuth } from '@/providers/TurnkeyAuthProvider';
2122

2223
import breakpoints from '@/styles/breakpoints';
2324
import { formMixins } from '@/styles/formMixins';
@@ -58,6 +59,7 @@ export const DepositAddressDialog = ({ setIsOpen }: DialogProps<DepositDialog2Pr
5859
const indexerReady = useAppSelector(selectIndexerReady);
5960

6061
const canQueryForDepositAddresses = dydxAddress != null && indexerReady;
62+
const { isUploadingAddress } = useTurnkeyAuth();
6163

6264
const {
6365
data: depositAddresses,
@@ -205,50 +207,51 @@ export const DepositAddressDialog = ({ setIsOpen }: DialogProps<DepositDialog2Pr
205207

206208
const { copied, copy } = useCopyValue({ value: depositAddress });
207209

208-
const addressCard = isLoadingDepositAddresses ? (
209-
<$AddressCard>
210-
<div tw="mx-auto flex size-[155px] items-center justify-center">
211-
<LoadingSpace />
212-
</div>
213-
</$AddressCard>
214-
) : failedToFetchDepositAddresses ? (
215-
<$AddressCard>
216-
<div tw="mx-auto flex h-[155px] flex-col items-center justify-center gap-1">
217-
<Icon iconName={IconName.Warning} tw="size-2 text-color-error" />
218-
<span tw="text-color-text-0">
219-
{stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG })}
220-
</span>
221-
</div>
222-
</$AddressCard>
223-
) : (
224-
<$AddressCard onClick={copy} tabIndex={0} role="button">
225-
<div tw="flexColumn min-w-0 justify-between">
226-
<img
227-
tw="size-2.25 rounded-[50%]"
228-
src={CHAIN_INFO[selectedChain]?.icon}
229-
alt={CHAIN_INFO[selectedChain]?.name}
230-
/>
231-
{addressRepresentation && depositAddress ? (
232-
<div tw="row ml-[-0.5rem] cursor-pointer items-end gap-0.125 rounded-[6px] p-0.5 hover:bg-color-layer-1">
233-
<div tw="min-w-0 whitespace-normal break-words text-justify">
234-
<span tw="text-color-text-2">{addressRepresentation.firstPart}</span>
235-
<span tw="text-color-text-0">{addressRepresentation.middlePart}</span>
236-
<span tw="text-color-text-2">{addressRepresentation.lastPart}</span>
210+
const addressCard =
211+
isUploadingAddress || isLoadingDepositAddresses ? (
212+
<$AddressCard>
213+
<div tw="mx-auto flex size-[155px] items-center justify-center">
214+
<LoadingSpace />
215+
</div>
216+
</$AddressCard>
217+
) : failedToFetchDepositAddresses ? (
218+
<$AddressCard>
219+
<div tw="mx-auto flex h-[155px] flex-col items-center justify-center gap-1">
220+
<Icon iconName={IconName.Warning} tw="size-2 text-color-error" />
221+
<span tw="text-color-text-0">
222+
{stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG })}
223+
</span>
224+
</div>
225+
</$AddressCard>
226+
) : (
227+
<$AddressCard onClick={copy} tabIndex={0} role="button">
228+
<div tw="flexColumn min-w-0 justify-between">
229+
<img
230+
tw="size-2.25 rounded-[50%]"
231+
src={CHAIN_INFO[selectedChain]?.icon}
232+
alt={CHAIN_INFO[selectedChain]?.name}
233+
/>
234+
{addressRepresentation && depositAddress ? (
235+
<div tw="row ml-[-0.5rem] cursor-pointer items-end gap-0.125 rounded-[6px] p-0.5 hover:bg-color-layer-1">
236+
<div tw="min-w-0 whitespace-normal break-words text-justify">
237+
<span tw="text-color-text-2">{addressRepresentation.firstPart}</span>
238+
<span tw="text-color-text-0">{addressRepresentation.middlePart}</span>
239+
<span tw="text-color-text-2">{addressRepresentation.lastPart}</span>
240+
</div>
241+
{copied ? (
242+
<Icon iconName={IconName.CheckCircle} tw="text-color-success" />
243+
) : (
244+
<Icon iconName={IconName.Copy} tw="text-color-accent" />
245+
)}
237246
</div>
238-
{copied ? (
239-
<Icon iconName={IconName.CheckCircle} tw="text-color-success" />
240-
) : (
241-
<Icon iconName={IconName.Copy} tw="text-color-accent" />
242-
)}
243-
</div>
244-
) : null}
245-
</div>
247+
) : null}
248+
</div>
246249

247-
<div tw="flex size-[155px] min-w-[155px]">
248-
{depositAddress && <QrCode hasLogo tw="size-full" value={depositAddress} />}
249-
</div>
250-
</$AddressCard>
251-
);
250+
<div tw="flex size-[155px] min-w-[155px]">
251+
{depositAddress && <QrCode hasLogo tw="size-full" value={depositAddress} />}
252+
</div>
253+
</$AddressCard>
254+
);
252255

253256
const getAcceptedAssets = (id: string | number) => {
254257
if (id === SOLANA_MAINNET_ID || id === avalanche.id) {

0 commit comments

Comments
 (0)