From 18ff538cb30428ce54e9d4a240c4d44f347dc325 Mon Sep 17 00:00:00 2001 From: wilfred Date: Wed, 31 Jan 2024 15:52:33 +0700 Subject: [PATCH 01/10] WIP --- src/modules/PublicSale/AuthForBuyV2/index.tsx | 60 ++++++++++++++ .../AuthForBuyV2/styles.module.scss | 27 ++++++ src/modules/PublicSale/BuyForm/index.tsx | 83 +++++++++++-------- src/stores/index.ts | 2 +- src/stores/states/user/reducer.ts | 5 ++ src/stores/states/user/selector.ts | 28 ++++--- src/stores/states/user/types.ts | 11 +-- 7 files changed, 164 insertions(+), 52 deletions(-) create mode 100644 src/modules/PublicSale/AuthForBuyV2/index.tsx create mode 100644 src/modules/PublicSale/AuthForBuyV2/styles.module.scss diff --git a/src/modules/PublicSale/AuthForBuyV2/index.tsx b/src/modules/PublicSale/AuthForBuyV2/index.tsx new file mode 100644 index 000000000..98793c7cc --- /dev/null +++ b/src/modules/PublicSale/AuthForBuyV2/index.tsx @@ -0,0 +1,60 @@ +import { userTokenSelector } from '@/stores/states/user/selector'; +import { Button, Flex, Tooltip, useDisclosure } from '@chakra-ui/react'; +import cx from 'classnames'; +import React, { PropsWithChildren, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import s from './styles.module.scss'; + +interface IAuthForBuyV2 extends PropsWithChildren {} + +const AuthForBuyV2: React.FC = ({ children }) => { + const userToken = useSelector(userTokenSelector); + + const isLogged = useMemo(() => Boolean(userToken), [userToken]); + + const { isOpen, onOpen, onClose } = useDisclosure(); + + if (isLogged) { + return children; + } + + return ( + <> + + + + + + + + ); +}; + +export default AuthForBuyV2; diff --git a/src/modules/PublicSale/AuthForBuyV2/styles.module.scss b/src/modules/PublicSale/AuthForBuyV2/styles.module.scss new file mode 100644 index 000000000..4770f41ad --- /dev/null +++ b/src/modules/PublicSale/AuthForBuyV2/styles.module.scss @@ -0,0 +1,27 @@ +.btnWrapper { + grid-gap: 16px; + grid-template-columns: 2fr 1fr; + + @include is-mobile { + grid-template-columns: 1fr; + } +} + +.btnContainer { + height: 48px !important; + padding: 8px 60px !important; + border-radius: 0px !important; + background: #fa4e0e !important; + color: #fff !important; + width: 100%; + font-weight: normal !important; + gap: 8px; + &.btnBuyAsGuest { + background: #ffffff !important; + color: #000000 !important; + } + &.btnBuyAndStake { + background: #000000 !important; + color: #ffffff !important; + } +} diff --git a/src/modules/PublicSale/BuyForm/index.tsx b/src/modules/PublicSale/BuyForm/index.tsx index 81ef64661..56958dae5 100644 --- a/src/modules/PublicSale/BuyForm/index.tsx +++ b/src/modules/PublicSale/BuyForm/index.tsx @@ -1,13 +1,24 @@ import { Box, Button, Flex, Text, Tooltip } from '@chakra-ui/react'; import { FormikProps, useFormik } from 'formik'; -import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + forwardRef, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import s from './styles.module.scss'; import { getPublicSaleLeaderBoards, getPublicSaleSummary, postPublicsaleWalletInfoManualCheck, } from '@/services/public-sale'; -import { defaultSummary, IPublicSaleDepositInfo, VCInfo } from '@/interfaces/vc'; +import { + defaultSummary, + IPublicSaleDepositInfo, + VCInfo, +} from '@/interfaces/vc'; import { formatCurrency } from '@/utils/format'; import { toast } from 'react-hot-toast'; import dayjs from 'dayjs'; @@ -26,6 +37,7 @@ import { GuestCodeHere } from '../depositModal/deposit.guest.code'; import LoginTooltip from '@/modules/PublicSale/depositModal/login.tooltip'; import { useAppSelector } from '@/stores/hooks'; import { commonSelector } from '@/stores/states/common/selector'; +import AuthForBuyV2 from '../AuthForBuyV2'; interface FormValues { tokenAmount: string; @@ -175,14 +187,13 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { lineHeight={1} fontWeight={400} color={'rgba(0,0,0,0.7)'} - gap={1} alignItems={"center"} + gap={1} + alignItems={'center'} > Your contribution - { - - }}/> + {}} /> - + {token ? `$${formatCurrency( @@ -247,8 +258,7 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { : '-'} - ) - } + )} @@ -256,25 +266,21 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { }); const renderLoginTooltip = useCallback(() => { - return ( - token ? ( - } - > - - - ) : ( - - ) - ) + return token ? ( + } + > + + + ) : ( + + ); }, [token, userContributeInfo]); return ( @@ -381,16 +387,23 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { {/* */} {/*)}*/} -
- { - renderLoginTooltip() - } -
+
{renderLoginTooltip()}
- + + + + {/* - + */} {parseFloat(userContributeInfo?.usdt_value || '0') > 0 && ( diff --git a/src/stores/index.ts b/src/stores/index.ts index afee6af6e..53da83220 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -10,7 +10,7 @@ const reducers = combineReducers(reducer); const persistConfig = getPersistConfig({ key: 'root', storage: persistLocalStorage, - whitelist: ['common.poolTabIndex', 'common.coinPrices', 'airdrop'], + whitelist: ['common.poolTabIndex', 'common.coinPrices', 'airdrop', 'user'], rootReducer: reducers, }); diff --git a/src/stores/states/user/reducer.ts b/src/stores/states/user/reducer.ts index 797534210..31905545a 100644 --- a/src/stores/states/user/reducer.ts +++ b/src/stores/states/user/reducer.ts @@ -20,6 +20,7 @@ const initialState: UserState = { airdropGMHolders: null, airdropGenerativeUsers: null, airdropPerceptronsHolders: null, + userToken: null, } as any; const slice = createSlice({ @@ -93,6 +94,9 @@ const slice = createSlice({ guest_code: action.payload, } as any; }, + setUserToken: (state, action) => { + state.userToken = action.payload; + }, }, }); @@ -110,6 +114,7 @@ export const { clearPublicSaleLeaderBoard, setPublicSaleLeaderBoardVisual, setGuestSecretCode, + setUserToken } = slice.actions; export default slice.reducer; diff --git a/src/stores/states/user/selector.ts b/src/stores/states/user/selector.ts index ba1141b1a..13df04199 100644 --- a/src/stores/states/user/selector.ts +++ b/src/stores/states/user/selector.ts @@ -3,34 +3,40 @@ import { EVMFieldType, User } from '@/stores/states/user/types'; import { ILeaderBoardPoint } from '@/interfaces/leader-board-point'; import { SignatureStatus } from '@/interfaces/whitelist'; -export const userSelector = (state: RootState) => state.user?.user as User | undefined; +export const userSelector = (state: RootState) => + state.user?.user as User | undefined; +export const userTokenSelector = (state: RootState) => state.user?.userToken; export const leaderBoardSelector = (state: RootState) => ({ list: (state.user?.leaderBoard || []) as ILeaderBoardPoint[], - count: (state.user?.leaderBoardCount || '') as string + count: (state.user?.leaderBoardCount || '') as string, }); export const allowBTCSelector = (state: RootState) => ({ status: (state.user?.allowBTC?.status || []) as SignatureStatus[], - loaded: (state.user?.allowBTC?.loaded || false) as boolean + loaded: (state.user?.allowBTC?.loaded || false) as boolean, }); export const allowCelestiaSelector = (state: RootState) => ({ status: (state.user?.allowCelestia?.status || []) as SignatureStatus[], - loaded: (state.user?.allowCelestia?.loaded || false) as boolean + loaded: (state.user?.allowCelestia?.loaded || false) as boolean, }); export const allowEVMSelector = (state: RootState) => (type: EVMFieldType) => { const data = (state.user as any)?.[type]; - return ({ + return { status: (data?.status || []) as SignatureStatus[], - loaded: (data?.allowCelestia?.loaded || false) as boolean - }) + loaded: (data?.allowCelestia?.loaded || false) as boolean, + }; }; -export const airdropAlphaUsersSelector = (state: RootState) => state.user.airdropAlphaUsers; -export const airdropGMHoldersSelector = (state: RootState) => state.user.airdropGMHolders; -export const airdropGenerativeUsersSelector = (state: RootState) => state.user.airdropGenerativeUsers; -export const airdropPerceptronsHoldersSelector = (state: RootState) => state.user.airdropPerceptronsHolders; +export const airdropAlphaUsersSelector = (state: RootState) => + state.user.airdropAlphaUsers; +export const airdropGMHoldersSelector = (state: RootState) => + state.user.airdropGMHolders; +export const airdropGenerativeUsersSelector = (state: RootState) => + state.user.airdropGenerativeUsers; +export const airdropPerceptronsHoldersSelector = (state: RootState) => + state.user.airdropPerceptronsHolders; export const publicSaleLeaderBoardSelector = (state: RootState) => ({ list: (state.user?.publicSaleLeaderBoard || []) as ILeaderBoardPoint[], diff --git a/src/stores/states/user/types.ts b/src/stores/states/user/types.ts index 501cac9b1..e50b8857d 100644 --- a/src/stores/states/user/types.ts +++ b/src/stores/states/user/types.ts @@ -23,27 +23,28 @@ export interface User { num_post: string; boost: string; guest_code: string; - view_boost: string + view_boost: string; } -export type EVMFieldType = "allowOptimism" +export type EVMFieldType = 'allowOptimism'; export interface UserState { - user?: User | undefined, + user?: User | undefined; leaderBoard: ILeaderBoardPoint[]; leaderBoardCount: string; allowBTC: { status: SignatureStatus[]; loaded: boolean; - }, + }; allowCelestia: { status: SignatureStatus[]; loaded: boolean; - }, + }; airdropAlphaUsers: any; airdropGMHolders: any; airdropGenerativeUsers: any; airdropPerceptronsHolders: any; publicSaleLeaderBoard: ILeaderBoardPoint[]; publicSaleLeaderBoardVisual: ILeaderBoardPoint[]; + userToken: string; } From 9c9fbeb0641ac1feead0882b264308e22ba30ca3 Mon Sep 17 00:00:00 2001 From: wilfred Date: Wed, 31 Jan 2024 16:30:30 +0700 Subject: [PATCH 02/10] WIP --- src/modules/PublicSale/AuthForBuy/index.tsx | 61 +++-- src/modules/PublicSale/AuthForBuyV2/index.tsx | 215 ++++++++++++++---- .../AuthForBuyV2/styles.module.scss | 22 +- src/modules/PublicSale/BuyForm/index.tsx | 83 +++---- 4 files changed, 264 insertions(+), 117 deletions(-) diff --git a/src/modules/PublicSale/AuthForBuy/index.tsx b/src/modules/PublicSale/AuthForBuy/index.tsx index 3674e18c8..425c7dfa5 100644 --- a/src/modules/PublicSale/AuthForBuy/index.tsx +++ b/src/modules/PublicSale/AuthForBuy/index.tsx @@ -8,6 +8,7 @@ import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import DepositContent from '../depositModal/deposit.content'; import s from './styles.module.scss'; import cx from 'clsx'; +import AuthForBuyV2 from '../AuthForBuyV2'; interface IAuthForBuy extends PropsWithChildren {} @@ -26,39 +27,49 @@ const AuthForBuy: React.FC = () => { return ( <> - - ( + + )} > - + + + + + + any; +} -const AuthForBuyV2: React.FC = ({ children }) => { +const AuthForBuyV2: React.FC = ({ + children, + renderWithoutLogin, +}) => { const userToken = useSelector(userTokenSelector); const isLogged = useMemo(() => Boolean(userToken), [userToken]); @@ -18,43 +24,176 @@ const AuthForBuyV2: React.FC = ({ children }) => { return children; } - return ( - <> - - - - + - - - - ); + + + + ); + } + + return <>; }; export default AuthForBuyV2; diff --git a/src/modules/PublicSale/AuthForBuyV2/styles.module.scss b/src/modules/PublicSale/AuthForBuyV2/styles.module.scss index 4770f41ad..90968d822 100644 --- a/src/modules/PublicSale/AuthForBuyV2/styles.module.scss +++ b/src/modules/PublicSale/AuthForBuyV2/styles.module.scss @@ -7,18 +7,28 @@ } } +.loginModal { + max-width: 400px !important; + header { + p { + color: white; + } + } +} + .btnContainer { height: 48px !important; - padding: 8px 60px !important; + padding: 6px !important; border-radius: 0px !important; - background: #fa4e0e !important; - color: #fff !important; + background: #ffffff !important; + color: #000000 !important; width: 100%; font-weight: normal !important; gap: 8px; - &.btnBuyAsGuest { - background: #ffffff !important; - color: #000000 !important; + justify-content: flex-start !important; + &.btnPrimary { + background: #fa4e0e !important; + color: #fff !important; } &.btnBuyAndStake { background: #000000 !important; diff --git a/src/modules/PublicSale/BuyForm/index.tsx b/src/modules/PublicSale/BuyForm/index.tsx index 56958dae5..81ef64661 100644 --- a/src/modules/PublicSale/BuyForm/index.tsx +++ b/src/modules/PublicSale/BuyForm/index.tsx @@ -1,24 +1,13 @@ import { Box, Button, Flex, Text, Tooltip } from '@chakra-ui/react'; import { FormikProps, useFormik } from 'formik'; -import React, { - forwardRef, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import s from './styles.module.scss'; import { getPublicSaleLeaderBoards, getPublicSaleSummary, postPublicsaleWalletInfoManualCheck, } from '@/services/public-sale'; -import { - defaultSummary, - IPublicSaleDepositInfo, - VCInfo, -} from '@/interfaces/vc'; +import { defaultSummary, IPublicSaleDepositInfo, VCInfo } from '@/interfaces/vc'; import { formatCurrency } from '@/utils/format'; import { toast } from 'react-hot-toast'; import dayjs from 'dayjs'; @@ -37,7 +26,6 @@ import { GuestCodeHere } from '../depositModal/deposit.guest.code'; import LoginTooltip from '@/modules/PublicSale/depositModal/login.tooltip'; import { useAppSelector } from '@/stores/hooks'; import { commonSelector } from '@/stores/states/common/selector'; -import AuthForBuyV2 from '../AuthForBuyV2'; interface FormValues { tokenAmount: string; @@ -187,13 +175,14 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { lineHeight={1} fontWeight={400} color={'rgba(0,0,0,0.7)'} - gap={1} - alignItems={'center'} + gap={1} alignItems={"center"} > Your contribution - {}} /> + { + + }}/> - + {token ? `$${formatCurrency( @@ -258,7 +247,8 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { : '-'} - )} + ) + } @@ -266,21 +256,25 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { }); const renderLoginTooltip = useCallback(() => { - return token ? ( - } - > - - - ) : ( - - ); + return ( + token ? ( + } + > + + + ) : ( + + ) + ) }, [token, userContributeInfo]); return ( @@ -387,23 +381,16 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { {/* */} {/*)}*/} -
{renderLoginTooltip()}
+
+ { + renderLoginTooltip() + } +
- - - - {/* + - */} + {parseFloat(userContributeInfo?.usdt_value || '0') > 0 && ( From 5efa604d77c0ee0d7f44faf56c4dea8d413748ab Mon Sep 17 00:00:00 2001 From: wilfred Date: Wed, 31 Jan 2024 16:33:29 +0700 Subject: [PATCH 03/10] update --- src/stores/states/user/reducer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/states/user/reducer.ts b/src/stores/states/user/reducer.ts index 31905545a..3d5a601dc 100644 --- a/src/stores/states/user/reducer.ts +++ b/src/stores/states/user/reducer.ts @@ -1,6 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { EVMFieldType, UserState } from './types'; import uniqueBy from '@popperjs/core/lib/utils/uniqueBy'; +import AuthenStorage from '@/utils/storage/authen.storage'; const initialState: UserState = { user: undefined, @@ -96,6 +97,7 @@ const slice = createSlice({ }, setUserToken: (state, action) => { state.userToken = action.payload; + AuthenStorage.setAuthenKey(action.payload); }, }, }); @@ -114,7 +116,7 @@ export const { clearPublicSaleLeaderBoard, setPublicSaleLeaderBoardVisual, setGuestSecretCode, - setUserToken + setUserToken, } = slice.actions; export default slice.reducer; From b03eaa06fa1e0deca309ae33ce6d0bde32d8d1b8 Mon Sep 17 00:00:00 2001 From: camewell <130561684+camewell071@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:29:29 +0700 Subject: [PATCH 04/10] chore: update ui --- src/components/BaseModal/styles.module.scss | 2 +- src/modules/PublicSale/AuthForBuyV2/index.tsx | 469 ++++++++++++------ src/modules/PublicSale/BuyForm/index.tsx | 10 + src/modules/Whitelist/FAQContent/index.tsx | 12 +- src/services/public-sale.ts | 12 + src/stores/states/user/selector.ts | 3 +- src/utils/metamask-helper.ts | 7 +- 7 files changed, 353 insertions(+), 162 deletions(-) diff --git a/src/components/BaseModal/styles.module.scss b/src/components/BaseModal/styles.module.scss index a017af630..00945262f 100644 --- a/src/components/BaseModal/styles.module.scss +++ b/src/components/BaseModal/styles.module.scss @@ -38,7 +38,7 @@ background-color: transparent !important; position: absolute; right: 0; - top: 0; + top: -2px; &:focus-visible { outline: none; } diff --git a/src/modules/PublicSale/AuthForBuyV2/index.tsx b/src/modules/PublicSale/AuthForBuyV2/index.tsx index 0b57ed33d..f6986672a 100644 --- a/src/modules/PublicSale/AuthForBuyV2/index.tsx +++ b/src/modules/PublicSale/AuthForBuyV2/index.tsx @@ -1,10 +1,25 @@ -import { userTokenSelector } from '@/stores/states/user/selector'; +import { userSelector, userTokenSelector } from '@/stores/states/user/selector'; import { Button, Flex, Text, Tooltip, useDisclosure } from '@chakra-ui/react'; import cx from 'classnames'; -import React, { PropsWithChildren, useMemo } from 'react'; -import { useSelector } from 'react-redux'; +import React, { PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import s from './styles.module.scss'; import BaseModal from '@/components/BaseModal'; +import { IAuthenCode } from '@/modules/Whitelist/steps'; +import { getLink, getUuid } from '@/utils/helpers'; +import { generateTokenWithTwPost, requestAuthenByShareCode } from '@/services/player-share'; +import AuthenStorage from '@/utils/storage/authen.storage'; +import { setBearerToken } from '@/services/whitelist'; +import { requestReload } from '@/stores/states/common/reducer'; +import { generateTokenWithMetamask, generateTokenWithOauth, getPublicSaleSummary } from '@/services/public-sale'; +import { BVM_API, TWITTER_CLIENT_ID } from '@/config'; +import { formatCurrency } from '@/utils/format'; +import { useAppSelector } from '@/stores/hooks'; +import { setUserToken } from '@/stores/states/user/reducer'; +import AppLoading from '@/components/AppLoading'; +import { getError } from '@/utils/error'; +import toast from 'react-hot-toast'; +import { signMessage } from '@/utils/metamask-helper'; interface IAuthForBuyV2 extends PropsWithChildren { renderWithoutLogin?: (onClick: any) => any; @@ -14,12 +29,159 @@ const AuthForBuyV2: React.FC = ({ children, renderWithoutLogin, }) => { + const user = useAppSelector(userSelector); + const [authenCode, setAuthenCode] = useState(); + const timer = useRef(); + const [submitting, setSubmitting] = useState(false); + const dispatch = useDispatch(); + const [isCopy, setIsCopy] = useState(false); + const uuid = getUuid(); + const userToken = useSelector(userTokenSelector); const isLogged = useMemo(() => Boolean(userToken), [userToken]); const { isOpen, onOpen, onClose } = useDisclosure(); + useEffect(() => { + if (authenCode?.public_code) { + setSubmitting(true); + timer.current = setInterval(async () => { + handleVerifyTwitter(); + }, 5000); + } + return () => { + clearInterval(timer.current); + }; + }, [authenCode?.public_code]); + + const handleVerifyTwitter = async (): Promise => { + try { + const result = await generateTokenWithTwPost( + authenCode?.secret_code as string, + ); + onVerifyTwSuccess(result); + } catch (err) { + console.log('handleVerifyTwitter', err); + } + }; + + const onVerifyTwSuccess = (result: any) => { + if (result) { + clearInterval(timer.current); + const twitterToken = AuthenStorage.getAuthenKey(); + if (!twitterToken || twitterToken !== result?.token) { + AuthenStorage.setAuthenKey(result?.token); + setBearerToken(result?.token); + dispatch(setUserToken(result?.token)) + } + setSubmitting(false); + dispatch(requestReload()); + onClose(); + } + }; + + useEffect(() => { + if (!user?.twitter_id) { + handleVerifyTwitterWithUUID(); + timer.current = setInterval(async () => { + handleVerifyTwitterWithUUID(); + }, 2000); + } + return () => { + clearInterval(timer.current); + }; + }, [user]); + + const handleVerifyTwitterWithUUID = async (): Promise => { + try { + const result = await generateTokenWithOauth(uuid); + if (result) { + clearInterval(timer.current); + if (!userToken || userToken !== result?.token) { + onVerifyTwSuccess(result); + } + } + } catch (e) { + console.log('handleVerifyTwitter TwitterSignIn', e); + } + }; + + const getTwitterOauthUrl = () => { + const URL = `${window.location.origin}/public-sale`; + const rootUrl = 'https://twitter.com/i/oauth2/authorize'; + const options = { + redirect_uri: `${BVM_API}/twitter-api/oauth/twitter-bvm?callbackURL=${URL}&uuid=${uuid}`, + client_id: TWITTER_CLIENT_ID, + state: 'state', + response_type: 'code', + code_challenge: 'challenge', + code_challenge_method: 'plain', + scope: [ + 'users.read', + 'tweet.read', + 'follows.read', + 'offline.access', + ].join(' '), + }; + const qs = new URLSearchParams(options).toString(); + setTimeout(() => window.open(`${rootUrl}?${qs}`, '_self')) + }; + + const generateLinkTweet = async () => { + let code = ''; + if (!userToken) { + const res: any = await requestAuthenByShareCode(); + setAuthenCode(res); + code = `\n\n#${res?.public_code}`; + } + + const shareUrl = !user?.referral_code + ? 'bvm.network/public-sale' + : getLink(user?.referral_code || ''); + + const saleSummary = await getPublicSaleSummary(); + const content = `Welcome to the future of Bitcoin!\n\n@BVMnetwork is the first modular blockchain metaprotocol that lets you launch your Bitcoin L2 blockchain in a few clicks.\n\nJoin the ${formatCurrency( + saleSummary.total_user || '0', + 0, + 0, + 'BTC', + false, + )} early contributors backing us with ${formatCurrency( + saleSummary.total_usdt_value_not_boost || '0', + 0, + 0, + 'BTC', + false, + )} to build the future of Bitcoin.${code}`; + return `https://twitter.com/intent/tweet?url=${shareUrl}&text=${encodeURIComponent( + content, + )}`; + }; + + const handleShareTw = async () => { + setIsCopy(false); + const content = await generateLinkTweet(); + setTimeout(() => window.open(content, '_blank')) + }; + + const handleLoginMetamask = async () => { + try { + const { signature, message, address } = await signMessage((address: string) => { + return `This action verifies the ownership of ${address} and allows you to join the $BVM public sale.` + }) + const result = await generateTokenWithMetamask({ message, signature, address }) + if (result && !!result.token) { + AuthenStorage.setAuthenKey(result.token as string); + setBearerToken(result.token as string); + dispatch(setUserToken(result.token as string)) + } + } catch (error) { + const { message } = getError(error) + toast.error(message) + } + } + if (isLogged) { return children; } @@ -35,159 +197,162 @@ const AuthForBuyV2: React.FC = ({ onHide={onClose} className={s.loginModal} > - - + - + + + + + + + + + + + + + + Connect your Metamask + + ); diff --git a/src/modules/PublicSale/BuyForm/index.tsx b/src/modules/PublicSale/BuyForm/index.tsx index 81ef64661..d6de10622 100644 --- a/src/modules/PublicSale/BuyForm/index.tsx +++ b/src/modules/PublicSale/BuyForm/index.tsx @@ -26,6 +26,8 @@ import { GuestCodeHere } from '../depositModal/deposit.guest.code'; import LoginTooltip from '@/modules/PublicSale/depositModal/login.tooltip'; import { useAppSelector } from '@/stores/hooks'; import { commonSelector } from '@/stores/states/common/selector'; +import IcHelp from '@/components/InfoTooltip/IcHelp'; +import AuthForBuyV2 from '@/modules/PublicSale/AuthForBuyV2'; interface FormValues { tokenAmount: string; @@ -178,6 +180,14 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { gap={1} alignItems={"center"} > Your contribution + ( + + + + )}> + + { }}/> diff --git a/src/modules/Whitelist/FAQContent/index.tsx b/src/modules/Whitelist/FAQContent/index.tsx index 7eb7fb6d1..ddef2a8d8 100644 --- a/src/modules/Whitelist/FAQContent/index.tsx +++ b/src/modules/Whitelist/FAQContent/index.tsx @@ -3,7 +3,7 @@ import { Accordion, AccordionItem, AccordionButton, - AccordionPanel, + AccordionPanel, Button, } from '@chakra-ui/react'; import s from './styles.module.scss'; import { CDN_URL } from '@/config'; @@ -15,6 +15,7 @@ import { compareString } from '@/utils/string'; import DepositGuestCodeHere from '@/modules/PublicSale/depositModal/deposit.guest.code'; import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import AuthenStorage from '@/utils/storage/authen.storage'; +import AuthForBuyV2 from '@/modules/PublicSale/AuthForBuyV2'; const FAQContent: React.FC = (): React.ReactElement => { const user = useAppSelector(userSelector); @@ -232,9 +233,12 @@ const FAQContent: React.FC = (): React.ReactElement => { ) : (

If you have a boost,{' '} - - claim it here - + + ( + claim it here + )}> + .

)} diff --git a/src/services/public-sale.ts b/src/services/public-sale.ts index 9ba0b93c5..6ca208fa2 100644 --- a/src/services/public-sale.ts +++ b/src/services/public-sale.ts @@ -67,6 +67,18 @@ export const generateTokenWithOauth = async ( return res; }; +export const generateTokenWithMetamask = async ( + params: { address: string, message: string, signature: string } +): Promise => { + const res = (await apiClient.post(`/bvm/generate-token-with-wallet`, { + "wallet_type": "ethereum", + "address": params.address, + "message": params.message, + "signature": params.signature + })) as unknown as IGenerateTOkenWithSecretCode; + return res; +}; + export const getPublicSaleLeaderBoards = async (params: { page?: number; limit?: number; diff --git a/src/stores/states/user/selector.ts b/src/stores/states/user/selector.ts index 13df04199..25931b9cb 100644 --- a/src/stores/states/user/selector.ts +++ b/src/stores/states/user/selector.ts @@ -2,10 +2,11 @@ import { RootState } from '@/stores'; import { EVMFieldType, User } from '@/stores/states/user/types'; import { ILeaderBoardPoint } from '@/interfaces/leader-board-point'; import { SignatureStatus } from '@/interfaces/whitelist'; +import AuthenStorage from '@/utils/storage/authen.storage'; export const userSelector = (state: RootState) => state.user?.user as User | undefined; -export const userTokenSelector = (state: RootState) => state.user?.userToken; +export const userTokenSelector = (state: RootState) => state.user?.userToken || AuthenStorage.getAuthenKey() || AuthenStorage.getGuestAuthenKey(); export const leaderBoardSelector = (state: RootState) => ({ list: (state.user?.leaderBoard || []) as ILeaderBoardPoint[], count: (state.user?.leaderBoardCount || '') as string, diff --git a/src/utils/metamask-helper.ts b/src/utils/metamask-helper.ts index 18192e07c..c9dd9f541 100644 --- a/src/utils/metamask-helper.ts +++ b/src/utils/metamask-helper.ts @@ -119,13 +119,12 @@ export const signMessage = async (message: any): Promise<{ message: string, addr const signer = web3Provider.getSigner(); const address = addresses && Array.isArray(addresses) ? addresses[0] : ''; - const signature = await signer.signMessage( - typeof message === 'function' ? message(address) : message, - ); + const messageForSign = typeof message === 'function' ? message(address) : message; + const signature = await signer.signMessage(messageForSign); return { address, signature, - message, + message: messageForSign, }; } catch (err) { throw err; From b9951bee0a74a8476e4a3a7220d88c4586dcefd3 Mon Sep 17 00:00:00 2001 From: camewell <130561684+camewell071@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:39:51 +0700 Subject: [PATCH 05/10] chore: login v3 --- src/modules/PublicSale/AuthForBuyV2/index.tsx | 85 +++++++++++++------ src/modules/PublicSale/BuyForm/index.tsx | 4 +- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/modules/PublicSale/AuthForBuyV2/index.tsx b/src/modules/PublicSale/AuthForBuyV2/index.tsx index f6986672a..4d610bba1 100644 --- a/src/modules/PublicSale/AuthForBuyV2/index.tsx +++ b/src/modules/PublicSale/AuthForBuyV2/index.tsx @@ -20,6 +20,7 @@ import AppLoading from '@/components/AppLoading'; import { getError } from '@/utils/error'; import toast from 'react-hot-toast'; import { signMessage } from '@/utils/metamask-helper'; +import VerifyTwModal from '@/modules/Whitelist/steps/VerifyTwModal'; interface IAuthForBuyV2 extends PropsWithChildren { renderWithoutLogin?: (onClick: any) => any; @@ -36,6 +37,8 @@ const AuthForBuyV2: React.FC = ({ const dispatch = useDispatch(); const [isCopy, setIsCopy] = useState(false); const uuid = getUuid(); + const [showManualCheck, setShowManualCheck] = useState(false); + const [showManualCheckModal, setShowManualCheckModal] = useState(false); const userToken = useSelector(userTokenSelector); @@ -162,7 +165,10 @@ const AuthForBuyV2: React.FC = ({ const handleShareTw = async () => { setIsCopy(false); const content = await generateLinkTweet(); - setTimeout(() => window.open(content, '_blank')) + setTimeout(() => window.open(content, '_blank')); + setTimeout(() => { + setShowManualCheck(true); + }, 10000); }; const handleLoginMetamask = async () => { @@ -199,34 +205,48 @@ const AuthForBuyV2: React.FC = ({ > {submitting && } - + + + + + + + + Post on X + + {showManualCheck && ( + setShowManualCheckModal(true)} + mt={1} + fontSize={'12px !important'} + mb="12px" + > + Can't link account? + + )} + + { + setShowManualCheckModal(false); + }} + secretCode={authenCode?.secret_code} + onSuccess={onVerifyTwSuccess} + title={`Can't link account?`} + /> ); } diff --git a/src/modules/PublicSale/BuyForm/index.tsx b/src/modules/PublicSale/BuyForm/index.tsx index d6de10622..f10288975 100644 --- a/src/modules/PublicSale/BuyForm/index.tsx +++ b/src/modules/PublicSale/BuyForm/index.tsx @@ -188,9 +188,9 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { )}> - { + {/* {*/} - }}/> + {/*}}/>*/} From c7365715ba8ca876b778f43a8617b940420d9871 Mon Sep 17 00:00:00 2001 From: camewell <130561684+camewell071@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:44:31 +0700 Subject: [PATCH 06/10] chore: update ui --- src/modules/PublicSale/BuyForm/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/PublicSale/BuyForm/index.tsx b/src/modules/PublicSale/BuyForm/index.tsx index f10288975..ba94ea36b 100644 --- a/src/modules/PublicSale/BuyForm/index.tsx +++ b/src/modules/PublicSale/BuyForm/index.tsx @@ -182,7 +182,7 @@ const PrivateSaleForm = ({ vcInfo }: { vcInfo?: VCInfo }) => { Your contribution ( - + )}> From 493d7bae4bc4c1edf0b2e07c271ff8945dc94729 Mon Sep 17 00:00:00 2001 From: camewell <130561684+camewell071@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:46:11 +0700 Subject: [PATCH 07/10] chore: update code --- src/utils/metamask-helper.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/metamask-helper.ts b/src/utils/metamask-helper.ts index c9dd9f541..83d804b8d 100644 --- a/src/utils/metamask-helper.ts +++ b/src/utils/metamask-helper.ts @@ -105,15 +105,16 @@ const connect = async (): Promise> => { export const signMessage = async (message: any): Promise<{ message: string, address: string, signature: string }> => { try { - if (!(window as any).ethereum) { - throw Error(WalletError.NO_INSTANCE); - } if (!isMetamaskInstalled()) { window.open(METAMASK_DOWNLOAD_PAGE); throw Error(WalletError.NO_METAMASK); } + if (!(window as any).ethereum) { + throw Error(WalletError.NO_INSTANCE); + } + const web3Provider = new ethers.providers.Web3Provider((window as any).ethereum); const addresses = await web3Provider.send('eth_requestAccounts', []); const signer = web3Provider.getSigner(); From 0b01a425d7172dad8099d74e0c1f47a6c72bc9ba Mon Sep 17 00:00:00 2001 From: camewell <130561684+camewell071@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:56:23 +0700 Subject: [PATCH 08/10] chore: remove authen tw without post --- src/modules/PublicSale/AuthForBuyV2/index.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/modules/PublicSale/AuthForBuyV2/index.tsx b/src/modules/PublicSale/AuthForBuyV2/index.tsx index 4d610bba1..ee098cf1b 100644 --- a/src/modules/PublicSale/AuthForBuyV2/index.tsx +++ b/src/modules/PublicSale/AuthForBuyV2/index.tsx @@ -247,35 +247,35 @@ const AuthForBuyV2: React.FC = ({ )} - + {/* Authorize*/} + {/**/}