Skip to content

Commit

Permalink
feat(evm): add turnstile
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao committed Jan 30, 2025
1 parent fa43a92 commit e2809c8
Show file tree
Hide file tree
Showing 16 changed files with 204 additions and 59 deletions.
3 changes: 3 additions & 0 deletions apps/evm/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ SENTRY_AUTH_TOKEN=
# REDIS CACHE
KV_REST_API_URL="https://communal-hen-23354.upstash.io"
KV_REST_API_TOKEN=


NEXT_PUBLIC_TURNSTILE_SITE_KEY=
1 change: 1 addition & 0 deletions apps/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@gobob/utils": "workspace:^",
"@lingui/core": "catalog:",
"@lingui/react": "catalog:",
"@marsidev/react-turnstile": "^1.1.0",
"@next/third-parties": "catalog:",
"@react-aria/button": "catalog:",
"@react-aria/focus": "catalog:",
Expand Down
3 changes: 2 additions & 1 deletion apps/evm/src/app/[lang]/nested-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useLocalStorage } from 'usehooks-ts';
import { isAddressEqual } from 'viem';
import { useAccount, useAccountEffect, useChainId, useConfig, useSwitchChain } from 'wagmi';

import { Footer, Header, Layout, ReceiveModal } from '@/components';
import { Footer, Header, Layout, ReceiveModal, TurnstileModal } from '@/components';
import { ConnectProvider } from '@/connect-ui';
import { isClient, L2_CHAIN, LocalStorageKey } from '@/constants';
import { useBalances, useGetUser, useLogout, useTokens } from '@/hooks';
Expand Down Expand Up @@ -148,6 +148,7 @@ export function NestedProviders({ children }: PropsWithChildren) {
{children}
<Footer />
</Layout>
<TurnstileModal />
</ConnectProvider>
</BOBUIProvider>
</StyledComponentsRegistry>
Expand Down
41 changes: 37 additions & 4 deletions apps/evm/src/app/[lang]/sign-up/SignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import { Button, Divider, Flex, P, toast } from '@gobob/ui';
import { Trans, t } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { useMutation } from '@tanstack/react-query';
import { useStore } from '@tanstack/react-store';
import { useParams, useRouter } from 'next/navigation';
import { FormEventHandler, Suspense, useEffect, useState } from 'react';
import { useAccount, useSwitchChain } from 'wagmi';

import { Auditors, HighlightText, ReferralInput } from './components';
import { StyledAuthCard, StyledH1 } from './SignUp.style';

import { chakraPetch } from '@/app/fonts';
import { Geoblock, LoginSection, Main } from '@/components';
import { useConnectModal } from '@/connect-ui';
import { L1_CHAIN, L2_CHAIN, RoutesPath, isValidChain } from '@/constants';
import { useGetUser, useSignUp } from '@/hooks';
import { fusionKeys } from '@/lib/react-query';
import { store } from '@/lib/store';
import { apiClient } from '@/utils';
import { chakraPetch } from '@/app/fonts';

const SignUp = (): JSX.Element | null => {
const { address, chain } = useAccount();
Expand All @@ -30,9 +32,20 @@ const SignUp = (): JSX.Element | null => {
const params = useParams();
const { data: user } = useGetUser();

const { token: turnstileToken } = useStore(store, (state) => state.shared.turnstile);

const [referralCode, setReferralCode] = useState('');

const { mutate: signUp, isPending: isLoadingSignUp } = useSignUp();
const { mutate: signUp, isPending: isLoadingSignUp } = useSignUp({
onSuccess: () =>
store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: false }
}
}))
});

const {
mutateAsync: validateReferralCodeAsync,
Expand Down Expand Up @@ -74,7 +87,17 @@ const SignUp = (): JSX.Element | null => {
}
}

return signUp({ address });
if (!turnstileToken) {
return store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: true, onSuccess: (token) => signUp({ address, turnstileToken: token }) }
}
}));
}

return signUp({ address, turnstileToken });
}
});
}
Expand All @@ -83,7 +106,17 @@ const SignUp = (): JSX.Element | null => {
await switchChainAsync({ chainId: L1_CHAIN });
}

return signUp({ address, referralCode });
if (!turnstileToken) {
return store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: true, onSuccess: (token) => signUp({ address, turnstileToken: token }) }
}
}));
}

return signUp({ address, turnstileToken });
};

return (
Expand Down
39 changes: 36 additions & 3 deletions apps/evm/src/components/SignUpButton/SignUpButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { Button, ButtonProps, toast } from '@gobob/ui';
import { Trans } from '@lingui/macro';
import { mergeProps } from '@react-aria/utils';
import { useAccount, useSwitchChain } from 'wagmi';
import { useStore } from '@tanstack/react-store';

import { useConnectModal } from '@/connect-ui';
import { L2_CHAIN, isValidChain } from '@/constants';
import { useSignUp } from '@/hooks';
import { store } from '@/lib/store';

type SignUpButtonProps = ButtonProps;

Expand All @@ -15,7 +17,18 @@ const SignUpButton = (props: SignUpButtonProps): JSX.Element => {
const { open } = useConnectModal();
const { address, chain } = useAccount();

const { mutate: signUp, isPending: isSigningUp } = useSignUp();
const { token: turnstileToken } = useStore(store, (state) => state.shared.turnstile);

const { mutate: signUp, isPending: isSigningUp } = useSignUp({
onSuccess: () =>
store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: false }
}
}))
});

const handlePress = async () => {
if (!address) {
Expand All @@ -30,7 +43,17 @@ const SignUpButton = (props: SignUpButtonProps): JSX.Element => {
}
}

return signUp({ address });
if (!turnstileToken) {
return store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: true, onSuccess: (token) => signUp({ address, turnstileToken: token }) }
}
}));
}

return signUp({ address, turnstileToken });
}
});
}
Expand All @@ -39,7 +62,17 @@ const SignUpButton = (props: SignUpButtonProps): JSX.Element => {
await switchChainAsync({ chainId: L2_CHAIN });
}

return signUp({ address });
if (!turnstileToken) {
return store.setState((s) => ({
...s,
shared: {
...s.shared,
turnstile: { isOpen: true, onSuccess: (token) => signUp({ address, turnstileToken: token }) }
}
}));
}

return signUp({ address, turnstileToken });
};

return <Button loading={isSigningUp} {...mergeProps(props, { onPress: handlePress })} />;
Expand Down
32 changes: 32 additions & 0 deletions apps/evm/src/components/TurnstileModal/TurnstileModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Modal, ModalBody, ModalHeader } from '@gobob/ui';
import { useStore } from '@tanstack/react-store';
import { Turnstile } from '@marsidev/react-turnstile';
import { Trans } from '@lingui/macro';

import { store } from '@/lib/store';

const TurnstileModal = () => {
const { isOpen, onSuccess } = useStore(store, (state) => state.shared.turnstile);

const handleSuccess = (token: string) => {
onSuccess?.(token);

store.setState((s) => ({ ...s, shared: { ...s.shared, turnstile: { isOpen: false, token } } }));
};

return (
<Modal
isOpen={isOpen}
onClose={() => store.setState((s) => ({ ...s, shared: { ...s.shared, turnstile: { isOpen: false } } }))}
>
<ModalHeader>
<Trans>Verify You&apos;re Human</Trans>
</ModalHeader>
<ModalBody padding='even'>
<Turnstile siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} onSuccess={handleSuccess} />
</ModalBody>
</Modal>
);
};

export { TurnstileModal };
1 change: 1 addition & 0 deletions apps/evm/src/components/TurnstileModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TurnstileModal } from './TurnstileModal';
1 change: 1 addition & 0 deletions apps/evm/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './SignUpButton';
export * from './SpiceAmount';
export * from './Trapezoid';
export * from './WithdrawAlert';
export * from './TurnstileModal';
9 changes: 5 additions & 4 deletions apps/evm/src/hooks/tests/useSignUp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('useSignUp', () => {
it('calls refetchUser on successful sign-up', async () => {
vi.useFakeTimers();
const mockAddress = '0x123';
const mockTurnstileToken = '0x12345';
const mockChainId = 1;
const mockNonce = 'mock-nonce';
const mockRefetchUser = vi.fn();
Expand All @@ -69,13 +70,13 @@ describe('useSignUp', () => {
wrapper
});

await act(() => result.current.mutate({ address: mockAddress }));
await act(() => result.current.mutate({ address: mockAddress, turnstileToken: mockTurnstileToken }));

vi.runAllTimers();

expect(apiClient.getNonce).toHaveBeenCalled();
expect(mockSignMessageAsync).toHaveBeenCalledWith({ message: 'Message for 0x123' });
expect(apiClient.signUp).toHaveBeenCalledWith(expect.any(Object), 'mock-signature');
expect(apiClient.signUp).toHaveBeenCalledWith(expect.any(Object), mockTurnstileToken, 'mock-signature');
expect(mockRefetchUser).toHaveBeenCalled();
});

Expand All @@ -91,7 +92,7 @@ describe('useSignUp', () => {
wrapper
});

await act(() => result.current.mutate({ address: mockAddress }));
await act(() => result.current.mutate({ address: mockAddress, turnstileToken: '' }));

expect(toast.error).toHaveBeenCalledWith('User rejected the request');
});
Expand All @@ -108,7 +109,7 @@ describe('useSignUp', () => {
wrapper
});

await act(() => result.current.mutate({ address: mockAddress }));
await act(() => result.current.mutate({ address: mockAddress, turnstileToken: '' }));

expect(toast.error).toHaveBeenCalledWith('Network error');
});
Expand Down
18 changes: 15 additions & 3 deletions apps/evm/src/hooks/useSignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { useGetUser } from './useGetUser';
import { fusionKeys } from '@/lib/react-query';
import { apiClient } from '@/utils';

const useSignUp = () => {
type UseSignUpProps = {
onSuccess?: () => void;
};

const useSignUp = ({ onSuccess }: UseSignUpProps = {}) => {
const { signMessageAsync } = useSignMessage();
const { chain } = useAccount();

Expand All @@ -22,7 +26,14 @@ const useSignUp = () => {

return useMutation({
mutationKey: fusionKeys.signUp(),
mutationFn: async ({ address }: { address: Address; referralCode?: string }) => {
mutationFn: async ({
address,
turnstileToken
}: {
address: Address;
turnstileToken: string;
referralCode?: string;
}) => {
const nonce = await apiClient.getNonce();

const message = new SiweMessage({
Expand All @@ -39,10 +50,11 @@ const useSignUp = () => {
message: message.prepareMessage()
});

await apiClient.signUp(message, signature);
await apiClient.signUp(message, turnstileToken, signature);
},
onSuccess: (_, { address, referralCode }) => {
sendGAEvent('event', 'signup', { evm_address: JSON.stringify(address), referral_code: referralCode });
onSuccess?.();
setTimeout(() => refetchUser(), 100);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
8 changes: 8 additions & 0 deletions apps/evm/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ type SharedStore = {
};
};
};
turnstile: {
isOpen: boolean;
token?: string;
onSuccess?: (token: string) => void;
};
};

type BridgeStore = {
Expand Down Expand Up @@ -71,6 +76,9 @@ const store = new StoreLib<Store>({
type: undefined
}
}
},
turnstile: {
isOpen: false
}
},
bridge: {
Expand Down
6 changes: 3 additions & 3 deletions apps/evm/src/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -1516,13 +1516,13 @@ msgstr "Solv XP"
#: src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx:130
#: src/app/[lang]/fusion/components/LotterySections/LotteryModal/LotteryModal.tsx:60
#: src/hooks/useLogin.ts:50
#: src/hooks/useSignUp.ts:53
#: src/hooks/useSignUp.ts:65
msgid "Something went wrong. Please try again later."
msgstr "Something went wrong. Please try again later."

#: src/app/[lang]/sign-up/SignUp.tsx:73
#: src/components/LoginButton/LoginButton.tsx:29
#: src/components/SignUpButton/SignUpButton.tsx:29
#: src/components/SignUpButton/SignUpButton.tsx:42
msgid "Something went wrong. Please try connecting your wallet again."
msgstr "Something went wrong. Please try connecting your wallet again."

Expand Down Expand Up @@ -1983,7 +1983,7 @@ msgstr "Use your Spice total to support your favourite BOB builders. Winners wil
#: src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx:128
#: src/connect-ui/component/ConnectModal/ConnectModal.tsx:96
#: src/hooks/useLogin.ts:48
#: src/hooks/useSignUp.ts:51
#: src/hooks/useSignUp.ts:63
msgid "User rejected the request"
msgstr "User rejected the request"

Expand Down
10 changes: 7 additions & 3 deletions apps/evm/src/locales/zh.po
Original file line number Diff line number Diff line change
Expand Up @@ -1760,13 +1760,13 @@ msgstr ""
#: src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx:130
#: src/app/[lang]/fusion/components/LotterySections/LotteryModal/LotteryModal.tsx:60
#: src/hooks/useLogin.ts:50
#: src/hooks/useSignUp.ts:53
#: src/hooks/useSignUp.ts:65
msgid "Something went wrong. Please try again later."
msgstr ""

#: src/app/[lang]/sign-up/SignUp.tsx:73
#: src/components/LoginButton/LoginButton.tsx:29
#: src/components/SignUpButton/SignUpButton.tsx:29
#: src/components/SignUpButton/SignUpButton.tsx:42
msgid "Something went wrong. Please try connecting your wallet again."
msgstr ""

Expand Down Expand Up @@ -2364,7 +2364,7 @@ msgstr "用你的Spice总数来支持你最喜爱的BOB构建者。获胜者将
#: src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx:128
#: src/connect-ui/component/ConnectModal/ConnectModal.tsx:96
#: src/hooks/useLogin.ts:48
#: src/hooks/useSignUp.ts:51
#: src/hooks/useSignUp.ts:63
msgid "User rejected the request"
msgstr ""

Expand Down Expand Up @@ -2392,6 +2392,10 @@ msgstr ""
#~ msgid "Using the official bridge usually takes 7 days. For faster withdrawals we recommend using a 3rd Party bridge."
#~ msgstr ""

#: src/components/TurnstileModal/TurnstileModal.tsx:23
msgid "Verify You're Human"
msgstr ""

#: src/app/[lang]/wallet/Wallet.tsx:29
#~ msgid "View a summary of your wallet on the BOB network. DeFi positions may not be tracked."
#~ msgstr "查看您在 BOB 网络上的钱包概况。"
Expand Down
Loading

0 comments on commit e2809c8

Please sign in to comment.