diff --git a/web/account/log-in-form.css b/web/account/log-in-form.css index 2cb4b0854..9aa1dbe83 100644 --- a/web/account/log-in-form.css +++ b/web/account/log-in-form.css @@ -1,74 +1,42 @@ div.modal_body { display: flex; flex-direction: column; padding: 20px 40px; border-radius: 16px; background-color: var(--modal-bg); box-shadow: 0 0 40px rgba(126, 87, 194, 0.5); } div.form_title { padding: 6px 6px 0 0; font-size: var(--s-font-14); font-weight: var(--bold); color: var(--fg); } div.form_content { margin-top: 4px; margin-bottom: 8px; font-family: var(--font-stack); color: var(--fg); } div.form_footer { display: flex; flex-direction: column; margin-top: 16px; } div.form_footer > button:disabled { min-height: 48px; opacity: 0.5; } div.modal_form_error { margin-top: 16px; font-size: 14px; color: var(--error); font-style: italic; padding-left: 6px; align-self: center; } - -div.ethereum_logo_container { - display: flex; - justify-content: center; - align-content: center; - margin: 0 4px; -} - -hr { - margin: 20px 0; - height: 0; - overflow: visible; - text-align: center; - border: none; - border-top: #ffffff33 solid 1px; -} - -hr:after { - position: relative; - top: -12px; - padding: 0 8px; - color: white; - content: 'or'; - background-color: #1f1f1fff; -} - -div.connectButtonContainer { - width: 100%; - display: flex; - justify-content: center; - align-content: center; -} diff --git a/web/account/log-in-form.react.js b/web/account/log-in-form.react.js index 529733ce2..46e3fd8c3 100644 --- a/web/account/log-in-form.react.js +++ b/web/account/log-in-form.react.js @@ -1,315 +1,179 @@ // @flow -import '@rainbow-me/rainbowkit/dist/index.css'; - -import { - ConnectButton, - darkTheme, - getDefaultWallets, - RainbowKitProvider, - useConnectModal, -} from '@rainbow-me/rainbowkit'; import invariant from 'invariant'; -import _merge from 'lodash/fp/merge'; import * as React from 'react'; -import { FaEthereum } from 'react-icons/fa'; -import { - chain, - configureChains, - createClient, - WagmiConfig, - useSigner, -} from 'wagmi'; -import { alchemyProvider } from 'wagmi/providers/alchemy'; -import { publicProvider } from 'wagmi/providers/public'; -import { - getSIWENonce, - getSIWENonceActionTypes, -} from 'lib/actions/siwe-actions.js'; import { logInActionTypes, logIn } from 'lib/actions/user-actions'; import { useModalContext } from 'lib/components/modal-provider.react'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { oldValidUsernameRegex, validEmailRegex, } from 'lib/shared/account-utils'; import { type LogInExtraInfo, type LogInStartingPayload, logInActionSources, } from 'lib/types/account-types'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils'; -import { isDev } from 'lib/utils/dev-utils.js'; +import { isDev } from 'lib/utils/dev-utils'; import Button from '../components/button.react'; import LoadingIndicator from '../loading-indicator.react'; import Input from '../modals/input.react'; import { useSelector } from '../redux/redux-utils'; import { webLogInExtraInfoSelector } from '../selectors/account-selectors'; import css from './log-in-form.css'; import PasswordInput from './password-input.react'; +import SIWE from './siwe.react.js'; -// details can be found https://0.6.x.wagmi.sh/docs/providers/configuring-chains -const availableProviders = process.env.COMM_ALCHEMY_KEY - ? [alchemyProvider({ apiKey: process.env.COMM_ALCHEMY_KEY })] - : [publicProvider()]; -const { chains, provider } = configureChains( - [chain.mainnet], - availableProviders, -); - -const { connectors } = getDefaultWallets({ - appName: 'comm', - chains, -}); - -const wagmiClient = createClient({ - autoConnect: true, - connectors, - provider, -}); - -const getSIWENonceLoadingStatusSelector = createLoadingStatusSelector( - getSIWENonceActionTypes, -); const loadingStatusSelector = createLoadingStatusSelector(logInActionTypes); function LoginForm(): React.Node { - const { openConnectModal } = useConnectModal(); - const { data: signer } = useSigner(); const inputDisabled = useSelector(loadingStatusSelector) === 'loading'; const loginExtraInfo = useSelector(webLogInExtraInfoSelector); const callLogIn = useServerCall(logIn); - const getSIWENonceCall = useServerCall(getSIWENonce); - const getSIWENonceCallLoadingStatus = useSelector( - getSIWENonceLoadingStatusSelector, - ); - const dispatchActionPromise = useDispatchActionPromise(); const modalContext = useModalContext(); const [username, setUsername] = React.useState(''); const [password, setPassword] = React.useState(''); const [errorMessage, setErrorMessage] = React.useState(''); - const [siweNonce, setSIWENonce] = React.useState(null); const usernameInputRef = React.useRef(); React.useEffect(() => { usernameInputRef.current?.focus(); }, []); - React.useEffect(() => { - if (!signer || !isDev) { - setSIWENonce(null); - return; - } - dispatchActionPromise( - getSIWENonceActionTypes, - (async () => { - const response = await getSIWENonceCall(); - setSIWENonce(response); - })(), - ); - }, [dispatchActionPromise, getSIWENonceCall, signer]); - const onUsernameChange = React.useCallback(e => { invariant(e.target instanceof HTMLInputElement, 'target not input'); setUsername(e.target.value); }, []); const onUsernameBlur = React.useCallback(() => { setUsername(untrimmedUsername => untrimmedUsername.trim()); }, []); const onPasswordChange = React.useCallback(e => { invariant(e.target instanceof HTMLInputElement, 'target not input'); setPassword(e.target.value); }, []); const logInAction = React.useCallback( async (extraInfo: LogInExtraInfo) => { try { const result = await callLogIn({ ...extraInfo, username, password, logInActionSource: logInActionSources.logInFromWebForm, }); modalContext.popModal(); return result; } catch (e) { setUsername(''); setPassword(''); if (e.message === 'invalid_credentials') { setErrorMessage('incorrect username or password'); } else { setErrorMessage('unknown error'); } usernameInputRef.current?.focus(); throw e; } }, [callLogIn, modalContext, password, username], ); const onSubmit = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); if (username.search(validEmailRegex) > -1) { setUsername(''); setErrorMessage('usernames only, not emails'); usernameInputRef.current?.focus(); return; } else if (username.search(oldValidUsernameRegex) === -1) { setUsername(''); setErrorMessage('alphanumeric usernames only'); usernameInputRef.current?.focus(); return; } else if (password === '') { setErrorMessage('password is empty'); usernameInputRef.current?.focus(); return; } const extraInfo = loginExtraInfo(); dispatchActionPromise( logInActionTypes, logInAction(extraInfo), undefined, ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload), ); }, [dispatchActionPromise, logInAction, loginExtraInfo, username, password], ); const loginButtonContent = React.useMemo(() => { if (inputDisabled) { return ; } return 'Log in'; }, [inputDisabled]); - const siweButtonColor = React.useMemo( - () => ({ backgroundColor: 'white', color: 'black' }), - [], - ); - - let siweSeparator; + let siwe; if (isDev) { - siweSeparator =
; - } - - let siweConnectButton; - if (isDev && signer && !siweNonce) { - siweConnectButton = ( -
- -
- ); - } else if (isDev && signer) { - siweConnectButton = ( -
- -
- ); - } - - const onSIWEButtonClick = React.useCallback(() => { - openConnectModal && openConnectModal(); - }, [openConnectModal]); - - let siweButton; - if (isDev && openConnectModal) { - siweButton = ( - <> - - - ); + siwe = ; } return (
Username
Password
- {siweSeparator} - {siweConnectButton} - {siweButton} + {siwe}
{errorMessage}
); } -function LoginFormWrapper(): React.Node { - const theme = React.useMemo(() => { - return _merge(darkTheme())({ - radii: { - modal: 0, - modalMobile: 0, - }, - colors: { - modalBackdrop: '#242529', - }, - }); - }, []); - return ( - - - - - - ); -} - -export default LoginFormWrapper; +export default LoginForm; diff --git a/web/account/siwe.css b/web/account/siwe.css new file mode 100644 index 000000000..8f3d1210e --- /dev/null +++ b/web/account/siwe.css @@ -0,0 +1,36 @@ +div.ethereumLogoContainer { + display: flex; + justify-content: center; + align-content: center; + margin: 0 4px; +} + +hr { + margin: 20px 0; + height: 0; + overflow: visible; + text-align: center; + border: none; + border-top: #ffffff33 solid 1px; +} + +hr:after { + position: relative; + top: -12px; + padding: 0 8px; + color: white; + content: 'or'; + background-color: #1f1f1fff; +} + +div.connectButtonContainer { + width: 100%; + display: flex; + justify-content: center; + align-content: center; +} + +div.siweContainer { + display: flex; + flex-direction: column; +} diff --git a/web/account/siwe.react.js b/web/account/siwe.react.js new file mode 100644 index 000000000..fef02e27e --- /dev/null +++ b/web/account/siwe.react.js @@ -0,0 +1,162 @@ +// @flow + +import '@rainbow-me/rainbowkit/dist/index.css'; + +import { + ConnectButton, + darkTheme, + getDefaultWallets, + RainbowKitProvider, + useConnectModal, +} from '@rainbow-me/rainbowkit'; +import _merge from 'lodash/fp/merge'; +import * as React from 'react'; +import { FaEthereum } from 'react-icons/fa'; +import { + chain, + configureChains, + createClient, + useSigner, + WagmiConfig, +} from 'wagmi'; +import { alchemyProvider } from 'wagmi/providers/alchemy'; +import { publicProvider } from 'wagmi/providers/public'; + +import { + getSIWENonce, + getSIWENonceActionTypes, +} from 'lib/actions/siwe-actions'; +import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; +import { + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils'; + +import Button from '../components/button.react'; +import LoadingIndicator from '../loading-indicator.react'; +import { useSelector } from '../redux/redux-utils'; +import css from './siwe.css'; + +// details can be found https://0.6.x.wagmi.sh/docs/providers/configuring-chains +const availableProviders = process.env.COMM_ALCHEMY_KEY + ? [alchemyProvider({ apiKey: process.env.COMM_ALCHEMY_KEY })] + : [publicProvider()]; +const { chains, provider } = configureChains( + [chain.mainnet], + availableProviders, +); + +const { connectors } = getDefaultWallets({ + appName: 'comm', + chains, +}); + +const wagmiClient = createClient({ + autoConnect: true, + connectors, + provider, +}); + +const getSIWENonceLoadingStatusSelector = createLoadingStatusSelector( + getSIWENonceActionTypes, +); +function SIWE(): React.Node { + const { openConnectModal } = useConnectModal(); + const { data: signer } = useSigner(); + const dispatchActionPromise = useDispatchActionPromise(); + const getSIWENonceCall = useServerCall(getSIWENonce); + const getSIWENonceCallLoadingStatus = useSelector( + getSIWENonceLoadingStatusSelector, + ); + + const [siweNonce, setSIWENonce] = React.useState(null); + + React.useEffect(() => { + if (!signer) { + setSIWENonce(null); + return; + } + dispatchActionPromise( + getSIWENonceActionTypes, + (async () => { + const response = await getSIWENonceCall(); + setSIWENonce(response); + })(), + ); + }, [dispatchActionPromise, getSIWENonceCall, signer]); + + const siweButtonColor = React.useMemo( + () => ({ backgroundColor: 'white', color: 'black' }), + [], + ); + + let siweConnectButton; + if (signer && !siweNonce) { + siweConnectButton = ( +
+ +
+ ); + } else if (signer) { + siweConnectButton = ( +
+ +
+ ); + } + + const onSIWEButtonClick = React.useCallback(() => { + openConnectModal && openConnectModal(); + }, [openConnectModal]); + + let siweButton; + if (openConnectModal) { + siweButton = ( + <> + + + ); + } + return ( +
+
+ {siweConnectButton} + {siweButton} +
+ ); +} + +function SIWEWrapper(): React.Node { + const theme = React.useMemo(() => { + return _merge(darkTheme())({ + radii: { + modal: 0, + modalMobile: 0, + }, + colors: { + modalBackdrop: '#242529', + }, + }); + }, []); + return ( + + + + + + ); +} + +export default SIWEWrapper;