From 6ba996ed63ceae09dd455a15050f4a6d8df57acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:39:22 +0700 Subject: [PATCH 01/26] update: tooltip limit order (#2333) --- src/components/swapv2/LimitOrder/useValidateInputError.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/swapv2/LimitOrder/useValidateInputError.tsx b/src/components/swapv2/LimitOrder/useValidateInputError.tsx index 1e158c458d..3a158ebb26 100644 --- a/src/components/swapv2/LimitOrder/useValidateInputError.tsx +++ b/src/components/swapv2/LimitOrder/useValidateInputError.tsx @@ -53,12 +53,13 @@ const useValidateInputError = ({ return ( - You don't have sufficient {currencyIn?.symbol} balance. After your active orders, you have{' '} + Insufficient {currencyIn?.symbol} balance. +
setInputValue(remainBalance.toExact())}> {!remainBalance.equalTo(JSBI.BigInt(0)) ? '~' : ''} {formatNum} {currencyIn?.symbol} {' '} - left. + remaining after deducting Active and Open orders.
) From ab0dc67f6f4fabe13d858674d0f3d36be4d2c5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:16:14 +0700 Subject: [PATCH 02/26] feat: sign in email for kyberAI (#2296) --- package.json | 2 +- src/components/Header/web3/SelectWallet.tsx | 2 +- .../Header/web3/SignWallet/ConfirmModal.tsx | 8 +- .../Header/web3/SignWallet/ProfileContent.tsx | 17 ++- src/hooks/useLogin.tsx | 123 ++++++++---------- src/hooks/useSessionExpire.ts | 8 +- .../NotificationPreference/InputEmail.tsx | 74 ++++++++--- .../NotificationPreference/index.tsx | 34 ++--- .../NotificationCenter/Profile/AvatarEdit.tsx | 2 +- .../Profile/WarningSignMessage.tsx | 2 +- .../NotificationCenter/Profile/index.tsx | 48 +++---- src/pages/Oauth/AuthForm/ButtonEth.tsx | 55 +++----- src/pages/Oauth/AuthForm/ButtonGoogle.tsx | 6 +- src/pages/Oauth/AuthForm/EmailLoginForm.tsx | 73 +++++++++++ src/pages/Oauth/AuthForm/index.tsx | 72 +++++----- src/pages/Oauth/AuthForm/useAutoSignIn.tsx | 11 +- src/pages/Oauth/Login.tsx | 65 ++++----- src/pages/Oauth/helpers.ts | 37 +++++- .../pages/RegisterWhitelist/SignInForm.tsx | 58 +++++++++ .../pages/RegisterWhitelist/SubscribeForm.tsx | 4 +- .../pages/RegisterWhitelist/index.tsx | 26 +--- src/pages/Verify/VerifyCodeModal/index.tsx | 75 +++++++---- src/state/authen/reducer.ts | 2 +- src/state/profile/hooks.ts | 26 ++-- src/state/user/hooks.tsx | 4 +- yarn.lock | 8 +- 26 files changed, 510 insertions(+), 332 deletions(-) create mode 100644 src/pages/Oauth/AuthForm/EmailLoginForm.tsx create mode 100644 src/pages/TrueSightV2/pages/RegisterWhitelist/SignInForm.tsx diff --git a/package.json b/package.json index 285bf6399e..67d7d6bdbe 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@blocto/web3-react-connector": "^1.0.0", "@coinbase/wallet-sdk": "^3.0.4", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@kybernetwork/oauth2": "1.0.0", + "@kybernetwork/oauth2": "1.0.1", "@kyberswap/krystal-walletconnect-v2": "0.0.1", "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.0.13", diff --git a/src/components/Header/web3/SelectWallet.tsx b/src/components/Header/web3/SelectWallet.tsx index ca87b9b531..fe6fe28ef6 100644 --- a/src/components/Header/web3/SelectWallet.tsx +++ b/src/components/Header/web3/SelectWallet.tsx @@ -166,7 +166,7 @@ function Web3StatusInner() { style={{ cursor: 'pointer', fontSize: '12px', color: theme.primary }} onClick={e => { e.stopPropagation() - signIn(account) + signIn({ account }) }} > sign-in diff --git a/src/components/Header/web3/SignWallet/ConfirmModal.tsx b/src/components/Header/web3/SignWallet/ConfirmModal.tsx index f14f4cecf7..c1adaffec9 100644 --- a/src/components/Header/web3/SignWallet/ConfirmModal.tsx +++ b/src/components/Header/web3/SignWallet/ConfirmModal.tsx @@ -1,3 +1,4 @@ +import { LoginMethod } from '@kybernetwork/oauth2' import { Trans } from '@lingui/macro' import { useEffect, useState } from 'react' import { LogIn, X } from 'react-feather' @@ -75,7 +76,10 @@ const ModalConfirmProfile: React.FC = () => { const onCancel = async () => { const isGuest = !desiredAccountExist - await signIn(isGuest ? undefined : account, isGuest) + await signIn({ + account: isGuest ? undefined : account, + loginMethod: isGuest ? LoginMethod.ANONYMOUS : LoginMethod.ETH, + }) hideModal() } @@ -209,7 +213,7 @@ const ModalConfirmProfile: React.FC = () => { {!desiredAccountExist && !connectSuccess && ( signIn(account)} + onClick={() => signIn({ account })} style={{ color: theme.subText, display: 'flex', diff --git a/src/components/Header/web3/SignWallet/ProfileContent.tsx b/src/components/Header/web3/SignWallet/ProfileContent.tsx index 5514030ad2..48c1f1315c 100644 --- a/src/components/Header/web3/SignWallet/ProfileContent.tsx +++ b/src/components/Header/web3/SignWallet/ProfileContent.tsx @@ -1,4 +1,4 @@ -import KyberOauth2 from '@kybernetwork/oauth2' +import KyberOauth2, { LoginMethod } from '@kybernetwork/oauth2' import { Trans } from '@lingui/macro' import { rgba } from 'polished' import { useState } from 'react' @@ -24,7 +24,7 @@ import { useToggleModal } from 'state/application/hooks' import { ConnectedProfile, useProfileInfo } from 'state/profile/hooks' import { MEDIA_WIDTHS } from 'theme' import getShortenAddress from 'utils/getShortenAddress' -import { isEmailValid, shortString } from 'utils/string' +import { shortString } from 'utils/string' const ContentWrapper = styled.div` display: flex; @@ -120,7 +120,7 @@ const ProfileItemWrapper = styled(RowBetween)<{ active: boolean }>` ` const ProfileItem = ({ - data: { active, guest, address: account, profile, id }, + data: { active, name: account, profile, id, type }, totalGuest, }: { data: ConnectedProfile @@ -132,11 +132,16 @@ const ProfileItem = ({ const toggleModal = useToggleModal(ApplicationModal.SWITCH_PROFILE_POPUP) const { signIn, signOut } = useLogin() const [loading, setLoading] = useState(false) + const guest = type === LoginMethod.ANONYMOUS const onClick = async () => { if (active || loading) return setLoading(true) - await signIn(id, guest, true) + await signIn({ + account: id, + loginMethod: type, + showSessionExpired: true, + }) setLoading(false) toggleModal() } @@ -178,7 +183,7 @@ const ProfileItem = ({ fontSize={active ? '16px' : profile?.nickname ? '12px' : '16px'} color={active ? theme.subText : theme.subText} > - {guest || isEmailValid(account) ? shortString(account, 20) : getShortenAddress(account)} + {type === LoginMethod.ETH ? getShortenAddress(account) : shortString(account, 20)} {active && signOutBtn} @@ -220,7 +225,7 @@ const ProfileContent = ({ scroll, toggleModal }: { scroll?: boolean; toggleModal {listNotActive.map(data => ( - + ))} diff --git a/src/hooks/useLogin.tsx b/src/hooks/useLogin.tsx index 2c492a0f31..df7ae49f0e 100644 --- a/src/hooks/useLogin.tsx +++ b/src/hooks/useLogin.tsx @@ -14,7 +14,6 @@ import useParsedQueryString from 'hooks/useParsedQueryString' import { useNotify, useWalletModalToggle } from 'state/application/hooks' import { useIsAutoLoginAfterConnectWallet, - useSessionInfo, useSetConfirmChangeProfile, useSetPendingAuthentication, } from 'state/authen/hooks' @@ -27,7 +26,6 @@ import { useSaveUserProfile, useSignedAccountInfo, } from 'state/profile/hooks' -import { filterTruthy, isAddress } from 'utils' import { setLoginRedirectUrl } from 'utils/redirectUponLogin' import { isEmailValid } from 'utils/string' @@ -42,10 +40,9 @@ export const initializeOauthKyberSwap = () => { initializeOauthKyberSwap() const useLogin = (autoLogin = false) => { - const { account, chainId, isEVM } = useActiveWeb3React() + const { account } = useActiveWeb3React() const [createProfile] = useGetOrCreateProfileMutation() - // const [connectWalletToProfile] = useConnectWalletToProfileMutation() const notify = useNotify() const toggleWalletModal = useWalletModalToggle() const { signedMethod, signedAccount } = useSignedAccountInfo() @@ -59,28 +56,17 @@ const useLogin = (autoLogin = false) => { const getProfile = useCallback( async ({ walletAddress, - isAnonymous, - session, + loginMethod, account, }: { walletAddress: string | undefined - isAnonymous: boolean + loginMethod: LoginMethod account: string - session: any }) => { + const isAnonymous = loginMethod === LoginMethod.ANONYMOUS try { const profile = await createProfile().unwrap() - if (walletAddress && isAddress(chainId, walletAddress)) { - // await connectWalletToProfile({ walletAddress }) // temp off - } - const formatProfile = { ...profile } - if (isEmailValid(account) && session) { - // sign in with google - formatProfile.avatarUrl = session?.picture ?? '' - formatProfile.email = session?.email ?? '' - formatProfile.nickname = filterTruthy([session?.first_name, session?.last_name]).join(' ') - } setProfile({ profile: formatProfile, isAnonymous, account }) } catch (error) { const e = new Error('createProfile Error', { cause: error }) @@ -89,49 +75,49 @@ const useLogin = (autoLogin = false) => { setProfile({ profile: undefined, isAnonymous, account }) } }, - [createProfile, setProfile, chainId], + [createProfile, setProfile], ) const showSignInSuccess = useCallback( - (desireAccount: string | undefined, guest = false) => - !autoLogin && + (desireAccount: string | undefined, loginMethod: LoginMethod) => { + const isGuest = loginMethod === LoginMethod.ANONYMOUS + if (autoLogin) return notify( { type: NotificationType.SUCCESS, title: t`Signed in successfully`, summary: desireAccount?.toLowerCase() === account?.toLowerCase() - ? t`Connected successfully with the current wallet address` - : t`Connected successfully with ${ - isEmailValid(desireAccount) - ? `email ${desireAccount}` - : guest - ? `Guest Profile` - : `profile ${getProfileName(desireAccount, guest)}` - }`, + ? t`Connected successfully with the current wallet address.` + : isGuest + ? t`Connected successfully with Guest Profile.` + : t`Connected successfully with profile ${getProfileName(desireAccount, isGuest)}.`, }, 10_000, - ), + ) + }, [account, notify, autoLogin, getProfileName], ) const signInAnonymous = useCallback( async (guestAccountParam?: string, showSuccessMsg = true) => { - let userInfo const guestAccount = guestAccountParam || KEY_GUEST_DEFAULT let hasError = false try { setLoading(true) - const resp = await KyberOauth2.loginAnonymous(guestAccount === KEY_GUEST_DEFAULT ? undefined : guestAccount) - userInfo = resp.userInfo + await KyberOauth2.loginAnonymous(guestAccount === KEY_GUEST_DEFAULT ? undefined : guestAccount) saveSignedAccount({ account: guestAccount, method: LoginMethod.ANONYMOUS }) } catch (error) { console.log('sign in anonymous err', error) hasError = true } finally { setLoading(false) - await getProfile({ walletAddress: account, isAnonymous: true, account: guestAccount, session: userInfo }) - !hasError && showSuccessMsg && showSignInSuccess(guestAccount, true) + await getProfile({ + walletAddress: account, + account: guestAccount, + loginMethod: LoginMethod.ANONYMOUS, + }) + !hasError && showSuccessMsg && showSignInSuccess(guestAccount, LoginMethod.ANONYMOUS) } }, [getProfile, setLoading, account, saveSignedAccount, showSignInSuccess], @@ -143,7 +129,7 @@ const useLogin = (autoLogin = false) => { try { setLoading(true) const { loginMethod, userInfo } = await KyberOauth2.getSession( - isEmailValid(desireAccount ?? '') || !desireAccount + isEmailValid(desireAccount) || !desireAccount ? { account: desireAccount } : { method: LoginMethod.ETH, account: desireAccount }, ) @@ -151,11 +137,10 @@ const useLogin = (autoLogin = false) => { saveSignedAccount({ account: respAccount, method: loginMethod }) await getProfile({ walletAddress: respAccount, - isAnonymous: false, - session: userInfo, + loginMethod, account: respAccount, }) - showSignInSuccess(respAccount) + showSignInSuccess(respAccount, loginMethod) } catch (error) { console.log('sdk get session err:', desireAccount, error.message) if (loginAnonymousIfFailed) { @@ -168,23 +153,30 @@ const useLogin = (autoLogin = false) => { [setLoading, signInAnonymous, getProfile, saveSignedAccount, showSignInSuccess], ) - const redirectSignIn = useCallback( - (account: string) => { - if (window.location.pathname.startsWith(APP_PATHS.IAM_LOGIN)) return - setLoginRedirectUrl(window.location.href) - KyberOauth2.authenticate(isEVM ? { wallet_address: account } : {}) // navigate to login page - }, - [isEVM], - ) + const redirectSignIn = useCallback((account: string, loginMethod = LoginMethod.ETH) => { + if (window.location.pathname.startsWith(APP_PATHS.IAM_LOGIN)) return + setLoginRedirectUrl(window.location.href) + const accountKey = loginMethod === LoginMethod.ETH ? 'wallet_address' : 'email' + KyberOauth2.authenticate({ [accountKey]: account, type: loginMethod }) // navigate to login page + }, []) // check account info and redirect if needed const [, setAutoSignIn] = useIsAutoLoginAfterConnectWallet() const signIn = useCallback( - async (desireAccount?: string, showSessionExpired = false) => { + async ({ + desireAccount, + showSessionExpired, + loginMethod = LoginMethod.ETH, + }: { + desireAccount?: string + showSessionExpired?: boolean + loginMethod?: LoginMethod + }) => { const isAddAccount = !desireAccount const isSelectAccount = !!desireAccount + const isEth = loginMethod === LoginMethod.ETH - if (isAddAccount && !account) { + if (isAddAccount && !account && isEth) { toggleWalletModal() setAutoSignIn({ value: true, account: desireAccount }) return @@ -198,19 +190,19 @@ const useLogin = (autoLogin = false) => { return } - const formatAccount = desireAccount || account || '' + const formatAccount = desireAccount || (isEth ? account : '') || '' if (showSessionExpired && isSelectAccount && !isTokenExist) { showConfirm({ isOpen: true, content: t`Your session has expired. Please sign-in to continue.`, title: t`Session Expired`, confirmText: t`Sign-in`, - onConfirm: () => redirectSignIn(formatAccount), + onConfirm: () => redirectSignIn(formatAccount, loginMethod), cancelText: t`Cancel`, }) return } - redirectSignIn(formatAccount) + redirectSignIn(formatAccount, loginMethod) }, [account, checkSessionSignIn, toggleWalletModal, showConfirm, setAutoSignIn, redirectSignIn], ) @@ -305,8 +297,18 @@ const useLogin = (autoLogin = false) => { ) const signInWrapped = useCallback( - (desireAccount: string | undefined = undefined, isGuest = false, showSessionExpired = false) => { - return isGuest ? signInAnonymous(desireAccount) : signIn(desireAccount, showSessionExpired) + ({ + account, + loginMethod = LoginMethod.ETH, + showSessionExpired = false, + }: { + account?: string + loginMethod?: LoginMethod + showSessionExpired?: boolean + } = {}) => { + return loginMethod === LoginMethod.ANONYMOUS + ? signInAnonymous(account) + : signIn({ desireAccount: account, showSessionExpired, loginMethod }) }, [signInAnonymous, signIn], ) @@ -326,9 +328,7 @@ export const useAutoLogin = () => { const { signedMethod, signedAccount } = useSignedAccountInfo() const qs = useParsedQueryString() const { account } = useActiveWeb3React() - const { userInfo } = useSessionInfo() const [isKeepCurrentProfile] = useIsKeepCurrentProfile() - // const [connectWalletToProfile] = useConnectWalletToProfileMutation() const { signIn, checkSessionSignIn, signInAnonymous } = useLogin(true) // auto try sign in when the first visit app, call once @@ -353,19 +353,10 @@ export const useAutoLogin = () => { useIsAutoLoginAfterConnectWallet() useEffect(() => { if (!account || !needSignInAfterConnectWallet) return - signIn(accountSignAfterConnectedWallet) + signIn({ account: accountSignAfterConnectedWallet }) setAutoSignIn({ value: false, account: undefined }) }, [account, needSignInAfterConnectWallet, accountSignAfterConnectedWallet, signIn, setAutoSignIn]) - // call api connect-wallet to guest profile - useEffect(() => { - if (signedMethod === LoginMethod.ANONYMOUS && account && userInfo?.identityId) { - try { - // connectWalletToProfile({ walletAddress: account }) - } catch (error) {} - } - }, [account, userInfo?.identityId, signedMethod]) - const setConfirm = useSetConfirmChangeProfile() // show confirm change profile when change wallet diff --git a/src/hooks/useSessionExpire.ts b/src/hooks/useSessionExpire.ts index e000a5aca1..0c466bf4fe 100644 --- a/src/hooks/useSessionExpire.ts +++ b/src/hooks/useSessionExpire.ts @@ -8,6 +8,7 @@ import { APP_PATHS } from 'constants/index' import useLogin from 'hooks/useLogin' import { ConfirmModalState } from 'state/application/reducer' import { useSignedAccountInfo } from 'state/profile/hooks' +import { isEmailValid } from 'utils/string' export default function useSessionExpiredGlobal() { const { pathname } = useLocation() @@ -25,7 +26,10 @@ export default function useSessionExpiredGlobal() { title: t`Session Expired`, confirmText: t`Sign-in`, cancelText: t`Cancel`, - onConfirm: () => redirectSignIn(accountId || signedAccount), + onConfirm: () => { + const account = accountId || signedAccount + redirectSignIn(account, isEmailValid(account) ? LoginMethod.EMAIL : undefined) + }, onCancel: () => { signInAnonymous(KyberOauth2.getConnectedAnonymousAccounts()[0]) }, @@ -54,7 +58,7 @@ export default function useSessionExpiredGlobal() { const accountSignHasChanged = signedMethod !== newLoginMethod || signedAccount !== newSignedAccount if (document.visibilityState === 'visible' && accountSignHasChanged) { // sync account in multi window tab - signIn(newSignedAccount, newLoginMethod === LoginMethod.ANONYMOUS) + signIn({ account: newSignedAccount, loginMethod: newLoginMethod }) } } catch (error) {} } diff --git a/src/pages/NotificationCenter/NotificationPreference/InputEmail.tsx b/src/pages/NotificationCenter/NotificationPreference/InputEmail.tsx index 9d8c878332..e296483147 100644 --- a/src/pages/NotificationCenter/NotificationPreference/InputEmail.tsx +++ b/src/pages/NotificationCenter/NotificationPreference/InputEmail.tsx @@ -6,6 +6,7 @@ import { ButtonLight } from 'components/Button' import Input from 'components/Input' import Tooltip from 'components/Tooltip' import useTheme from 'hooks/useTheme' +import VerifyCodeModal from 'pages/Verify/VerifyCodeModal' const CheckIcon = styled(Check)` position: absolute; @@ -26,28 +27,26 @@ const ButtonVerify = styled(ButtonLight)` const InputWrapper = styled.div` position: relative; ` - -export default function InputEmail({ - errorColor, +type Props = { + onChange: (val: string) => void + isVerifiedEmail?: boolean + value: string + disabled?: boolean + hasError?: boolean + showVerifyModal?: () => void + style?: CSSProperties + placement?: string +} +export function InputEmail({ value, onChange, isVerifiedEmail, showVerifyModal, disabled, hasError, - color, style, -}: { - errorColor?: string - onChange: (val: string) => void - isVerifiedEmail: boolean - value: string - disabled?: boolean - hasError?: boolean - showVerifyModal: () => void - color?: string - style?: CSSProperties -}) { + placement, +}: Props) { const theme = useTheme() return ( onChange(e.target.value)} /> {!isVerifiedEmail && value && ( - + { + e.preventDefault() + showVerifyModal?.() + }} + > Verify )} - {isVerifiedEmail && value && } + {isVerifiedEmail && value && !hasError && } ) } + +export default function InputEmailWithVerification( + props: Props & { + isShowVerify: boolean + onDismissVerifyModal: () => void + sendCodeFn?: (data: { email: string }) => Promise + verifyCodeFn?: (data: { email: string; code: string }) => Promise + getErrorMsgFn?: (err: any) => string + }, +) { + const { value, isShowVerify, onDismissVerifyModal, sendCodeFn, verifyCodeFn, getErrorMsgFn } = props + return ( + <> + + + + ) +} diff --git a/src/pages/NotificationCenter/NotificationPreference/index.tsx b/src/pages/NotificationCenter/NotificationPreference/index.tsx index 153edacb22..a2c977f7f5 100644 --- a/src/pages/NotificationCenter/NotificationPreference/index.tsx +++ b/src/pages/NotificationCenter/NotificationPreference/index.tsx @@ -18,12 +18,10 @@ import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useNotification, { Topic, TopicType } from 'hooks/useNotification' import useTheme from 'hooks/useTheme' import ActionButtons from 'pages/NotificationCenter/NotificationPreference/ActionButtons' -import InputEmail from 'pages/NotificationCenter/NotificationPreference/InputEmail' +import InputEmailWithVerification from 'pages/NotificationCenter/NotificationPreference/InputEmail' import { PROFILE_MANAGE_ROUTES } from 'pages/NotificationCenter/const' -import VerifyCodeModal from 'pages/Verify/VerifyCodeModal' import { useNotify } from 'state/application/hooks' import { useSessionInfo } from 'state/authen/hooks' -import { useSignedAccountInfo } from 'state/profile/hooks' import { useIsWhiteListKyberAI } from 'state/user/hooks' import { pushUnique } from 'utils' import { isEmailValid } from 'utils/string' @@ -110,8 +108,6 @@ export const useValidateEmail = (defaultEmail?: string) => { const [inputEmail, setInputEmail] = useState(defaultEmail || '') const [errorInput, setErrorInput] = useState(null) - const theme = useTheme() - const validateInput = useCallback((value: string) => { const isValid = isEmailValid(value) const errMsg = t`Please input a valid email address` @@ -127,9 +123,6 @@ export const useValidateEmail = (defaultEmail?: string) => { [validateInput], ) - const hasErrorInput = !!errorInput - const errorColor = hasErrorInput ? theme.red : theme.border - const reset = useCallback( (email: string | undefined) => { setErrorInput(null) @@ -138,7 +131,7 @@ export const useValidateEmail = (defaultEmail?: string) => { [defaultEmail], ) - return { inputEmail: inputEmail.trim(), onChangeEmail, errorInput, errorColor, hasErrorInput, reset } + return { inputEmail: inputEmail.trim(), onChangeEmail, errorInput, reset } } function NotificationPreference({ toggleModal = noop }: { toggleModal?: () => void }) { @@ -147,9 +140,9 @@ function NotificationPreference({ toggleModal = noop }: { toggleModal?: () => vo const { account } = useActiveWeb3React() const { userInfo, isLogin } = useSessionInfo() - const { isSignInEmail } = useSignedAccountInfo() const { isWhiteList } = useIsWhiteListKyberAI() + const { inputEmail, errorInput, onChangeEmail, reset } = useValidateEmail(userInfo?.email) const [isShowVerify, setIsShowVerify] = useState(false) const showVerifyModal = () => { setIsShowVerify(true) @@ -165,7 +158,8 @@ function NotificationPreference({ toggleModal = noop }: { toggleModal?: () => vo const { mixpanelHandler } = useMixpanel() const [emailPendingVerified, setEmailPendingVerified] = useState('') - const { inputEmail, errorInput, onChangeEmail, errorColor, reset, hasErrorInput } = useValidateEmail(userInfo?.email) + + const hasErrorInput = !!errorInput const [selectedTopic, setSelectedTopic] = useState([]) @@ -375,16 +369,20 @@ function NotificationPreference({ toggleModal = noop }: { toggleModal?: () => vo - - {errorInput && } + {errorInput && ( + + )} @@ -461,12 +459,6 @@ function NotificationPreference({ toggleModal = noop }: { toggleModal?: () => vo } /> )} - ) } diff --git a/src/pages/NotificationCenter/Profile/AvatarEdit.tsx b/src/pages/NotificationCenter/Profile/AvatarEdit.tsx index afd5009d8f..9e3e15fc22 100644 --- a/src/pages/NotificationCenter/Profile/AvatarEdit.tsx +++ b/src/pages/NotificationCenter/Profile/AvatarEdit.tsx @@ -40,7 +40,7 @@ export default function AvatarEdit({ handleFileChange, size, }: { - disabled: boolean + disabled?: boolean size: string avatar: string | undefined handleFileChange: (imgUrl: string, file: File) => void diff --git a/src/pages/NotificationCenter/Profile/WarningSignMessage.tsx b/src/pages/NotificationCenter/Profile/WarningSignMessage.tsx index 189673d167..972bfc58c4 100644 --- a/src/pages/NotificationCenter/Profile/WarningSignMessage.tsx +++ b/src/pages/NotificationCenter/Profile/WarningSignMessage.tsx @@ -63,7 +63,7 @@ const WarningSignMessage = () => { Read More )} - signIn(account)}> + signIn({ account })}> Sign-in diff --git a/src/pages/NotificationCenter/Profile/index.tsx b/src/pages/NotificationCenter/Profile/index.tsx index 6c0ca88943..091f00adba 100644 --- a/src/pages/NotificationCenter/Profile/index.tsx +++ b/src/pages/NotificationCenter/Profile/index.tsx @@ -21,13 +21,12 @@ import { useUploadImageToCloud } from 'hooks/social' import useLogin from 'hooks/useLogin' import useTheme from 'hooks/useTheme' import { useValidateEmail } from 'pages/NotificationCenter/NotificationPreference' -import InputEmail from 'pages/NotificationCenter/NotificationPreference/InputEmail' +import InputEmailWithVerification from 'pages/NotificationCenter/NotificationPreference/InputEmail' import AvatarEdit from 'pages/NotificationCenter/Profile/AvatarEdit' import ExportAccountButton from 'pages/NotificationCenter/Profile/ExportAccountButton' import WarningSignMessage from 'pages/NotificationCenter/Profile/WarningSignMessage' import { ButtonLogout, ButtonSave } from 'pages/NotificationCenter/Profile/buttons' import { PROFILE_MANAGE_ROUTES } from 'pages/NotificationCenter/const' -import VerifyCodeModal from 'pages/Verify/VerifyCodeModal' import { useNotify } from 'state/application/hooks' import { useSessionInfo } from 'state/authen/hooks' import { useIsKeepCurrentProfile, useProfileInfo, useRefreshProfile, useSignedAccountInfo } from 'state/profile/hooks' @@ -119,11 +118,20 @@ export default function Profile() { const isMobile = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const { chainId } = useActiveWeb3React() const { userInfo } = useSessionInfo() - const { inputEmail, onChangeEmail, errorColor, hasErrorInput } = useValidateEmail(userInfo?.email) + + const { inputEmail, onChangeEmail, errorInput } = useValidateEmail(userInfo?.email) + const [isShowVerify, setIsShowVerify] = useState(false) + const showVerifyModal = () => { + setIsShowVerify(true) + } + const onDismissVerifyModal = () => { + setIsShowVerify(false) + } + const [nickname, setNickName] = useState('') const { signOut } = useLogin() const navigate = useNavigate() - const { isSignInEmail, isSignInEth, signedAccount, isSigInGuest } = useSignedAccountInfo() + const { isSignInEth, signedAccount, isSigInGuest } = useSignedAccountInfo() const { totalGuest } = useProfileInfo() const canSignOut = !isSigInGuest || (isSigInGuest && totalGuest > 1) @@ -158,14 +166,6 @@ export default function Profile() { file && setFile(file) }, [userInfo, onChangeEmail, prevIdentity]) - const [isShowVerify, setIsShowVerify] = useState(false) - const showVerifyModal = () => { - setIsShowVerify(true) - } - const onDismissVerifyModal = () => { - setIsShowVerify(false) - } - const handleFileChange = (imgUrl: string, file: File) => { setFile(file) setPreviewImage(imgUrl) @@ -276,7 +276,6 @@ export default function Profile() { value={nickname} onChange={e => onChangeNickname(e.target.value)} placeholder="Your nickname" - disabled={isSignInEmail} /> @@ -289,15 +288,15 @@ export default function Profile() { Email Address (Optional) - @@ -306,12 +305,7 @@ export default function Profile() { - + @@ -334,12 +328,6 @@ export default function Profile() { )} - ) } diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index c95509a665..42b33dffc0 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -1,31 +1,28 @@ import { LoginMethod } from '@kybernetwork/oauth2' import { Trans } from '@lingui/macro' -import { useCallback } from 'react' -import { Flex, Text } from 'rebass' +import React, { useCallback } from 'react' +import { Text } from 'rebass' -import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import { ButtonLight, ButtonPrimary } from 'components/Button' import Wallet from 'components/Icons/Wallet' import Loader from 'components/Loader' import { useActiveWeb3React } from 'hooks' import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' import { FlowStatus } from 'pages/Oauth/Login' import { useWalletModalToggle } from 'state/application/hooks' -import { navigateToUrl } from 'utils/redirect' const ButtonEth = ({ loading, disabled, onClick, flowStatus, - showBtnCancel, - backUrl, + primary, }: { disabled: boolean loading: boolean onClick: () => void - backUrl: string | undefined flowStatus: FlowStatus - showBtnCancel: boolean + primary: boolean }) => { const toggleWalletModal = useWalletModalToggle() const { account } = useActiveWeb3React() @@ -40,31 +37,16 @@ const ButtonEth = ({ useAutoSignIn({ onClick: onClickEth, flowStatus, method: LoginMethod.ETH }) - return ( - - {showBtnCancel && ( - { - e.preventDefault() - navigateToUrl(backUrl) - }} - > - Cancel - - )} - { - e.preventDefault() - onClickEth() - }} - disabled={disabled} - > + const propsEth = { + height: '36px', + id: 'btnLoginEth', + onClick: (e: MouseEvent) => { + e.preventDefault() + onClickEth() + }, + disabled: disabled || loading, + children: ( + <> {loading ? ( <> @@ -80,9 +62,10 @@ const ButtonEth = ({   Sign-In with Wallet )} - - - ) + + ), + } + return React.createElement(primary ? ButtonPrimary : ButtonLight, propsEth) } export default ButtonEth diff --git a/src/pages/Oauth/AuthForm/ButtonGoogle.tsx b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx index 34e1896d94..e80d598fee 100644 --- a/src/pages/Oauth/AuthForm/ButtonGoogle.tsx +++ b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx @@ -7,11 +7,11 @@ import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' import { FlowStatus } from 'pages/Oauth/Login' interface Props { - outline: boolean + primary: boolean flowStatus: FlowStatus } -const ButtonGoogle: React.FC = ({ outline, flowStatus }) => { +const ButtonGoogle: React.FC = ({ primary, flowStatus }) => { const ref = useRef(null) const { autoLoginMethod } = flowStatus const isAutoLogin = autoLoginMethod === LoginMethod.GOOGLE @@ -32,6 +32,6 @@ const ButtonGoogle: React.FC = ({ outline, flowStatus }) => { children: Sign-In with Google, style: isAutoLogin ? { opacity: 0 } : undefined, } - return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) + return React.createElement(primary ? ButtonPrimary : ButtonOutlined, props) } export default ButtonGoogle diff --git a/src/pages/Oauth/AuthForm/EmailLoginForm.tsx b/src/pages/Oauth/AuthForm/EmailLoginForm.tsx new file mode 100644 index 0000000000..51c78128a1 --- /dev/null +++ b/src/pages/Oauth/AuthForm/EmailLoginForm.tsx @@ -0,0 +1,73 @@ +import KyberOauth2, { LoginMethod } from '@kybernetwork/oauth2' +import { Trans, t } from '@lingui/macro' +import { useState } from 'react' +import styled from 'styled-components' + +import { ButtonPrimary } from 'components/Button' +import Column from 'components/Column' +import useParsedQueryString from 'hooks/useParsedQueryString' +import { useValidateEmail } from 'pages/NotificationCenter/NotificationPreference' +import InputEmailWithVerification from 'pages/NotificationCenter/NotificationPreference/InputEmail' +import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' +import { FlowStatus, getIamErrorMsg } from 'pages/Oauth/Login' +import { isEmailValid, queryStringToObject } from 'utils/string' + +const Wrapper = styled(Column)` + width: 100%; + justify-content: center; + gap: 16px; +` + +const EmailLoginForm = ({ flowStatus }: { flowStatus: FlowStatus }) => { + const { email } = useParsedQueryString<{ email: string }>() + const { inputEmail, errorInput, onChangeEmail } = useValidateEmail(isEmailValid(email) ? email || '' : '') + + const [isShowVerify, setIsShowVerify] = useState(false) + const onDismissVerifyModal = () => { + setIsShowVerify(false) + } + + const onVerifyEmail = (e?: React.MouseEvent) => { + e?.preventDefault?.() + if (errorInput || isShowVerify || !inputEmail) return + setIsShowVerify(true) + } + + useAutoSignIn({ method: LoginMethod.EMAIL, onClick: onVerifyEmail, flowStatus }) + + const onVerifyCode = async (data: { code: string; email: string }) => { + const resp = await KyberOauth2.oauthUi.loginEmail(data) + console.debug('oauth resp login email', resp) + } + + const onSendCode = async ({ email }: { email: string }) => { + return KyberOauth2.oauthUi.sendVerifyCode({ + email, + flow: queryStringToObject(window.location.search).flow + '', + csrf: flowStatus.csrf, + }) + } + + return ( + + + + Sign-In with Email + + + ) +} + +export default EmailLoginForm diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index 0d287d748d..dd1384333f 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -1,15 +1,13 @@ import { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' -import React from 'react' -import { isMobile } from 'react-device-detect' +import React, { Fragment, useMemo } from 'react' import { Flex } from 'rebass' import styled from 'styled-components' -import useParsedQueryString from 'hooks/useParsedQueryString' import useTheme from 'hooks/useTheme' import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' import ButtonGoogle from 'pages/Oauth/AuthForm/ButtonGoogle' +import EmailLoginForm from 'pages/Oauth/AuthForm/EmailLoginForm' import { FlowStatus } from 'pages/Oauth/Login' -import { validateRedirectURL } from 'utils/redirect' import { getSupportLoginMethods } from '../helpers' import AuthFormFieldMessage from './AuthFormMessage' @@ -19,6 +17,8 @@ const Form = styled.form` flex-direction: column; align-items: center; gap: 14px; + width: 340px; + max-width: 90vw; ` interface AuthFormProps extends React.FormHTMLAttributes { @@ -30,38 +30,52 @@ interface AuthFormProps extends React.FormHTMLAttributes { const Splash = () =>
-const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStatus, disableEth }) => { - const { back_uri } = useParsedQueryString<{ back_uri: string }>() +export const OrDivider = () => { const theme = useTheme() - if (!formConfig) return null + return ( + + or + + ) +} - const { autoLoginMethod, processingSignIn } = flowStatus - const { ui } = formConfig - const loginMethods = getSupportLoginMethods(formConfig) +const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStatus, disableEth }) => { + const { processingSignIn } = flowStatus - const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE - const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) - const showBtnCancel = !isMobile && !hasGoogle && validateRedirectURL(back_uri) && !processingSignIn - const hasBothEthAndGoogle = hasGoogle && showEth - return ( -
- - {showEth && ( + const nodes = useMemo(() => { + const loginMethods = getSupportLoginMethods(formConfig) + const hasEth = loginMethods.includes(LoginMethod.ETH) + const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) + const hasEmail = loginMethods.includes(LoginMethod.EMAIL) + + const nodes = [] + if (hasEmail) nodes.push() + if (hasEth) + nodes.push( - )} - {hasBothEthAndGoogle && ( - - or - - )} - {hasGoogle && } + primary={!hasEmail} + />, + ) + if (hasGoogle) nodes.push() + return nodes + }, [disableEth, flowStatus, formConfig, processingSignIn, signInWithEth]) + + if (!formConfig) return null + const { ui } = formConfig + + return ( + + + {nodes.map((el, i) => ( + + {el} + {i !== nodes.length - 1 && } + + ))} ) } diff --git a/src/pages/Oauth/AuthForm/useAutoSignIn.tsx b/src/pages/Oauth/AuthForm/useAutoSignIn.tsx index 25c825ab2e..31b59e2514 100644 --- a/src/pages/Oauth/AuthForm/useAutoSignIn.tsx +++ b/src/pages/Oauth/AuthForm/useAutoSignIn.tsx @@ -1,6 +1,7 @@ import { LoginMethod } from '@kybernetwork/oauth2' import { useEffect, useRef } from 'react' +import { useActiveWeb3React } from 'hooks' import { useEagerConnect } from 'hooks/web3/useEagerConnect' import { FlowStatus } from 'pages/Oauth/Login' @@ -14,14 +15,18 @@ const useAutoSignIn = ({ flowStatus: FlowStatus }) => { const autoSelect = useRef(false) + const { account } = useActiveWeb3React() const { current: triedEager } = useEagerConnect() useEffect(() => { if (autoSelect.current || !flowReady || autoLoginMethod !== method) return - if ((triedEager && autoLoginMethod === LoginMethod.ETH) || autoLoginMethod === LoginMethod.GOOGLE) { + if ( + (triedEager && autoLoginMethod === LoginMethod.ETH && account) || + [LoginMethod.GOOGLE, LoginMethod.EMAIL].includes(autoLoginMethod) + ) { autoSelect.current = true - onClick() + onClick?.() } - }, [flowReady, autoLoginMethod, onClick, triedEager, method]) + }, [flowReady, autoLoginMethod, onClick, triedEager, method, account]) } export default useAutoSignIn diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 4b0a288308..5e9809b978 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -1,5 +1,5 @@ import KyberOauth2, { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' -import { Trans } from '@lingui/macro' +import { t } from '@lingui/macro' import { useCallback, useEffect, useRef, useState } from 'react' import Loader from 'components/Loader' @@ -14,28 +14,28 @@ import { queryStringToObject } from 'utils/string' import { formatSignature } from 'utils/transaction' import AuthForm from './AuthForm' -import { createSignMessage, getSupportLoginMethods } from './helpers' +import { canAutoSignInEth, createSignMessage, extractAutoLoginMethod, getSupportLoginMethods } from './helpers' -const getErrorMsg = (error: any) => { +export const getIamErrorMsg = (error: any) => { const data = error?.response?.data const isExpired = data?.error?.id === 'self_service_flow_expired' - if (isExpired) { - return ( - - Time to sign-in is Expired, please go back and try again. - - ) - } + if (isExpired) return t`Time to sign-in is Expired, please go back and try again.` - return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' + const message = data?.ui?.messages?.[0] + if (message?.id === 4000001) return t`Verification code is wrong or expired. Please try again.` + return message?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' } export type FlowStatus = { processingSignIn: boolean flowReady: boolean + csrf: string autoLoginMethod: LoginMethod | undefined // not waiting for click btn } +const getCsrfToken = (loginFlow: LoginFlow | undefined) => + loginFlow?.ui?.nodes?.find(e => e.attributes.name === 'csrf_token')?.attributes?.value ?? '' + export function Login() { const { account, chainId } = useActiveWeb3React() const { library: provider } = useWeb3React() @@ -46,18 +46,15 @@ export function Login() { flowReady: false, autoLoginMethod: undefined, processingSignIn: false, + csrf: '', }) const { wallet_address } = useParsedQueryString<{ wallet_address: string }>() const loginMethods = getSupportLoginMethods(authFormConfig) - const isSignInEth = loginMethods.includes(LoginMethod.ETH) + const showMsgSignInEth = account && canAutoSignInEth(loginMethods) const isMismatchEthAddress = - !loginMethods.includes(LoginMethod.GOOGLE) && - isSignInEth && - wallet_address && - account && - wallet_address?.toLowerCase() !== account?.toLowerCase() + showMsgSignInEth && wallet_address && wallet_address?.toLowerCase() !== account?.toLowerCase() const connectingWallet = useRef(false) @@ -68,9 +65,8 @@ export function Login() { return } setFlowStatus(v => ({ ...v, processingSignIn: true })) - const { ui, challenge, issued_at } = authFormConfig + const { challenge, issued_at } = authFormConfig connectingWallet.current = true - const csrf = ui.nodes.find(e => e.attributes.name === 'csrf_token')?.attributes?.value ?? '' const message = createSignMessage({ address: account, chainId, @@ -83,7 +79,7 @@ export function Login() { const resp = await KyberOauth2.oauthUi.loginEthereum({ address: account, signature: formatSignature(signature), - csrf, + csrf: getCsrfToken(authFormConfig), chainId, }) @@ -93,7 +89,7 @@ export function Login() { } } catch (error: any) { if (!didUserReject(error)) { - setError(getErrorMsg(error)) + setError(getIamErrorMsg(error)) } console.error('signInWithEthereum err', error) connectingWallet.current = false @@ -110,26 +106,17 @@ export function Login() { setAuthFormConfig(loginFlow) const { client_id } = loginFlow.oauth_client - const loginMethods = getSupportLoginMethods(loginFlow) - - let autoLoginMethod: LoginMethod | undefined - const isIncludeGoogle = loginMethods.includes(LoginMethod.GOOGLE) - if (loginMethods.length === 1) { - if (loginMethods.includes(LoginMethod.ANONYMOUS)) { - throw new Error('Not found login method for this app') - } - if (isIncludeGoogle) { - autoLoginMethod = LoginMethod.GOOGLE - } - } - if (loginMethods.includes(LoginMethod.ETH) && !isIncludeGoogle) { - autoLoginMethod = LoginMethod.ETH - } KyberOauth2.initialize({ clientId: client_id, mode: ENV_KEY }) - setFlowStatus(v => ({ ...v, flowReady: true, autoLoginMethod })) + + setFlowStatus(v => ({ + ...v, + flowReady: true, + autoLoginMethod: extractAutoLoginMethod(loginFlow), + csrf: getCsrfToken(loginFlow), + })) } catch (error: any) { const { error_description } = queryStringToObject(window.location.search) - setError(error_description || getErrorMsg(error)) + setError(error_description || getIamErrorMsg(error)) } } getFlowLogin() @@ -166,7 +153,7 @@ export function Login() { Checking data ... - ) : isSignInEth && account ? ( + ) : showMsgSignInEth ? ( renderEthMsg() ) : ( appName && Please sign in to continue with {appName} diff --git a/src/pages/Oauth/helpers.ts b/src/pages/Oauth/helpers.ts index 125dffee13..a84e4c8076 100644 --- a/src/pages/Oauth/helpers.ts +++ b/src/pages/Oauth/helpers.ts @@ -1,9 +1,44 @@ -import { LoginFlow } from '@kybernetwork/oauth2' +import { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' + +import { isInEnum, queryStringToObject } from 'utils/string' export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { return loginFlow?.oauth_client?.metadata?.allowed_login_methods ?? [] } +export const canAutoSignInEth = (loginMethods: LoginMethod[]) => { + const isIncludeEth = loginMethods.includes(LoginMethod.ETH) + const totalMethod = loginMethods.length + return ( + (isIncludeEth && totalMethod === 1) || + (isIncludeEth && totalMethod === 2 && loginMethods.includes(LoginMethod.ANONYMOUS)) + ) +} + +export const extractAutoLoginMethod = (loginFlow: LoginFlow) => { + const loginMethods = getSupportLoginMethods(loginFlow) + let autoLoginMethod: LoginMethod | undefined + + if (loginMethods.length === 1) { + if (loginMethods.includes(LoginMethod.ANONYMOUS)) { + throw new Error('Not found login method for this app') + } + if (loginMethods.includes(LoginMethod.GOOGLE)) { + autoLoginMethod = LoginMethod.GOOGLE + } + } + if (canAutoSignInEth(loginMethods)) { + autoLoginMethod = LoginMethod.ETH + } + + // auto login method from url + const { type } = queryStringToObject(window.location.search) + if (!autoLoginMethod && isInEnum(type + '', LoginMethod) && loginMethods.includes(type)) { + autoLoginMethod = type as LoginMethod + } + return autoLoginMethod +} + type MessageParams = { domain: string uri: string diff --git a/src/pages/TrueSightV2/pages/RegisterWhitelist/SignInForm.tsx b/src/pages/TrueSightV2/pages/RegisterWhitelist/SignInForm.tsx new file mode 100644 index 0000000000..ec5fd06aff --- /dev/null +++ b/src/pages/TrueSightV2/pages/RegisterWhitelist/SignInForm.tsx @@ -0,0 +1,58 @@ +import { LoginMethod } from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' +import { Text } from 'rebass' +import styled from 'styled-components' + +import { ButtonLight, ButtonPrimary } from 'components/Button' +import Column from 'components/Column' +import DownloadWalletModal from 'components/DownloadWalletModal' +import useLogin from 'hooks/useLogin' +import useTheme from 'hooks/useTheme' +import { useValidateEmail } from 'pages/NotificationCenter/NotificationPreference' +import { InputEmail } from 'pages/NotificationCenter/NotificationPreference/InputEmail' +import { OrDivider } from 'pages/Oauth/AuthForm' +import { ApplicationModal } from 'state/application/actions' +import { useOpenModal } from 'state/application/hooks' + +const Wrapper = styled(Column)` + width: 340px; + gap: 16px; + align-items: center; +` + +export default function SignInForm() { + const { signIn } = useLogin() + const theme = useTheme() + const openDownloadWalletModal = useOpenModal(ApplicationModal.DOWNLOAD_WALLET) + const { inputEmail, errorInput, onChangeEmail } = useValidateEmail('') + return ( + + + inputEmail && !errorInput && signIn({ loginMethod: LoginMethod.EMAIL, account: inputEmail })} + height={'36px'} + > + Sign-In + + + signIn()} height={'36px'}> + Sign-In with Wallet + + + + Don't have a wallet?{' '} + + Get started here + + + + + + ) +} diff --git a/src/pages/TrueSightV2/pages/RegisterWhitelist/SubscribeForm.tsx b/src/pages/TrueSightV2/pages/RegisterWhitelist/SubscribeForm.tsx index fcacfc53b5..999ed7f715 100644 --- a/src/pages/TrueSightV2/pages/RegisterWhitelist/SubscribeForm.tsx +++ b/src/pages/TrueSightV2/pages/RegisterWhitelist/SubscribeForm.tsx @@ -21,13 +21,13 @@ export default function EmailForm({ }: { showVerify: (email: string, code: string, showSuccess: boolean) => void }) { + const { userInfo } = useSessionInfo() const { mixpanelHandler } = useMixpanel() - const [inputEmail, setInputEmail] = useState('') + const [inputEmail, setInputEmail] = useState(userInfo?.email || '') const qs = useParsedQueryString<{ referrer: string }>() const [referredByCode, setCode] = useState(qs.referrer || '') const [errorInput, setErrorInput] = useState({ email: '', referredByCode: '' }) - const { userInfo } = useSessionInfo() const [requestWaitList] = useRequestWhiteListMutation() const [checkReferalCode] = useLazyCheckReferralCodeQuery() diff --git a/src/pages/TrueSightV2/pages/RegisterWhitelist/index.tsx b/src/pages/TrueSightV2/pages/RegisterWhitelist/index.tsx index 28d4245171..3f3942e91d 100644 --- a/src/pages/TrueSightV2/pages/RegisterWhitelist/index.tsx +++ b/src/pages/TrueSightV2/pages/RegisterWhitelist/index.tsx @@ -6,21 +6,15 @@ import { useRequestWhiteListMutation } from 'services/kyberAISubscription' import styled from 'styled-components' import { ButtonLight, ButtonPrimary } from 'components/Button' -import Column from 'components/Column' -import DownloadWalletModal from 'components/DownloadWalletModal' -import Row from 'components/Row' import { APP_PATHS } from 'constants/index' -import useLogin from 'hooks/useLogin' import { MIXPANEL_TYPE, useMixpanelKyberAI } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' +import SignInForm from 'pages/TrueSightV2/pages/RegisterWhitelist/SignInForm' import SubscribeForm from 'pages/TrueSightV2/pages/RegisterWhitelist/SubscribeForm' import WaitListForm from 'pages/TrueSightV2/pages/RegisterWhitelist/WaitListForm' import VerifyCodeModal from 'pages/Verify/VerifyCodeModal' -import { ApplicationModal } from 'state/application/actions' -import { useOpenModal } from 'state/application/hooks' import { useSessionInfo } from 'state/authen/hooks' import { useIsWhiteListKyberAI } from 'state/user/hooks' -import { ButtonText } from 'theme' const ConnectWalletButton = styled(ButtonLight)` height: 36px; @@ -32,7 +26,6 @@ export default function RegisterWhitelist({ showForm = true }: { showForm?: bool const theme = useTheme() const mixpanelHandler = useMixpanelKyberAI() const { isLogin } = useSessionInfo() - const { signIn } = useLogin() const { isWhiteList, isWaitList, loading: isCheckingPermission } = useIsWhiteListKyberAI() @@ -42,7 +35,6 @@ export default function RegisterWhitelist({ showForm = true }: { showForm?: bool referredByCode: '', showVerifySuccess: false, }) - const openDownloadWalletModal = useOpenModal(ApplicationModal.DOWNLOAD_WALLET) const showVerify = (email: string, referredByCode: string, showVerifySuccess: boolean) => { setVerifyModalState({ isOpen: true, referredByCode, email, showVerifySuccess }) @@ -96,21 +88,7 @@ export default function RegisterWhitelist({ showForm = true }: { showForm?: bool ) - if (!isLogin) - return ( - - signIn()}> - Sign-In to Continue - - - Don't have a wallet? - - Get started here - - - - - ) + if (!isLogin) return const btnGetStart = ( Promise) | (() => void) isOpen: boolean @@ -84,6 +89,10 @@ export default function VerifyCodeModal({ showVerifySuccess?: boolean verifySuccessTitle?: string verifySuccessContent?: ReactNode + sendCodeFn?: (data: { email: string }) => Promise + verifyCodeFn?: (data: { email: string; code: string }) => Promise + getErrorMsgFn?: (err: any) => string + refreshProfile?: boolean }) { const theme = useTheme() const [otp, setOtp] = useState('') @@ -95,10 +104,12 @@ export default function VerifyCodeModal({ const [isTypingIos, setIsTypingIos] = useState(false) const isTypingAndroid = useMedia(`(max-height: 450px)`) - const [expiredDuration, setExpireDuration] = useState(defaultTime) + const [expiredDuration, setExpireDuration] = useState(0) + const isSendMailError = error === ErrorType.SEND_EMAIL_ERROR const isVerifyMailError = error === ErrorType.VALIDATE_ERROR const isRateLimitError = error === ErrorType.RATE_LIMIT + const canShowResend = !isSendMailError && !isRateLimitError && expiredDuration < (timeExpire - 1) * TIMES_IN_SECS.ONE_MIN @@ -123,20 +134,19 @@ export default function VerifyCodeModal({ [notify], ) - const sendEmail = useCallback(() => { + const sendEmail = useCallback(async () => { interval.current && clearInterval(interval.current) if (!email) return - sendOtp({ email }) - .unwrap() - .then(() => { - setExpireDuration(defaultTime) - setError(undefined) - }) - .catch(data => { - setExpireDuration(0) - setError(!data?.status ? ErrorType.RATE_LIMIT : ErrorType.SEND_EMAIL_ERROR) - }) - }, [email, sendOtp]) + try { + const promise = sendCodeFn ? sendCodeFn({ email }) : sendOtp({ email }).unwrap() + await promise + setExpireDuration(defaultTime) + setError(undefined) + } catch (error) { + setExpireDuration(0) + setError(!error?.status ? ErrorType.RATE_LIMIT : ErrorType.SEND_EMAIL_ERROR) + } + }, [email, sendOtp, sendCodeFn]) const checkedRegisterStatus = useRef(false) // prevent spam const sendEmailWhenInit = useCallback(() => { @@ -158,26 +168,33 @@ export default function VerifyCodeModal({ } }, [isOpen, showNotiSuccess, showVerifySuccess, sendEmailWhenInit]) - const refreshProfile = useRefreshProfile() + const refreshProfileInfo = useRefreshProfile() + const [verifying, setIsVerifying] = useState(false) const verify = async () => { + if (!email || verifying) return try { - if (!email) return - await verifyOtp({ code: otp, email }).unwrap() - await onVerifySuccess?.() - await refreshProfile() - showNotiSuccess() + setIsVerifying(true) + const promise = verifyCodeFn ? verifyCodeFn({ code: otp, email }) : verifyOtp({ code: otp, email }).unwrap() + await promise + await (onVerifySuccess ? onVerifySuccess() : onDismiss()) + if (refreshProfile) { + await refreshProfileInfo() + showNotiSuccess() + } } catch (error) { setError(ErrorType.VALIDATE_ERROR) notify({ title: t`Error`, - summary: getErrorMessage(error), + summary: getErrorMsgFn?.(error) || getErrorMessage(error), type: NotificationType.ERROR, }) + } finally { + setIsVerifying(false) } } - const onChange = (value: string) => { + const onChangeOTP = (value: string) => { isVerifyMailError && setError(undefined) setOtp(value) } @@ -244,7 +261,7 @@ export default function VerifyCodeModal({ ( Resend
) : ( - - Verify + + {verifying ? ( + + Verifying + + ) : ( + Verify + )} )} diff --git a/src/state/authen/reducer.ts b/src/state/authen/reducer.ts index 74640b4899..78cc7a32d8 100644 --- a/src/state/authen/reducer.ts +++ b/src/state/authen/reducer.ts @@ -6,7 +6,7 @@ export type UserProfile = { telegramUsername: string nickname: string avatarUrl: string - data: { hasAccessToKyberAI: boolean; favouriteChainIds?: string[] } + data?: { hasAccessToKyberAI: boolean; favouriteChainIds?: string[] } } export type ConfirmProfile = { showModal: boolean diff --git a/src/state/profile/hooks.ts b/src/state/profile/hooks.ts index 6afdb1c52d..3912058357 100644 --- a/src/state/profile/hooks.ts +++ b/src/state/profile/hooks.ts @@ -11,6 +11,7 @@ import { UserProfile, authenActions } from 'state/authen/reducer' import { useAppDispatch } from 'state/hooks' import { CacheProfile, ProfileMap, SignedAccountParams, profileActions } from 'state/profile/reducer' import getShortenAddress from 'utils/getShortenAddress' +import { isEmailValid } from 'utils/string' const { setImportToken, setKeepCurrentProfile, setProfileMap, updateSignedAccount } = profileActions @@ -68,10 +69,10 @@ export function useSaveConnectedProfile() { export type ConnectedProfile = { active: boolean - address: string - profile: UserProfile | undefined - guest: boolean + name: string id: string + profile: UserProfile | undefined + type: LoginMethod default?: boolean } @@ -130,20 +131,20 @@ export const useProfileInfo = (): { const profile = userInfo || getCacheProfile(signedAccount ? signedAccount : KEY_GUEST_DEFAULT, isSigInGuest) const profiles = useMemo(() => { - const getAccountGuest = (account: string) => ({ - address: account === KEY_GUEST_DEFAULT ? t`Guest` : t`Imported Guest`, + const getAccountGuest = (account: string): ConnectedProfile => ({ + name: account === KEY_GUEST_DEFAULT ? t`Guest` : t`Imported Guest`, active: account === signedAccount?.toLowerCase(), id: account, profile: getCacheProfile(account, true), - guest: true, + type: LoginMethod.ANONYMOUS, default: account === KEY_GUEST_DEFAULT, }) - const getAccountSignIn = (account: string) => ({ + const getAccountSignIn = (account: string): ConnectedProfile => ({ active: account === signedAccount?.toLowerCase(), - address: account, + name: account, id: account, profile: getCacheProfile(account, false), - guest: false, + type: isEmailValid(account) ? LoginMethod.EMAIL : LoginMethod.ETH, }) const results = Object.keys(profileInfo?.wallet ?? {}) @@ -170,7 +171,7 @@ export const useProfileInfo = (): { [saveCacheProfile], ) - const totalGuest = profiles.reduce((total, cur) => total + (cur.guest ? 1 : 0), 0) + const totalGuest = profiles.reduce((total, cur) => total + (cur.type === LoginMethod.ANONYMOUS ? 1 : 0), 0) return { profiles, totalGuest, profile, removeProfile, removeAllProfile, getCacheProfile, saveCacheProfile } } @@ -178,12 +179,12 @@ export const useProfileInfo = (): { export const useSignedAccountInfo = () => { const { isEVM } = useActiveWeb3React() const signedAccount = useSelector((state: AppState) => state.profile.signedAccount) - const signedMethod = useSelector((state: AppState) => state.profile.signedMethod) + const signedMethod = useSelector((state: AppState) => state.profile.signedMethod) as LoginMethod const { account } = useActiveWeb3React() const isSigInGuest = signedMethod === LoginMethod.ANONYMOUS - const isSignInEmail = signedMethod === LoginMethod.GOOGLE + const isSignInEmail = signedMethod === LoginMethod.EMAIL || signedMethod === LoginMethod.GOOGLE const isSignInEth = signedMethod === LoginMethod.ETH const isSignInDifferentWallet = @@ -196,7 +197,6 @@ export const useSignedAccountInfo = () => { isSignInDifferentWallet, isSigInGuest, isSignInGuestDefault, - isSignInEmail, isSignInEth, } } diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index d9d80fbb9a..456cc28251 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -502,13 +502,13 @@ export const useIsWhiteListKyberAI = () => { userInfo && getParticipantInfoQuery() }, [getParticipantInfoQuery, userInfo]) - const { account } = useActiveWeb3React() const [connectingWallet] = useIsConnectingWallet() const isLoading = isFetching || pendingAuthentication const loadingDebounced = useDebounce(isLoading, 500) || connectingWallet - const participantInfo = isError || loadingDebounced || !account ? participantDefault : rawData + const participantInfo = isError || loadingDebounced ? participantDefault : rawData + return { loading: loadingDebounced, isWhiteList: diff --git a/yarn.lock b/yarn.lock index 35100ef88a..8f867837fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2257,10 +2257,10 @@ bs58 "^5.0.0" uuid "^8.3.2" -"@kybernetwork/oauth2@1.0.0": - version "1.0.0" - resolved "https://npm.pkg.github.com/download/@kybernetwork/oauth2/1.0.0/dbb42e967c8a6755bb801b2c528064764534d00e#dbb42e967c8a6755bb801b2c528064764534d00e" - integrity sha512-fIv7W+s+PIK8xxm+aic4GjtrKZsv8TRuqNfa6CeDGOW16oxhZlSARHVJy4rWfGxAVT2+0GRuXXR2uMSwAdfczw== +"@kybernetwork/oauth2@1.0.1": + version "1.0.1" + resolved "https://npm.pkg.github.com/download/@kybernetwork/oauth2/1.0.1/f78ca8ea7fd20290d54aeb191fc60d41ce73bed4#f78ca8ea7fd20290d54aeb191fc60d41ce73bed4" + integrity sha512-OiuFv7L5oHJVtsupRYBd06Ss9e/01RAA/PQPnKyDn9rcDmjN52OucdKMALh/5HBcEMyVnNkTeoddubEaLTIJhA== dependencies: axios "1.2.1" client-oauth2 "^4.3.3" From e700d7b0b3ca78e67b724e0398c0716fc6346757 Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Thu, 26 Oct 2023 10:35:41 +0700 Subject: [PATCH 03/26] feat: add red2 (#2295) --- index.html | 408 +-- public/libs/red2.js | 6267 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 6491 insertions(+), 184 deletions(-) create mode 100644 public/libs/red2.js diff --git a/index.html b/index.html index 747986cc54..375040fd08 100644 --- a/index.html +++ b/index.html @@ -1,62 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - KyberSwap - Trading Smart - - - - + + + - - - - - -
- - - -
-
- -