From e2abc9e5a5e71da20c4268892b7e88dae20fb0ea Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 17 Sep 2024 16:09:48 +0200 Subject: [PATCH 01/49] simple profile button and auth modal layout --- ui/home/HeroBanner.tsx | 2 + ui/marketplace/MarketplaceAppTopBar.tsx | 2 + ui/snippets/auth/AuthModal.tsx | 84 +++++++++++++++++++ .../auth/screens/AuthModalScreenEmail.tsx | 25 ++++++ .../auth/screens/AuthModalScreenOtpCode.tsx | 36 ++++++++ .../screens/AuthModalScreenSelectMethod.tsx | 24 ++++++ .../AuthModalScreenSuccessCreatedEmail.tsx | 14 ++++ ui/snippets/auth/types.ts | 10 +++ ui/snippets/header/HeaderDesktop.tsx | 2 + .../horizontal/NavigationDesktop.tsx | 2 + ui/snippets/profile/ProfileButton.tsx | 31 +++++++ ui/snippets/profile/ProfileDesktop.tsx | 31 +++++++ 12 files changed, 263 insertions(+) create mode 100644 ui/snippets/auth/AuthModal.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenEmail.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx create mode 100644 ui/snippets/auth/types.ts create mode 100644 ui/snippets/profile/ProfileButton.tsx create mode 100644 ui/snippets/profile/ProfileDesktop.tsx diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index 9ad893a668..a992228489 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -3,6 +3,7 @@ import React from 'react'; import config from 'configs/app'; import AdBanner from 'ui/shared/ad/AdBanner'; +import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; @@ -56,6 +57,7 @@ const HeroBanner = () => { { config.features.account.isEnabled && } { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled && } ) } diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 8f9c233620..55b23cb697 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -12,6 +12,7 @@ import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; +import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; @@ -101,6 +102,7 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) { config.features.account.isEnabled && } { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled && } ) } diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx new file mode 100644 index 0000000000..4d4f6e7350 --- /dev/null +++ b/ui/snippets/auth/AuthModal.tsx @@ -0,0 +1,84 @@ +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from './types'; + +import IconSvg from 'ui/shared/IconSvg'; + +import AuthModalScreenEmail from './screens/AuthModalScreenEmail'; +import AuthModalScreenOtpCode from './screens/AuthModalScreenOtpCode'; +import AuthModalScreenSelectMethod from './screens/AuthModalScreenSelectMethod'; +import AuthModalScreenSuccessCreatedEmail from './screens/AuthModalScreenSuccessCreatedEmail'; + +interface Props { + initialScreen: Screen; + onClose: () => void; +} + +const AuthModal = ({ initialScreen, onClose }: Props) => { + const [ steps, setSteps ] = React.useState>([ initialScreen ]); + + const onNextStep = React.useCallback((screen: Screen) => { + setSteps((prev) => [ ...prev, screen ]); + }, []); + + const onPrevStep = React.useCallback(() => { + setSteps((prev) => prev.length > 1 ? prev.slice(0, -1) : prev); + }, []); + + const header = (() => { + const currentStep = steps[steps.length - 1]; + switch (currentStep.type) { + case 'select_method': + return 'Select a way to connect'; + case 'email': + return 'Continue with email'; + case 'otp_code': + return 'Confirmation code'; + case 'success_created_email': + return 'Congrats!'; + } + })(); + + const content = (() => { + const currentStep = steps[steps.length - 1]; + switch (currentStep.type) { + case 'select_method': + return ; + case 'email': + return ; + case 'otp_code': + return ; + case 'success_created_email': + return ; + } + })(); + + return ( + + + + + { steps.length > 1 && !steps[steps.length - 1].type.startsWith('success') && ( + + ) } + { header } + + + + { content } + + + + ); +}; + +export default React.memo(AuthModal); diff --git a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx new file mode 100644 index 0000000000..8d5c6a1df3 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx @@ -0,0 +1,25 @@ +import { Box, Button, Input, Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; + +interface Props { + onSubmit: (screen: Screen) => void; +} + +const AuthModalScreenEmail = ({ onSubmit }: Props) => { + + const handleSubmitClick = React.useCallback(() => { + onSubmit({ type: 'otp_code', email: 'tom@ohhhh.me' }); + }, [ onSubmit ]); + + return ( + + Account email, used for transaction notifications from your watchlist. + + + + ); +}; + +export default React.memo(AuthModalScreenEmail); diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx new file mode 100644 index 0000000000..898c3eb8de --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -0,0 +1,36 @@ +import { chakra, Box, Text, Input, Button, Link } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + email: string; + onSubmit: (screen: Screen) => void; +} + +const AuthModalScreenOtpCode = ({ email, onSubmit }: Props) => { + + const handleSubmitClick = React.useCallback(() => { + onSubmit({ type: 'success_created_email' }); + }, [ onSubmit ]); + + return ( + + + Please check{ ' ' } + { email }{ ' ' } + and enter your code below. + + + + + Resend code + + + + ); +}; + +export default React.memo(AuthModalScreenOtpCode); diff --git a/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx new file mode 100644 index 0000000000..596d735b5e --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx @@ -0,0 +1,24 @@ +import { Button, VStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; + +interface Props { + onSelectMethod: (screen: Screen) => void; +} + +const AuthModalScreenSelectMethod = ({ onSelectMethod }: Props) => { + + const handleEmailClick = React.useCallback(() => { + onSelectMethod({ type: 'email' }); + }, [ onSelectMethod ]); + + return ( + + + + + ); +}; + +export default React.memo(AuthModalScreenSelectMethod); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx new file mode 100644 index 0000000000..cf6de510cc --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx @@ -0,0 +1,14 @@ +import { Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; + +const AuthModalScreenSuccessCreatedEmail = () => { + return ( + + Your account was successfully created! + Connect a web3 wallet to safely interact with smart contracts and dapps inside Blockscout. + + + ); +}; + +export default React.memo(AuthModalScreenSuccessCreatedEmail); diff --git a/ui/snippets/auth/types.ts b/ui/snippets/auth/types.ts new file mode 100644 index 0000000000..bde9e4b76d --- /dev/null +++ b/ui/snippets/auth/types.ts @@ -0,0 +1,10 @@ +export type Screen = { + type: 'select_method'; +} | { + type: 'email'; +} | { + type: 'otp_code'; + email: string; +} | { + type: 'success_created_email'; +} diff --git a/ui/snippets/header/HeaderDesktop.tsx b/ui/snippets/header/HeaderDesktop.tsx index 43dcb38d8a..14501219ca 100644 --- a/ui/snippets/header/HeaderDesktop.tsx +++ b/ui/snippets/header/HeaderDesktop.tsx @@ -3,6 +3,7 @@ import React from 'react'; import config from 'configs/app'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; +import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; @@ -40,6 +41,7 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => { { config.features.account.isEnabled && } { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled && } ) } diff --git a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx index 063db1ba0a..b3b224f987 100644 --- a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx +++ b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx @@ -5,6 +5,7 @@ import config from 'configs/app'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; +import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; @@ -40,6 +41,7 @@ const NavigationDesktop = () => { { config.features.account.isEnabled && } { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled && } ); diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx new file mode 100644 index 0000000000..16c06e2ec6 --- /dev/null +++ b/ui/snippets/profile/ProfileButton.tsx @@ -0,0 +1,31 @@ +import type { ButtonProps } from '@chakra-ui/react'; +import { Button, Tooltip } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import React from 'react'; + +import type { UserInfo } from 'types/api/account'; + +interface Props { + profileQuery: UseQueryResult; + size?: ButtonProps['size']; + variant?: 'hero' | 'header'; + onClick: () => void; +} + +const ProfileButton = ({ profileQuery, size, variant, onClick }: Props) => { + const { data, isPending } = profileQuery; + + return ( + Sign in to My Account to add tags,
create watchlists, access API keys and more } + textAlign="center" + padding={ 2 } + isDisabled={ isPending || Boolean(data) } + openDelay={ 500 } + > + +
+ ); +}; + +export default React.memo(ProfileButton); diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx new file mode 100644 index 0000000000..0f703effb7 --- /dev/null +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -0,0 +1,31 @@ +import { useDisclosure, type ButtonProps } from '@chakra-ui/react'; +import React from 'react'; + +import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; +import AuthModal from 'ui/snippets/auth/AuthModal'; + +import ProfileButton from './ProfileButton'; + +interface Props { + buttonSize?: ButtonProps['size']; + isHomePage?: boolean; +} + +const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => { + const profileQuery = useFetchProfileInfo(); + const authModal = useDisclosure(); + + return ( + <> + + { authModal.isOpen && } + + ); +}; + +export default React.memo(ProfileDesktop); From 64946979fc56d7db9968a7ee579acf641c786c25 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 17 Sep 2024 18:31:07 +0200 Subject: [PATCH 02/49] connect email and code screens to API --- lib/api/resources.ts | 9 ++ lib/errors/getErrorMessage.ts | 6 + .../auth/fields/AuthModalFieldEmail.tsx | 38 ++++++ .../auth/fields/AuthModalFieldOtpCode.tsx | 36 ++++++ .../auth/screens/AuthModalScreenEmail.tsx | 71 +++++++++-- .../auth/screens/AuthModalScreenOtpCode.tsx | 116 +++++++++++++++--- ui/snippets/auth/types.ts | 9 ++ 7 files changed, 257 insertions(+), 28 deletions(-) create mode 100644 lib/errors/getErrorMessage.ts create mode 100644 ui/snippets/auth/fields/AuthModalFieldEmail.tsx create mode 100644 ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 826046f9ac..dc89e0411d 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -217,6 +217,15 @@ export const RESOURCES = { needAuth: true, }, + // AUTH + auth_send_otp: { + path: '/api/account/v2/send_otp', + }, + + auth_confirm_otp: { + path: '/api/account/v2/confirm_otp', + }, + // STATS MICROSERVICE API stats_counters: { path: '/api/v1/counters', diff --git a/lib/errors/getErrorMessage.ts b/lib/errors/getErrorMessage.ts new file mode 100644 index 0000000000..51b0db9495 --- /dev/null +++ b/lib/errors/getErrorMessage.ts @@ -0,0 +1,6 @@ +import getErrorObj from './getErrorObj'; + +export default function getErrorMessage(error: Error | undefined): string | undefined { + const errorObj = getErrorObj(error); + return errorObj && 'message' in errorObj && typeof errorObj.message === 'string' ? errorObj.message : undefined; +} diff --git a/ui/snippets/auth/fields/AuthModalFieldEmail.tsx b/ui/snippets/auth/fields/AuthModalFieldEmail.tsx new file mode 100644 index 0000000000..ceb064b7e3 --- /dev/null +++ b/ui/snippets/auth/fields/AuthModalFieldEmail.tsx @@ -0,0 +1,38 @@ +import { chakra, FormControl, Input } from '@chakra-ui/react'; +import React from 'react'; +import { useController, useFormContext } from 'react-hook-form'; + +import type { EmailFormFields } from '../types'; + +import { EMAIL_REGEXP } from 'lib/validations/email'; +import InputPlaceholder from 'ui/shared/InputPlaceholder'; + +interface Props { + className?: string; +} + +const AuthModalFieldEmail = ({ className }: Props) => { + const { control } = useFormContext(); + const { field, fieldState, formState } = useController({ + control, + name: 'email', + rules: { required: true, pattern: EMAIL_REGEXP }, + }); + + const isDisabled = formState.isSubmitting; + + return ( + + + + + ); +}; + +export default React.memo(chakra(AuthModalFieldEmail)); diff --git a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx new file mode 100644 index 0000000000..55d64bccd7 --- /dev/null +++ b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx @@ -0,0 +1,36 @@ +import { FormControl, Input } from '@chakra-ui/react'; +import React from 'react'; +import { useController, useFormContext } from 'react-hook-form'; + +import type { OtpCodeFormFields } from '../types'; + +import InputPlaceholder from 'ui/shared/InputPlaceholder'; + +const AuthModalFieldOtpCode = () => { + const { control } = useFormContext(); + const { field, fieldState, formState } = useController({ + control, + name: 'code', + rules: { required: true, minLength: 6, maxLength: 6 }, + }); + + const isDisabled = formState.isSubmitting; + + return ( + + + + + ); +}; + +export default React.memo(AuthModalFieldOtpCode); diff --git a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx index 8d5c6a1df3..5595cd6d14 100644 --- a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx @@ -1,7 +1,16 @@ -import { Box, Button, Input, Text } from '@chakra-ui/react'; +import { chakra, Button, Text } from '@chakra-ui/react'; import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; -import type { Screen } from '../types'; +import type { EmailFormFields, Screen } from '../types'; + +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import useToast from 'lib/hooks/useToast'; +import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha'; + +import AuthModalFieldEmail from '../fields/AuthModalFieldEmail'; interface Props { onSubmit: (screen: Screen) => void; @@ -9,16 +18,58 @@ interface Props { const AuthModalScreenEmail = ({ onSubmit }: Props) => { - const handleSubmitClick = React.useCallback(() => { - onSubmit({ type: 'otp_code', email: 'tom@ohhhh.me' }); - }, [ onSubmit ]); + const apiFetch = useApiFetch(); + const toast = useToast(); + + const formApi = useForm({ + mode: 'onBlur', + defaultValues: { + email: '', + }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback((formData) => { + return apiFetch('auth_send_otp', { + fetchParams: { + method: 'POST', + body: { + email: formData.email, + recaptcha_response: formData.reCaptcha, + }, + }, + }) + .then(() => { + onSubmit({ type: 'otp_code', email: formData.email }); + }) + .catch((error) => { + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + }); + }, [ apiFetch, onSubmit, toast ]); return ( - - Account email, used for transaction notifications from your watchlist. - - - + + + Account email, used for transaction notifications from your watchlist. + + + + + ); }; diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx index 898c3eb8de..bebefeaffc 100644 --- a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -1,10 +1,17 @@ -import { chakra, Box, Text, Input, Button, Link } from '@chakra-ui/react'; +import { chakra, Box, Text, Button, Link } from '@chakra-ui/react'; import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; -import type { Screen } from '../types'; +import type { OtpCodeFormFields, Screen } from '../types'; +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import useToast from 'lib/hooks/useToast'; import IconSvg from 'ui/shared/IconSvg'; +import AuthModalFieldOtpCode from '../fields/AuthModalFieldOtpCode'; + interface Props { email: string; onSubmit: (screen: Screen) => void; @@ -12,24 +19,97 @@ interface Props { const AuthModalScreenOtpCode = ({ email, onSubmit }: Props) => { - const handleSubmitClick = React.useCallback(() => { - onSubmit({ type: 'success_created_email' }); - }, [ onSubmit ]); + const apiFetch = useApiFetch(); + const toast = useToast(); + + const formApi = useForm({ + mode: 'onBlur', + defaultValues: { + code: '', + }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback((formData) => { + return apiFetch('auth_confirm_otp', { + fetchParams: { + method: 'POST', + body: { + otp: formData.code, + email, + }, + }, + }) + .then(() => { + onSubmit({ type: 'success_created_email' }); + }) + .catch((error) => { + // TODO @tom2drum handle incorrect code error + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + }); + }, [ apiFetch, email, onSubmit, toast ]); + + const handleResendCodeClick = React.useCallback(() => { + return apiFetch('auth_send_otp', { + fetchParams: { + method: 'POST', + body: { + email, + }, + }, + }) + .then(() => { + toast({ + status: 'success', + title: 'Code sent', + description: 'Code has been sent to your email', + }); + }) + .catch((error) => { + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + }); + }, [ apiFetch, email, toast ]); return ( - - - Please check{ ' ' } - { email }{ ' ' } - and enter your code below. - - - - - Resend code - - - + + + + Please check{ ' ' } + { email }{ ' ' } + and enter your code below. + + + + + Resend code + + + + ); }; diff --git a/ui/snippets/auth/types.ts b/ui/snippets/auth/types.ts index bde9e4b76d..adb8971d07 100644 --- a/ui/snippets/auth/types.ts +++ b/ui/snippets/auth/types.ts @@ -8,3 +8,12 @@ export type Screen = { } | { type: 'success_created_email'; } + +export interface EmailFormFields { + email: string; + reCaptcha: string; +} + +export interface OtpCodeFormFields { + code: string; +} From 39ac3399c9c36f41d246fef9e6320bb7b0c2bacb Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 08:37:51 +0200 Subject: [PATCH 03/49] add screens to modal for wallet authentication --- lib/api/resources.ts | 8 +++ lib/errors/getErrorMessage.ts | 2 +- ui/shared/Web3ModalProvider.tsx | 2 +- ui/snippets/auth/AuthModal.tsx | 15 ++++- .../screens/AuthModalScreenConnectWallet.tsx | 62 +++++++++++++++++++ .../screens/AuthModalScreenSelectMethod.tsx | 6 +- .../AuthModalScreenSuccessCreatedWallet.tsx | 31 ++++++++++ ui/snippets/auth/types.ts | 6 ++ 8 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx diff --git a/lib/api/resources.ts b/lib/api/resources.ts index dc89e0411d..b37a0eb9eb 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -226,6 +226,14 @@ export const RESOURCES = { path: '/api/account/v2/confirm_otp', }, + auth_siwe_message: { + path: '/api/account/v2/siwe_message', + }, + + auth_siwe_verify: { + path: '/api/account/v2/authenticate_via_wallet', + }, + // STATS MICROSERVICE API stats_counters: { path: '/api/v1/counters', diff --git a/lib/errors/getErrorMessage.ts b/lib/errors/getErrorMessage.ts index 51b0db9495..5e15f29a1c 100644 --- a/lib/errors/getErrorMessage.ts +++ b/lib/errors/getErrorMessage.ts @@ -1,6 +1,6 @@ import getErrorObj from './getErrorObj'; -export default function getErrorMessage(error: Error | undefined): string | undefined { +export default function getErrorMessage(error: unknown): string | undefined { const errorObj = getErrorObj(error); return errorObj && 'message' in errorObj && typeof errorObj.message === 'string' ? errorObj.message : undefined; } diff --git a/ui/shared/Web3ModalProvider.tsx b/ui/shared/Web3ModalProvider.tsx index 9159066a8c..8b370327f8 100644 --- a/ui/shared/Web3ModalProvider.tsx +++ b/ui/shared/Web3ModalProvider.tsx @@ -24,7 +24,7 @@ const init = () => { '--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`, '--w3m-accent': colors.blue[600], '--w3m-border-radius-master': '2px', - '--w3m-z-index': zIndices.modal, + '--w3m-z-index': zIndices.popover, }, featuredWalletIds: [], allowUnsupportedChain: true, diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx index 4d4f6e7350..22e0c09f86 100644 --- a/ui/snippets/auth/AuthModal.tsx +++ b/ui/snippets/auth/AuthModal.tsx @@ -5,10 +5,12 @@ import type { Screen } from './types'; import IconSvg from 'ui/shared/IconSvg'; +import AuthModalScreenConnectWallet from './screens/AuthModalScreenConnectWallet'; import AuthModalScreenEmail from './screens/AuthModalScreenEmail'; import AuthModalScreenOtpCode from './screens/AuthModalScreenOtpCode'; import AuthModalScreenSelectMethod from './screens/AuthModalScreenSelectMethod'; import AuthModalScreenSuccessCreatedEmail from './screens/AuthModalScreenSuccessCreatedEmail'; +import AuthModalScreenSuccessCreatedWallet from './screens/AuthModalScreenSuccessCreatedWallet'; interface Props { initialScreen: Screen; @@ -26,16 +28,23 @@ const AuthModal = ({ initialScreen, onClose }: Props) => { setSteps((prev) => prev.length > 1 ? prev.slice(0, -1) : prev); }, []); + const onReset = React.useCallback(() => { + setSteps([ initialScreen ]); + }, [ initialScreen ]); + const header = (() => { const currentStep = steps[steps.length - 1]; switch (currentStep.type) { case 'select_method': return 'Select a way to connect'; + case 'connect_wallet': + return 'Continue with wallet'; case 'email': - return 'Continue with email'; + return currentStep.isAccountExists ? 'Add email' : 'Continue with email'; case 'otp_code': return 'Confirmation code'; case 'success_created_email': + case 'success_created_wallet': return 'Congrats!'; } })(); @@ -45,12 +54,16 @@ const AuthModal = ({ initialScreen, onClose }: Props) => { switch (currentStep.type) { case 'select_method': return ; + case 'connect_wallet': + return ; case 'email': return ; case 'otp_code': return ; case 'success_created_email': return ; + case 'success_created_wallet': + return ; } })(); diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx new file mode 100644 index 0000000000..04aff0ea7e --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -0,0 +1,62 @@ +import { Center, Spinner } from '@chakra-ui/react'; +import { useWeb3Modal } from '@web3modal/wagmi/react'; +import React from 'react'; +import { useAccount, useSignMessage } from 'wagmi'; + +import type { Screen } from '../types'; + +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import useToast from 'lib/hooks/useToast'; + +interface Props { + onSuccess: (screen: Screen) => void; + onError: () => void; +} + +const AuthModalScreenConnectWallet = ({ onSuccess, onError }: Props) => { + const isSigningRef = React.useRef(false); + + const apiFetch = useApiFetch(); + const toast = useToast(); + const web3Modal = useWeb3Modal(); + const { isConnected, address } = useAccount(); + const { signMessageAsync } = useSignMessage(); + + React.useEffect(() => { + !isConnected && web3Modal.open(); + }, [ isConnected, web3Modal ]); + + const proceedToAuth = React.useCallback(async(address: string) => { + try { + const siweMessage = await apiFetch('auth_siwe_message', { queryParams: { address } }) as { siwe_message: string }; + const signature = await signMessageAsync({ message: siweMessage.siwe_message }); + await apiFetch('auth_siwe_verify', { + fetchParams: { + method: 'POST', + body: { message: siweMessage.siwe_message, signature }, + }, + }); + onSuccess({ type: 'success_created_wallet', address }); + } catch (error) { + // TODO @tom2drum show better error message + onError(); + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + } + }, [ apiFetch, onError, onSuccess, signMessageAsync, toast ]); + + React.useEffect(() => { + if (isConnected && address && !isSigningRef.current) { + isSigningRef.current = true; + proceedToAuth(address); + } + }, [ address, isConnected, proceedToAuth ]); + + return
; +}; + +export default React.memo(AuthModalScreenConnectWallet); diff --git a/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx index 596d735b5e..eb1309b832 100644 --- a/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx @@ -13,9 +13,13 @@ const AuthModalScreenSelectMethod = ({ onSelectMethod }: Props) => { onSelectMethod({ type: 'email' }); }, [ onSelectMethod ]); + const handleConnectWalletClick = React.useCallback(() => { + onSelectMethod({ type: 'connect_wallet' }); + }, [ onSelectMethod ]); + return ( - + ); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx new file mode 100644 index 0000000000..f2c3085e4f --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx @@ -0,0 +1,31 @@ +import { chakra, Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; + +import shortenString from 'lib/shortenString'; + +interface Props { + address: string; + onAddEmail: (screen: Screen) => void; +} + +const AuthModalScreenSuccessCreatedWallet = ({ address, onAddEmail }: Props) => { + const handleAddEmailClick = React.useCallback(() => { + onAddEmail({ type: 'email', isAccountExists: true }); + }, [ onAddEmail ]); + + return ( + + + Your account was linked to{ ' ' } + { shortenString(address) }{ ' ' } + wallet. Use for the next login. + + Add your email to receive notifications about addresses in your watch list. + + + ); +}; + +export default React.memo(AuthModalScreenSuccessCreatedWallet); diff --git a/ui/snippets/auth/types.ts b/ui/snippets/auth/types.ts index adb8971d07..901f0c2263 100644 --- a/ui/snippets/auth/types.ts +++ b/ui/snippets/auth/types.ts @@ -1,12 +1,18 @@ export type Screen = { type: 'select_method'; +} | { + type: 'connect_wallet'; } | { type: 'email'; + isAccountExists?: boolean; } | { type: 'otp_code'; email: string; } | { type: 'success_created_email'; +} | { + type: 'success_created_wallet'; + address: string; } export interface EmailFormFields { From 2c2837048312ec8bee8d91db9618edf3e4a0dc5f Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 09:07:04 +0200 Subject: [PATCH 04/49] migrate to pin input --- .../auth/fields/AuthModalFieldOtpCode.tsx | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx index 55d64bccd7..c304f67926 100644 --- a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx +++ b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx @@ -1,11 +1,9 @@ -import { FormControl, Input } from '@chakra-ui/react'; +import { HStack, PinInput, PinInputField, Text } from '@chakra-ui/react'; import React from 'react'; import { useController, useFormContext } from 'react-hook-form'; import type { OtpCodeFormFields } from '../types'; -import InputPlaceholder from 'ui/shared/InputPlaceholder'; - const AuthModalFieldOtpCode = () => { const { control } = useFormContext(); const { field, fieldState, formState } = useController({ @@ -17,19 +15,19 @@ const AuthModalFieldOtpCode = () => { const isDisabled = formState.isSubmitting; return ( - - - - + <> + + + + + + + + + + + { fieldState.error?.message && { fieldState.error.message } } + ); }; From ec917a04de4f74612587bd5e7c264d29cf822e3d Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 12:27:13 +0200 Subject: [PATCH 05/49] user profile menu --- icons/API_slim.svg | 3 + icons/private_tags_slim.svg | 5 ++ icons/sign_out.svg | 4 ++ icons/verified_slim.svg | 4 ++ lib/hooks/useNavItems.tsx | 1 + public/icons/name.d.ts | 4 ++ ui/snippets/profile/ProfileButton.tsx | 38 ++++++++-- ui/snippets/profile/ProfileDesktop.tsx | 29 ++++++-- ui/snippets/profile/ProfileMenuContent.tsx | 84 ++++++++++++++++++++++ ui/snippets/profile/ProfileMenuNavLink.tsx | 30 ++++++++ ui/snippets/profile/types.ts | 8 +++ ui/snippets/profile/utils.ts | 3 + 12 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 icons/API_slim.svg create mode 100644 icons/private_tags_slim.svg create mode 100644 icons/sign_out.svg create mode 100644 icons/verified_slim.svg create mode 100644 ui/snippets/profile/ProfileMenuContent.tsx create mode 100644 ui/snippets/profile/ProfileMenuNavLink.tsx create mode 100644 ui/snippets/profile/types.ts create mode 100644 ui/snippets/profile/utils.ts diff --git a/icons/API_slim.svg b/icons/API_slim.svg new file mode 100644 index 0000000000..1473387362 --- /dev/null +++ b/icons/API_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/private_tags_slim.svg b/icons/private_tags_slim.svg new file mode 100644 index 0000000000..599cff3349 --- /dev/null +++ b/icons/private_tags_slim.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/sign_out.svg b/icons/sign_out.svg new file mode 100644 index 0000000000..b577078676 --- /dev/null +++ b/icons/sign_out.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/verified_slim.svg b/icons/verified_slim.svg new file mode 100644 index 0000000000..a13930aab2 --- /dev/null +++ b/icons/verified_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index b1a0bf5245..30cb06209e 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -297,6 +297,7 @@ export default function useNavItems(): ReturnType { }, ].filter(Boolean); + // TODO @tom2drum remove this const profileItem = { text: 'My profile', nextRoute: { pathname: '/auth/profile' as const }, diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index c9c5819bc6..53660dec46 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -3,6 +3,7 @@ export type IconName = | "ABI_slim" | "ABI" + | "API_slim" | "API" | "apps_list" | "apps_slim" @@ -103,6 +104,7 @@ | "output_roots" | "payment_link" | "plus" + | "private_tags_slim" | "privattags" | "profile" | "publictags_slim" @@ -119,6 +121,7 @@ | "score/score-ok" | "search" | "share" + | "sign_out" | "social/canny" | "social/coingecko" | "social/coinmarketcap" @@ -163,6 +166,7 @@ | "validator" | "verification-steps/finalized" | "verification-steps/unfinalized" + | "verified_slim" | "verified" | "wallet" | "wallets/coinbase" diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index 16c06e2ec6..2298d74dbd 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -1,10 +1,14 @@ import type { ButtonProps } from '@chakra-ui/react'; -import { Button, Tooltip } from '@chakra-ui/react'; +import { Button, Skeleton, Tooltip, Text, HStack } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; import type { UserInfo } from 'types/api/account'; +import UserAvatar from 'ui/shared/UserAvatar'; + +import { getUserHandle } from './utils'; + interface Props { profileQuery: UseQueryResult; size?: ButtonProps['size']; @@ -12,9 +16,26 @@ interface Props { onClick: () => void; } -const ProfileButton = ({ profileQuery, size, variant, onClick }: Props) => { +const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: React.ForwardedRef) => { const { data, isPending } = profileQuery; + const content = (() => { + if (!data) { + return 'Connect'; + } + + if (data.email) { + return ( + + + { getUserHandle(data.email) } + + ); + } + + return 'Connected'; + })(); + return ( Sign in to My Account to add tags,
create watchlists, access API keys and more } @@ -23,9 +44,18 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props) => { isDisabled={ isPending || Boolean(data) } openDelay={ 500 } > - + + +
); }; -export default React.memo(ProfileButton); +export default React.memo(React.forwardRef(ProfileButton)); diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx index 0f703effb7..f582fe77f4 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -1,10 +1,12 @@ -import { useDisclosure, type ButtonProps } from '@chakra-ui/react'; +import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type ButtonProps } from '@chakra-ui/react'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; +import Popover from 'ui/shared/chakra/Popover'; import AuthModal from 'ui/snippets/auth/AuthModal'; import ProfileButton from './ProfileButton'; +import ProfileMenuContent from './ProfileMenuContent'; interface Props { buttonSize?: ButtonProps['size']; @@ -14,15 +16,28 @@ interface Props { const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => { const profileQuery = useFetchProfileInfo(); const authModal = useDisclosure(); + const profileMenu = useDisclosure(); return ( <> - + + + + + + { profileQuery.data && ( + + + + + + ) } + { authModal.isOpen && } ); diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx new file mode 100644 index 0000000000..82b85016a5 --- /dev/null +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -0,0 +1,84 @@ +import { Box, Divider, Flex, Text, VStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { NavLink } from './types'; +import type { UserInfo } from 'types/api/account'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; + +import ProfileMenuNavLink from './ProfileMenuNavLink'; +import { getUserHandle } from './utils'; + +const navLinks: Array = [ + { + text: 'Watch list', + href: route({ pathname: '/account/watchlist' }), + icon: 'star_outline' as const, + }, + { + text: 'Private tags', + href: route({ pathname: '/account/tag-address' }), + icon: 'private_tags_slim' as const, + }, + { + text: 'API keys', + href: route({ pathname: '/account/api-key' }), + icon: 'API_slim' as const, + }, + { + text: 'Custom ABI', + href: route({ pathname: '/account/custom-abi' }), + icon: 'ABI_slim' as const, + }, + config.features.addressVerification.isEnabled && { + text: 'Verified addrs', + href: route({ pathname: '/account/verified-addresses' }), + icon: 'verified_slim' as const, + }, +].filter(Boolean); + +interface Props { + data?: UserInfo; + onNavLinkClick?: () => void; +} + +const ProfileMenuContent = ({ data, onNavLinkClick }: Props) => { + + return ( + + + + { data?.email && { getUserHandle(data.email) } } + + + + + + { navLinks.map((item) => ( + + )) } + + + + + + + ); +}; + +export default React.memo(ProfileMenuContent); diff --git a/ui/snippets/profile/ProfileMenuNavLink.tsx b/ui/snippets/profile/ProfileMenuNavLink.tsx new file mode 100644 index 0000000000..7dd7842fa7 --- /dev/null +++ b/ui/snippets/profile/ProfileMenuNavLink.tsx @@ -0,0 +1,30 @@ +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +import type { NavLink } from './types'; + +import IconSvg from 'ui/shared/IconSvg'; +import LinkInternal from 'ui/shared/links/LinkInternal'; + +type Props = NavLink + +const ProfileMenuNavLink = ({ href, icon, text, onClick }: Props) => { + + return ( + + + { text } + + ); +}; + +export default React.memo(ProfileMenuNavLink); diff --git a/ui/snippets/profile/types.ts b/ui/snippets/profile/types.ts new file mode 100644 index 0000000000..79c035f79c --- /dev/null +++ b/ui/snippets/profile/types.ts @@ -0,0 +1,8 @@ +import type { IconName } from 'public/icons/name'; + +export interface NavLink { + text: string; + href?: string; + onClick?: () => void; + icon: IconName; +} diff --git a/ui/snippets/profile/utils.ts b/ui/snippets/profile/utils.ts new file mode 100644 index 0000000000..6d1b3ea446 --- /dev/null +++ b/ui/snippets/profile/utils.ts @@ -0,0 +1,3 @@ +export function getUserHandle(email: string) { + return email.split('@')[0]; +} From 7b19b08967948f8fb205aee6d4e80b86679caf68 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 12:33:06 +0200 Subject: [PATCH 06/49] refactor otp field --- ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx index c304f67926..43cb4bcf13 100644 --- a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx +++ b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx @@ -4,12 +4,14 @@ import { useController, useFormContext } from 'react-hook-form'; import type { OtpCodeFormFields } from '../types'; +const CODE_LENGTH = 6; + const AuthModalFieldOtpCode = () => { const { control } = useFormContext(); const { field, fieldState, formState } = useController({ control, name: 'code', - rules: { required: true, minLength: 6, maxLength: 6 }, + rules: { required: true, minLength: CODE_LENGTH, maxLength: CODE_LENGTH }, }); const isDisabled = formState.isSubmitting; @@ -17,13 +19,10 @@ const AuthModalFieldOtpCode = () => { return ( <> - - - - - - - + + { Array.from({ length: CODE_LENGTH }).map((_, index) => ( + + )) } { fieldState.error?.message && { fieldState.error.message } } From c2f67dd7099da5ff8d8321ddd4d23b35cced3db8 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 14:07:45 +0200 Subject: [PATCH 07/49] fix passing set-cookie from api response --- pages/api/proxy.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts index 95b168413b..565f22243d 100644 --- a/pages/api/proxy.ts +++ b/pages/api/proxy.ts @@ -22,8 +22,13 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => { ); // proxy some headers from API - nextRes.setHeader('x-request-id', apiRes.headers.get('x-request-id') || ''); - nextRes.setHeader('set-cookie', apiRes.headers.get('set-cookie') || ''); + const requestId = apiRes.headers.get('x-request-id'); + requestId && nextRes.setHeader('x-request-id', requestId); + + const setCookie = apiRes.headers.raw()['set-cookie']; + setCookie?.forEach((value) => { + nextRes.appendHeader('set-cookie', value); + }); nextRes.status(apiRes.status).send(apiRes.body); }; From 149a80b7e8f982962a9353e62e7ac920b32c9b8b Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 15:28:25 +0200 Subject: [PATCH 08/49] add wallet info into profile menu --- ui/snippets/profile/ProfileButton.tsx | 13 +++- ui/snippets/profile/ProfileDesktop.tsx | 4 +- ui/snippets/profile/ProfileMenuContent.tsx | 13 ++-- ui/snippets/profile/ProfileMenuWallet.tsx | 90 ++++++++++++++++++++++ 4 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 ui/snippets/profile/ProfileMenuWallet.tsx diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index 2298d74dbd..afba83aec5 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -17,7 +17,14 @@ interface Props { } const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: React.ForwardedRef) => { - const { data, isPending } = profileQuery; + const [ isFetched, setIsFetched ] = React.useState(false); + const { data, isLoading } = profileQuery; + + React.useEffect(() => { + if (!isLoading) { + setIsFetched(true); + } + }, [ isLoading ]); const content = (() => { if (!data) { @@ -41,10 +48,10 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: Rea label={ Sign in to My Account to add tags,
create watchlists, access API keys and more
} textAlign="center" padding={ 2 } - isDisabled={ isPending || Boolean(data) } + isDisabled={ isFetched || Boolean(data) } openDelay={ 500 } > - + + ); +}; + +export default React.memo(ProfileMenuWallet); From 658aeedda4f33482131274d7c190017a3a3fb06b Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 16:20:12 +0200 Subject: [PATCH 09/49] add mobile menu --- ui/home/HeroBanner.tsx | 6 +--- ui/marketplace/MarketplaceAppTopBar.tsx | 4 --- ui/snippets/header/HeaderDesktop.tsx | 2 +- ui/snippets/header/HeaderMobile.tsx | 6 ++-- ui/snippets/profile/ProfileDesktop.tsx | 1 - ui/snippets/profile/ProfileMenuContent.tsx | 2 +- ui/snippets/profile/ProfileMobile.tsx | 42 ++++++++++++++++++++++ 7 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 ui/snippets/profile/ProfileMobile.tsx diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index a992228489..cefe75f147 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -4,9 +4,7 @@ import React from 'react'; import config from 'configs/app'; import AdBanner from 'ui/shared/ad/AdBanner'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; const BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; const TEXT_COLOR_DEFAULT = 'white'; @@ -54,9 +52,7 @@ const HeroBanner = () => { } { config.UI.navigation.layout === 'vertical' && ( - - { config.features.account.isEnabled && } - { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled && } ) } diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 55b23cb697..df0b1d52dc 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -13,8 +13,6 @@ import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; -import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; import AppSecurityReport from './AppSecurityReport'; import ContractListModal from './ContractListModal'; @@ -100,8 +98,6 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) /> { !isMobile && ( - { config.features.account.isEnabled && } - { config.features.blockchainInteraction.isEnabled && } { config.features.account.isEnabled && } ) } diff --git a/ui/snippets/header/HeaderDesktop.tsx b/ui/snippets/header/HeaderDesktop.tsx index 14501219ca..57aa12dc1c 100644 --- a/ui/snippets/header/HeaderDesktop.tsx +++ b/ui/snippets/header/HeaderDesktop.tsx @@ -38,7 +38,7 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => { { searchBar } { config.UI.navigation.layout === 'vertical' && ( - + { config.features.account.isEnabled && } { config.features.blockchainInteraction.isEnabled && } { config.features.account.isEnabled && } diff --git a/ui/snippets/header/HeaderMobile.tsx b/ui/snippets/header/HeaderMobile.tsx index 796971f795..41ea1a7f92 100644 --- a/ui/snippets/header/HeaderMobile.tsx +++ b/ui/snippets/header/HeaderMobile.tsx @@ -5,9 +5,8 @@ import { useInView } from 'react-intersection-observer'; import config from 'configs/app'; import { useScrollDirection } from 'lib/contexts/scrollDirection'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile'; +import ProfileMobile from 'ui/snippets/profile/ProfileMobile'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletMenuMobile from 'ui/snippets/walletMenu/WalletMenuMobile'; import Burger from './Burger'; @@ -48,8 +47,7 @@ const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => { - { config.features.account.isEnabled ? : } - { config.features.blockchainInteraction.isEnabled && } + { config.features.account.isEnabled ? : } { !hideSearchBar && searchBar } diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx index 6e06fde76c..bc3c876cd6 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -28,7 +28,6 @@ const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => { variant={ isHomePage ? 'hero' : 'header' } onClick={ profileQuery.data ? profileMenu.onOpen : authModal.onOpen } /> - { profileQuery.data && ( diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx index 87e96e3942..2ad6ffbed8 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -41,7 +41,7 @@ const navLinks: Array = [ ].filter(Boolean); interface Props { - data?: UserInfo; + data: UserInfo; onClose?: () => void; } diff --git a/ui/snippets/profile/ProfileMobile.tsx b/ui/snippets/profile/ProfileMobile.tsx new file mode 100644 index 0000000000..eb98bfb0c7 --- /dev/null +++ b/ui/snippets/profile/ProfileMobile.tsx @@ -0,0 +1,42 @@ +import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; +import AuthModal from 'ui/snippets/auth/AuthModal'; + +import ProfileButton from './ProfileButton'; +import ProfileMenuContent from './ProfileMenuContent'; + +const ProfileMobile = () => { + const profileQuery = useFetchProfileInfo(); + const authModal = useDisclosure(); + const profileMenu = useDisclosure(); + + return ( + <> + + { profileQuery.data && ( + + + + + + + + + ) } + { authModal.isOpen && } + + ); +}; + +export default React.memo(ProfileMobile); From 4eb5a26f4fc1321270daa547373038cebdae86a0 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Sep 2024 18:53:19 +0200 Subject: [PATCH 10/49] show connected wallet address in button --- types/api/account.ts | 1 + ui/shared/UserAvatar.tsx | 1 + ui/snippets/profile/ProfileAddressIcon.tsx | 40 +++++++++++++++++++ ui/snippets/profile/ProfileButton.tsx | 39 ++++++++++++++++-- ui/snippets/profile/ProfileMenuContent.tsx | 27 ++++++++++++- ui/snippets/profile/ProfileMenuWallet.tsx | 18 +++------ .../profile/useWeb3AccountWithDomain.tsx | 30 ++++++++++++++ 7 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 ui/snippets/profile/ProfileAddressIcon.tsx create mode 100644 ui/snippets/profile/useWeb3AccountWithDomain.tsx diff --git a/types/api/account.ts b/types/api/account.ts index 67a8c3adef..50af396ea7 100644 --- a/types/api/account.ts +++ b/types/api/account.ts @@ -71,6 +71,7 @@ export interface UserInfo { name?: string; nickname?: string; email: string | null; + address_hash: string | null; avatar?: string; } diff --git a/ui/shared/UserAvatar.tsx b/ui/shared/UserAvatar.tsx index 3a75d56105..34cbb3e555 100644 --- a/ui/shared/UserAvatar.tsx +++ b/ui/shared/UserAvatar.tsx @@ -11,6 +11,7 @@ interface Props { fallbackIconSize?: number; } +// TODO @tom2drum remove this component const UserAvatar = ({ size, fallbackIconSize = 20 }: Props) => { const appProps = useAppContext(); const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies)); diff --git a/ui/snippets/profile/ProfileAddressIcon.tsx b/ui/snippets/profile/ProfileAddressIcon.tsx new file mode 100644 index 0000000000..0bad509d91 --- /dev/null +++ b/ui/snippets/profile/ProfileAddressIcon.tsx @@ -0,0 +1,40 @@ +import { Box, Center, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon'; +import IconSvg from 'ui/shared/IconSvg'; + +type Props = { + address: string; + isAutoConnectDisabled?: boolean; +}; + +const ProfileAddressIcon = ({ address, isAutoConnectDisabled }: Props) => { + const borderColor = useColorModeValue('orange.100', 'orange.900'); + + return ( + + + { isAutoConnectDisabled && ( +
+ +
+ ) } +
+ ); +}; + +export default React.memo(ProfileAddressIcon); diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index afba83aec5..9467050690 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -5,8 +5,12 @@ import React from 'react'; import type { UserInfo } from 'types/api/account'; -import UserAvatar from 'ui/shared/UserAvatar'; +import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import shortenString from 'lib/shortenString'; +import IconSvg from 'ui/shared/IconSvg'; +import ProfileAddressIcon from './ProfileAddressIcon'; +import useWeb3AccountWithDomain from './useWeb3AccountWithDomain'; import { getUserHandle } from './utils'; interface Props { @@ -19,6 +23,8 @@ interface Props { const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: React.ForwardedRef) => { const [ isFetched, setIsFetched ] = React.useState(false); const { data, isLoading } = profileQuery; + const web3AccountWithDomain = useWeb3AccountWithDomain(!data?.address_hash); + const { isAutoConnectDisabled } = useMarketplaceContext(); React.useEffect(() => { if (!isLoading) { @@ -31,11 +37,33 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: Rea return 'Connect'; } + const address = data.address_hash || web3AccountWithDomain.address; + if (address) { + const text = (() => { + if (data.address_hash) { + return shortenString(data.address_hash); + } + + if (web3AccountWithDomain.domain) { + return web3AccountWithDomain.domain; + } + + return shortenString(address); + })(); + + return ( + + + { text } + + ); + } + if (data.email) { return ( - - { getUserHandle(data.email) } + + { getUserHandle(data.email) } ); } @@ -57,6 +85,11 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: Rea variant={ variant } onClick={ onClick } data-selected={ Boolean(data) } + data-warning={ isAutoConnectDisabled } + fontSize="sm" + lineHeight={ 5 } + px={ data ? 2.5 : 4 } + fontWeight={ data ? 700 : 600 } > { content } diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx index 2ad6ffbed8..f48cc91ded 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, Flex, Text, VStack } from '@chakra-ui/react'; +import { Box, Divider, Flex, Text, VStack, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; import type { NavLink } from './types'; @@ -7,6 +7,8 @@ import type { UserInfo } from 'types/api/account'; import { route } from 'nextjs-routes'; import config from 'configs/app'; +import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import IconSvg from 'ui/shared/IconSvg'; import ProfileMenuNavLink from './ProfileMenuNavLink'; import ProfileMenuWallet from './ProfileMenuWallet'; @@ -46,9 +48,32 @@ interface Props { } const ProfileMenuContent = ({ data, onClose }: Props) => { + const { isAutoConnectDisabled } = useMarketplaceContext(); + const alertBgColor = useColorModeValue('orange.100', 'orange.900'); return ( + { isAutoConnectDisabled && ( + + + + Connect your wallet in the app below + + + ) } + void; @@ -16,15 +16,7 @@ interface Props { const ProfileMenuWallet = ({ onClose }: Props) => { const wallet = useWallet({ source: 'Header' }); - const addressDomainQuery = useApiQuery('address_domain', { - pathParams: { - chainId: config.chain.id, - address: wallet.address, - }, - queryOptions: { - enabled: config.features.nameService.isEnabled && Boolean(wallet.address), - }, - }); + const web3AccountWithDomain = useWeb3AccountWithDomain(true); const handleConnectWalletClick = React.useCallback(async() => { wallet.openModal(); @@ -42,14 +34,14 @@ const ProfileMenuWallet = ({ onClose }: Props) => { return ; } - if (wallet.isWalletConnected) { + if (wallet.isWalletConnected && web3AccountWithDomain.address) { return ( <> { + return { + address: isEnabled ? address : undefined, + domain: domainQuery.data?.domain?.name, + isLoading: isQueryEnabled && domainQuery.isLoading, + }; + }, [ address, domainQuery.data?.domain?.name, domainQuery.isLoading, isEnabled, isQueryEnabled ]); +} From 6e26942485a6c2726ff8c114597f507b1a6c98e1 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 12:01:03 +0200 Subject: [PATCH 11/49] my profile page re-design --- ui/myProfile/MyProfileEmail.tsx | 79 ++++++++++++++++++++++++++++++++ ui/myProfile/MyProfileWallet.tsx | 20 ++++++++ ui/pages/Login.tsx | 1 + ui/pages/MyProfile.tsx | 42 ++++------------- 4 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 ui/myProfile/MyProfileEmail.tsx create mode 100644 ui/myProfile/MyProfileWallet.tsx diff --git a/ui/myProfile/MyProfileEmail.tsx b/ui/myProfile/MyProfileEmail.tsx new file mode 100644 index 0000000000..a31aed0774 --- /dev/null +++ b/ui/myProfile/MyProfileEmail.tsx @@ -0,0 +1,79 @@ +import { Button, chakra, FormControl, Heading, Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; + +import type { UserInfo } from 'types/api/account'; + +import { EMAIL_REGEXP } from 'lib/validations/email'; +import IconSvg from 'ui/shared/IconSvg'; +import InputPlaceholder from 'ui/shared/InputPlaceholder'; + +interface FormFields { + email: string; +} + +interface Props { + profileQuery: UseQueryResult; +} + +const MyProfileEmail = ({ profileQuery }: Props) => { + const formApi = useForm({ + mode: 'onBlur', + defaultValues: { + email: profileQuery.data?.email || '', + }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback((formData) => { + // eslint-disable-next-line no-console + console.log(formData); + }, [ ]); + + const isDisabled = formApi.formState.isSubmitting; + + return ( +
+ Notifications + + + + + + + { !formApi.formState.isDirty && ( + + + + ) } + + Email for watch list notifications and private tags + + + + +
+ ); +}; + +export default React.memo(MyProfileEmail); diff --git a/ui/myProfile/MyProfileWallet.tsx b/ui/myProfile/MyProfileWallet.tsx new file mode 100644 index 0000000000..c034193a51 --- /dev/null +++ b/ui/myProfile/MyProfileWallet.tsx @@ -0,0 +1,20 @@ +import { Button, Heading } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import React from 'react'; + +import type { UserInfo } from 'types/api/account'; + +interface Props { + profileQuery: UseQueryResult; +} + +const MyProfileWallet = ({ profileQuery }: Props) => { + return ( +
+ My linked wallet + { !profileQuery.data?.address_hash && } +
+ ); +}; + +export default React.memo(MyProfileWallet); diff --git a/ui/pages/Login.tsx b/ui/pages/Login.tsx index f737bae160..fe6e7d44cd 100644 --- a/ui/pages/Login.tsx +++ b/ui/pages/Login.tsx @@ -11,6 +11,7 @@ import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useToast from 'lib/hooks/useToast'; import PageTitle from 'ui/shared/Page/PageTitle'; +// TODO @tom2drum delete this page { /* will be deleted when we fix login in preview CI stands */ } const Login = () => { const toast = useToast(); diff --git a/ui/pages/MyProfile.tsx b/ui/pages/MyProfile.tsx index 4b47b6f782..bc7fe5d58e 100644 --- a/ui/pages/MyProfile.tsx +++ b/ui/pages/MyProfile.tsx @@ -1,54 +1,32 @@ -import { VStack, FormControl, FormLabel, Input } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; +import MyProfileEmail from 'ui/myProfile/MyProfileEmail'; +import MyProfileWallet from 'ui/myProfile/MyProfileWallet'; import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import PageTitle from 'ui/shared/Page/PageTitle'; -import UserAvatar from 'ui/shared/UserAvatar'; const MyProfile = () => { - const { data, isPending, isError } = useFetchProfileInfo(); + const profileQuery = useFetchProfileInfo(); useRedirectForInvalidAuthToken(); const content = (() => { - if (isPending) { + if (profileQuery.isPending) { return ; } - if (isError) { + if (profileQuery.isError) { return ; } return ( - - - - - Name - - - - Nickname - - - - Email - - + + + + ); })(); From 8018001469b567d989e2f885519172e02afff2be Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 12:59:49 +0200 Subject: [PATCH 12/49] custom behaviour of connect button on dapp page --- ui/home/HeroBanner.tsx | 2 +- ui/marketplace/MarketplaceAppTopBar.tsx | 6 +- .../screens/AuthModalScreenConnectWallet.tsx | 54 ++++----------- .../auth/screens/useSignInWithWallet.tsx | 67 +++++++++++++++++++ ui/snippets/profile/ProfileButton.tsx | 7 +- ui/snippets/profile/ProfileDesktop.tsx | 31 +++++++-- ui/snippets/profile/ProfileMobile.tsx | 24 ++++++- 7 files changed, 138 insertions(+), 53 deletions(-) create mode 100644 ui/snippets/auth/screens/useSignInWithWallet.tsx diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index cefe75f147..e024567e2c 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -53,7 +53,7 @@ const HeroBanner = () => { { config.UI.navigation.layout === 'vertical' && ( - { config.features.account.isEnabled && } + { config.features.account.isEnabled && } ) }
diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index df0b1d52dc..d9cd1b8d35 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -1,4 +1,4 @@ -import { chakra, Flex, Tooltip, Skeleton } from '@chakra-ui/react'; +import { chakra, Flex, Tooltip, Skeleton, Box } from '@chakra-ui/react'; import React from 'react'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReport, ContractListTypes } from 'types/client/marketplace'; @@ -97,9 +97,9 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) source="App page" /> { !isMobile && ( - + { config.features.account.isEnabled && } - +
) } { contractListType && ( diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx index 04aff0ea7e..7c9407bc02 100644 --- a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -1,13 +1,9 @@ import { Center, Spinner } from '@chakra-ui/react'; -import { useWeb3Modal } from '@web3modal/wagmi/react'; import React from 'react'; -import { useAccount, useSignMessage } from 'wagmi'; import type { Screen } from '../types'; -import useApiFetch from 'lib/api/useApiFetch'; -import getErrorMessage from 'lib/errors/getErrorMessage'; -import useToast from 'lib/hooks/useToast'; +import useSignInWithWallet from './useSignInWithWallet'; interface Props { onSuccess: (screen: Screen) => void; @@ -15,46 +11,24 @@ interface Props { } const AuthModalScreenConnectWallet = ({ onSuccess, onError }: Props) => { - const isSigningRef = React.useRef(false); + const isStartedRef = React.useRef(false); - const apiFetch = useApiFetch(); - const toast = useToast(); - const web3Modal = useWeb3Modal(); - const { isConnected, address } = useAccount(); - const { signMessageAsync } = useSignMessage(); + const handleSignInSuccess = React.useCallback(({ address }: { address: string }) => { + onSuccess({ type: 'success_created_wallet', address }); + }, [ onSuccess ]); - React.useEffect(() => { - !isConnected && web3Modal.open(); - }, [ isConnected, web3Modal ]); - - const proceedToAuth = React.useCallback(async(address: string) => { - try { - const siweMessage = await apiFetch('auth_siwe_message', { queryParams: { address } }) as { siwe_message: string }; - const signature = await signMessageAsync({ message: siweMessage.siwe_message }); - await apiFetch('auth_siwe_verify', { - fetchParams: { - method: 'POST', - body: { message: siweMessage.siwe_message, signature }, - }, - }); - onSuccess({ type: 'success_created_wallet', address }); - } catch (error) { - // TODO @tom2drum show better error message - onError(); - toast({ - status: 'error', - title: 'Error', - description: getErrorMessage(error) || 'Something went wrong', - }); - } - }, [ apiFetch, onError, onSuccess, signMessageAsync, toast ]); + const handleSignInError = React.useCallback(() => { + onError(); + }, [ onError ]); + + const { start } = useSignInWithWallet({ onSuccess: handleSignInSuccess, onError: handleSignInError }); React.useEffect(() => { - if (isConnected && address && !isSigningRef.current) { - isSigningRef.current = true; - proceedToAuth(address); + if (!isStartedRef.current) { + isStartedRef.current = true; + start(); } - }, [ address, isConnected, proceedToAuth ]); + }, [ start ]); return
; }; diff --git a/ui/snippets/auth/screens/useSignInWithWallet.tsx b/ui/snippets/auth/screens/useSignInWithWallet.tsx new file mode 100644 index 0000000000..761a07feff --- /dev/null +++ b/ui/snippets/auth/screens/useSignInWithWallet.tsx @@ -0,0 +1,67 @@ +import { useWeb3Modal } from '@web3modal/wagmi/react'; +import React from 'react'; +import { useSignMessage } from 'wagmi'; + +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import useToast from 'lib/hooks/useToast'; +import useAccount from 'lib/web3/useAccount'; + +interface Props { + onSuccess?: ({ address }: { address: string }) => void; + onError?: () => void; +} + +export default function useSignInWithWallet({ onSuccess, onError }: Props) { + const [ isPending, setIsPending ] = React.useState(false); + const isConnectingWalletRef = React.useRef(false); + + const apiFetch = useApiFetch(); + const toast = useToast(); + const web3Modal = useWeb3Modal(); + const { isConnected, address } = useAccount(); + const { signMessageAsync } = useSignMessage(); + + const proceedToAuth = React.useCallback(async(address: string) => { + try { + const siweMessage = await apiFetch('auth_siwe_message', { queryParams: { address } }) as { siwe_message: string }; + const signature = await signMessageAsync({ message: siweMessage.siwe_message }); + await apiFetch('auth_siwe_verify', { + fetchParams: { + method: 'POST', + body: { message: siweMessage.siwe_message, signature }, + }, + }); + onSuccess?.({ address }); + } catch (error) { + // TODO @tom2drum show better error message + onError?.(); + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + } finally { + setIsPending(false); + } + }, [ apiFetch, onError, onSuccess, signMessageAsync, toast ]); + + const start = React.useCallback(() => { + setIsPending(true); + if (address) { + proceedToAuth(address); + } else { + isConnectingWalletRef.current = true; + web3Modal.open(); + } + }, [ address, proceedToAuth, web3Modal ]); + + React.useEffect(() => { + if (address && isConnectingWalletRef.current) { + isConnectingWalletRef.current = false; + proceedToAuth(address); + } + }, [ address, isConnected, proceedToAuth ]); + + return React.useMemo(() => ({ start, isPending }), [ start, isPending ]); +} diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index 9467050690..056e546fb1 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -16,11 +16,12 @@ import { getUserHandle } from './utils'; interface Props { profileQuery: UseQueryResult; size?: ButtonProps['size']; - variant?: 'hero' | 'header'; + variant?: ButtonProps['variant']; onClick: () => void; + isPending?: boolean; } -const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: React.ForwardedRef) => { +const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Props, ref: React.ForwardedRef) => { const [ isFetched, setIsFetched ] = React.useState(false); const { data, isLoading } = profileQuery; const web3AccountWithDomain = useWeb3AccountWithDomain(!data?.address_hash); @@ -90,6 +91,8 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: Rea lineHeight={ 5 } px={ data ? 2.5 : 4 } fontWeight={ data ? 700 : 600 } + isLoading={ isPending } + loadingText="Connecting" > { content } diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx index bc3c876cd6..c858d7675f 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -1,23 +1,43 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type ButtonProps } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import Popover from 'ui/shared/chakra/Popover'; import AuthModal from 'ui/snippets/auth/AuthModal'; +import useSignInWithWallet from 'ui/snippets/auth/screens/useSignInWithWallet'; import ProfileButton from './ProfileButton'; import ProfileMenuContent from './ProfileMenuContent'; interface Props { buttonSize?: ButtonProps['size']; - isHomePage?: boolean; + buttonVariant?: ButtonProps['variant']; } -const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => { - const profileQuery = useFetchProfileInfo(); +const ProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { + const router = useRouter(); + const authModal = useDisclosure(); const profileMenu = useDisclosure(); + const profileQuery = useFetchProfileInfo(); + const signInWithWallet = useSignInWithWallet({}); + + const handleProfileButtonClick = React.useCallback(() => { + if (profileQuery.data) { + profileMenu.onOpen(); + return; + } + + if (router.pathname === '/apps/[id]') { + signInWithWallet.start(); + return; + } + + authModal.onOpen(); + }, [ profileQuery.data, router.pathname, authModal, profileMenu, signInWithWallet ]); + return ( <> @@ -25,8 +45,9 @@ const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => { { profileQuery.data && ( diff --git a/ui/snippets/profile/ProfileMobile.tsx b/ui/snippets/profile/ProfileMobile.tsx index eb98bfb0c7..3e8115bfbd 100644 --- a/ui/snippets/profile/ProfileMobile.tsx +++ b/ui/snippets/profile/ProfileMobile.tsx @@ -1,23 +1,43 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import AuthModal from 'ui/snippets/auth/AuthModal'; +import useSignInWithWallet from '../auth/screens/useSignInWithWallet'; import ProfileButton from './ProfileButton'; import ProfileMenuContent from './ProfileMenuContent'; const ProfileMobile = () => { - const profileQuery = useFetchProfileInfo(); + const router = useRouter(); + const authModal = useDisclosure(); const profileMenu = useDisclosure(); + const profileQuery = useFetchProfileInfo(); + const signInWithWallet = useSignInWithWallet({}); + + const handleProfileButtonClick = React.useCallback(() => { + if (profileQuery.data) { + profileMenu.onOpen(); + return; + } + + if (router.pathname === '/apps/[id]') { + signInWithWallet.start(); + return; + } + + authModal.onOpen(); + }, [ profileQuery.data, router.pathname, authModal, profileMenu, signInWithWallet ]); + return ( <> { profileQuery.data && ( Date: Thu, 19 Sep 2024 13:33:17 +0200 Subject: [PATCH 13/49] style pin input --- .eslintrc.js | 2 +- theme/components/PinInput.ts | 34 +++++++++++++++++++ theme/components/index.ts | 2 ++ ui/shared/chakra/PinInput.tsx | 10 ++++++ .../auth/fields/AuthModalFieldEmail.tsx | 1 + .../auth/fields/AuthModalFieldOtpCode.tsx | 8 +++-- 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 theme/components/PinInput.ts create mode 100644 ui/shared/chakra/PinInput.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 49e2ad4c43..b17253eb63 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,7 @@ const RESTRICTED_MODULES = { { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: '@chakra-ui/react', - importNames: [ 'Popover', 'Menu', 'useToast' ], + importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast' ], message: 'Please use corresponding component or hook from ui/shared/chakra component instead', }, { diff --git a/theme/components/PinInput.ts b/theme/components/PinInput.ts new file mode 100644 index 0000000000..251b38ca78 --- /dev/null +++ b/theme/components/PinInput.ts @@ -0,0 +1,34 @@ +import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; + +import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; + +const baseStyle = defineStyle({ + textAlign: 'center', + bgColor: 'dialog_bg', +}); + +const sizes = { + md: defineStyle({ + fontSize: 'md', + w: 10, + h: 10, + borderRadius: 'md', + }), +}; + +const variants = { + outline: defineStyle( + (props) => getOutlinedFieldStyles(props), + ), +}; + +const PinInput = defineStyleConfig({ + baseStyle, + sizes, + variants, + defaultProps: { + size: 'md', + }, +}); + +export default PinInput; diff --git a/theme/components/index.ts b/theme/components/index.ts index b32509ccaa..61903f55f6 100644 --- a/theme/components/index.ts +++ b/theme/components/index.ts @@ -10,6 +10,7 @@ import Input from './Input'; import Link from './Link'; import Menu from './Menu'; import Modal from './Modal'; +import PinInput from './PinInput'; import Popover from './Popover'; import Radio from './Radio'; import Select from './Select'; @@ -36,6 +37,7 @@ const components = { Link, Menu, Modal, + PinInput, Popover, Radio, Select, diff --git a/ui/shared/chakra/PinInput.tsx b/ui/shared/chakra/PinInput.tsx new file mode 100644 index 0000000000..0dee1c39bd --- /dev/null +++ b/ui/shared/chakra/PinInput.tsx @@ -0,0 +1,10 @@ +import type { PinInputProps, StyleProps } from '@chakra-ui/react'; +// eslint-disable-next-line no-restricted-imports +import { PinInput as PinInputBase } from '@chakra-ui/react'; +import React from 'react'; + +const PinInput = (props: PinInputProps & { bgColor?: StyleProps['bgColor'] }) => { + return ; +}; + +export default React.memo(PinInput); diff --git a/ui/snippets/auth/fields/AuthModalFieldEmail.tsx b/ui/snippets/auth/fields/AuthModalFieldEmail.tsx index ceb064b7e3..dc900b8a93 100644 --- a/ui/snippets/auth/fields/AuthModalFieldEmail.tsx +++ b/ui/snippets/auth/fields/AuthModalFieldEmail.tsx @@ -29,6 +29,7 @@ const AuthModalFieldEmail = ({ className }: Props) => { isInvalid={ Boolean(fieldState.error) } isDisabled={ isDisabled } autoComplete="off" + bgColor="dialog_bg" /> diff --git a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx index 43cb4bcf13..1299a22c54 100644 --- a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx +++ b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx @@ -1,9 +1,11 @@ -import { HStack, PinInput, PinInputField, Text } from '@chakra-ui/react'; +import { HStack, PinInputField, Text } from '@chakra-ui/react'; import React from 'react'; import { useController, useFormContext } from 'react-hook-form'; import type { OtpCodeFormFields } from '../types'; +import PinInput from 'ui/shared/chakra/PinInput'; + const CODE_LENGTH = 6; const AuthModalFieldOtpCode = () => { @@ -19,9 +21,9 @@ const AuthModalFieldOtpCode = () => { return ( <> - + { Array.from({ length: CODE_LENGTH }).map((_, index) => ( - + )) } From 9cc3d536d3496839dd5ea112a3ec3b01fcfef0b3 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 14:11:52 +0200 Subject: [PATCH 14/49] add logout --- lib/cookies.ts | 6 +++- .../screens/AuthModalScreenConnectWallet.tsx | 2 +- ui/snippets/auth/useLogout.tsx | 33 +++++++++++++++++++ .../{screens => }/useSignInWithWallet.tsx | 0 ui/snippets/profile/ProfileButton.tsx | 6 ++-- ui/snippets/profile/ProfileDesktop.tsx | 2 +- ui/snippets/profile/ProfileMenuContent.tsx | 4 ++- ui/snippets/profile/ProfileMobile.tsx | 2 +- 8 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 ui/snippets/auth/useLogout.tsx rename ui/snippets/auth/{screens => }/useSignInWithWallet.tsx (100%) diff --git a/lib/cookies.ts b/lib/cookies.ts index cef6a38a42..a1a441c3b8 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -28,12 +28,16 @@ export function get(name?: NAMES | undefined | null, serverCookie?: string) { } } -export function set(name: string, value: string, attributes: Cookies.CookieAttributes = {}) { +export function set(name: NAMES, value: string, attributes: Cookies.CookieAttributes = {}) { attributes.path = '/'; return Cookies.set(name, value, attributes); } +export function remove(name: NAMES, attributes: Cookies.CookieAttributes = {}) { + return Cookies.remove(name, attributes); +} + export function getFromCookieString(cookieString: string, name?: NAMES | undefined | null) { return cookieString.split(`${ name }=`)[1]?.split(';')[0]; } diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx index 7c9407bc02..d2d9efc661 100644 --- a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { Screen } from '../types'; -import useSignInWithWallet from './useSignInWithWallet'; +import useSignInWithWallet from '../useSignInWithWallet'; interface Props { onSuccess: (screen: Screen) => void; diff --git a/ui/snippets/auth/useLogout.tsx b/ui/snippets/auth/useLogout.tsx new file mode 100644 index 0000000000..d4ca6d8a14 --- /dev/null +++ b/ui/snippets/auth/useLogout.tsx @@ -0,0 +1,33 @@ +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { Route } from 'nextjs-routes'; + +import * as cookies from 'lib/cookies'; + +const PROTECTED_ROUTES: Array = [ + '/account/api-key', + '/account/custom-abi', + '/account/tag-address', + '/account/verified-addresses', + '/account/watchlist', + '/auth/profile', + '/auth/unverified-email', +]; + +export default function useLogout() { + const router = useRouter(); + + return React.useCallback(async() => { + cookies.remove(cookies.NAMES.API_TOKEN); + + if ( + PROTECTED_ROUTES.includes(router.pathname) || + (router.pathname === '/txs' && router.query.tab === 'watchlist') + ) { + window.location.assign('/'); + } else { + window.location.reload(); + } + }, [ router ]); +} diff --git a/ui/snippets/auth/screens/useSignInWithWallet.tsx b/ui/snippets/auth/useSignInWithWallet.tsx similarity index 100% rename from ui/snippets/auth/screens/useSignInWithWallet.tsx rename to ui/snippets/auth/useSignInWithWallet.tsx diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index 056e546fb1..7e68bc0f24 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -1,5 +1,5 @@ import type { ButtonProps } from '@chakra-ui/react'; -import { Button, Skeleton, Tooltip, Text, HStack } from '@chakra-ui/react'; +import { Button, Skeleton, Tooltip, Box, HStack } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; @@ -55,7 +55,7 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop return ( - { text } + { text } ); } @@ -64,7 +64,7 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop return ( - { getUserHandle(data.email) } + { getUserHandle(data.email) } ); } diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx index c858d7675f..60a5dded44 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -5,7 +5,7 @@ import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import Popover from 'ui/shared/chakra/Popover'; import AuthModal from 'ui/snippets/auth/AuthModal'; -import useSignInWithWallet from 'ui/snippets/auth/screens/useSignInWithWallet'; +import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet'; import ProfileButton from './ProfileButton'; import ProfileMenuContent from './ProfileMenuContent'; diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx index f48cc91ded..deca45c6c7 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -9,6 +9,7 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; import IconSvg from 'ui/shared/IconSvg'; +import useLogout from 'ui/snippets/auth/useLogout'; import ProfileMenuNavLink from './ProfileMenuNavLink'; import ProfileMenuWallet from './ProfileMenuWallet'; @@ -50,6 +51,7 @@ interface Props { const ProfileMenuContent = ({ data, onClose }: Props) => { const { isAutoConnectDisabled } = useMarketplaceContext(); const alertBgColor = useColorModeValue('orange.100', 'orange.900'); + const logout = useLogout(); return ( @@ -101,7 +103,7 @@ const ProfileMenuContent = ({ data, onClose }: Props) => { ); diff --git a/ui/snippets/profile/ProfileMobile.tsx b/ui/snippets/profile/ProfileMobile.tsx index 3e8115bfbd..1d0e8ca3fa 100644 --- a/ui/snippets/profile/ProfileMobile.tsx +++ b/ui/snippets/profile/ProfileMobile.tsx @@ -4,8 +4,8 @@ import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import AuthModal from 'ui/snippets/auth/AuthModal'; +import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet'; -import useSignInWithWallet from '../auth/screens/useSignInWithWallet'; import ProfileButton from './ProfileButton'; import ProfileMenuContent from './ProfileMenuContent'; From 1878c18ba80b8276e19a742c02c6885867dcab17 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 16:10:50 +0200 Subject: [PATCH 15/49] handle case when account is disabled --- ui/home/HeroBanner.tsx | 6 +- ui/marketplace/MarketplaceAppTopBar.tsx | 6 +- ui/snippets/header/HeaderDesktop.tsx | 10 +-- ui/snippets/header/HeaderMobile.tsx | 7 +- .../horizontal/NavigationDesktop.tsx | 10 +-- ui/snippets/profile/ProfileButton.tsx | 7 +- ui/snippets/profile/ProfileMenuContent.tsx | 26 +------ .../wallet/WalletAutoConnectDisabledAlert.tsx | 31 +++++++++ ui/snippets/wallet/WalletButton.tsx | 66 ++++++++++++++++++ ui/snippets/wallet/WalletDesktop.tsx | 68 +++++++++++++++++++ ui/snippets/wallet/WalletMenuContent.tsx | 57 ++++++++++++++++ ui/snippets/wallet/WalletMobile.tsx | 66 ++++++++++++++++++ 12 files changed, 322 insertions(+), 38 deletions(-) create mode 100644 ui/snippets/wallet/WalletAutoConnectDisabledAlert.tsx create mode 100644 ui/snippets/wallet/WalletButton.tsx create mode 100644 ui/snippets/wallet/WalletDesktop.tsx create mode 100644 ui/snippets/wallet/WalletMenuContent.tsx create mode 100644 ui/snippets/wallet/WalletMobile.tsx diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index e024567e2c..b12df6f579 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -5,6 +5,7 @@ import config from 'configs/app'; import AdBanner from 'ui/shared/ad/AdBanner'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; +import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; const BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; const TEXT_COLOR_DEFAULT = 'white'; @@ -53,7 +54,10 @@ const HeroBanner = () => { { config.UI.navigation.layout === 'vertical' && ( - { config.features.account.isEnabled && } + { + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) + } ) } diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index d9cd1b8d35..9adc267078 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -13,6 +13,7 @@ import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; +import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; import AppSecurityReport from './AppSecurityReport'; import ContractListModal from './ContractListModal'; @@ -98,7 +99,10 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) /> { !isMobile && ( - { config.features.account.isEnabled && } + { + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) + } ) } diff --git a/ui/snippets/header/HeaderDesktop.tsx b/ui/snippets/header/HeaderDesktop.tsx index 57aa12dc1c..690d6ea6a9 100644 --- a/ui/snippets/header/HeaderDesktop.tsx +++ b/ui/snippets/header/HeaderDesktop.tsx @@ -4,9 +4,8 @@ import React from 'react'; import config from 'configs/app'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; +import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; import Burger from './Burger'; @@ -39,9 +38,10 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => {
{ config.UI.navigation.layout === 'vertical' && ( - { config.features.account.isEnabled && } - { config.features.blockchainInteraction.isEnabled && } - { config.features.account.isEnabled && } + { + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) + } ) } diff --git a/ui/snippets/header/HeaderMobile.tsx b/ui/snippets/header/HeaderMobile.tsx index 41ea1a7f92..288ffbe6f9 100644 --- a/ui/snippets/header/HeaderMobile.tsx +++ b/ui/snippets/header/HeaderMobile.tsx @@ -7,6 +7,7 @@ import { useScrollDirection } from 'lib/contexts/scrollDirection'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import ProfileMobile from 'ui/snippets/profile/ProfileMobile'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; +import WalletMobile from 'ui/snippets/wallet/WalletMobile'; import Burger from './Burger'; @@ -47,7 +48,11 @@ const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => { - { config.features.account.isEnabled ? : } + { + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) || + + } { !hideSearchBar && searchBar } diff --git a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx index b3b224f987..238347c109 100644 --- a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx +++ b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx @@ -6,8 +6,7 @@ import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; -import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; +import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; import TestnetBadge from '../TestnetBadge'; import NavLink from './NavLink'; @@ -39,9 +38,10 @@ const NavigationDesktop = () => { }) } - { config.features.account.isEnabled && } - { config.features.blockchainInteraction.isEnabled && } - { config.features.account.isEnabled && } + { + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) + }
); diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/profile/ProfileButton.tsx index 7e68bc0f24..984dddbc7b 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/profile/ProfileButton.tsx @@ -6,6 +6,7 @@ import React from 'react'; import type { UserInfo } from 'types/api/account'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import useIsMobile from 'lib/hooks/useIsMobile'; import shortenString from 'lib/shortenString'; import IconSvg from 'ui/shared/IconSvg'; @@ -23,6 +24,8 @@ interface Props { const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Props, ref: React.ForwardedRef) => { const [ isFetched, setIsFetched ] = React.useState(false); + const isMobile = useIsMobile(); + const { data, isLoading } = profileQuery; const web3AccountWithDomain = useWeb3AccountWithDomain(!data?.address_hash); const { isAutoConnectDisabled } = useMarketplaceContext(); @@ -77,7 +80,7 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop label={ Sign in to My Account to add tags,
create watchlists, access API keys and more
} textAlign="center" padding={ 2 } - isDisabled={ isFetched || Boolean(data) } + isDisabled={ isMobile || isFetched || Boolean(data) } openDelay={ 500 } > @@ -92,7 +95,7 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop px={ data ? 2.5 : 4 } fontWeight={ data ? 700 : 600 } isLoading={ isPending } - loadingText="Connecting" + loadingText={ isMobile ? undefined : 'Connecting' } > { content } diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx index deca45c6c7..e33377d141 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, Flex, Text, VStack, useColorModeValue } from '@chakra-ui/react'; +import { Box, Divider, Flex, Text, VStack } from '@chakra-ui/react'; import React from 'react'; import type { NavLink } from './types'; @@ -8,8 +8,8 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; -import IconSvg from 'ui/shared/IconSvg'; import useLogout from 'ui/snippets/auth/useLogout'; +import WalletAutoConnectDisabledAlert from 'ui/snippets/wallet/WalletAutoConnectDisabledAlert'; import ProfileMenuNavLink from './ProfileMenuNavLink'; import ProfileMenuWallet from './ProfileMenuWallet'; @@ -50,31 +50,11 @@ interface Props { const ProfileMenuContent = ({ data, onClose }: Props) => { const { isAutoConnectDisabled } = useMarketplaceContext(); - const alertBgColor = useColorModeValue('orange.100', 'orange.900'); const logout = useLogout(); return ( - { isAutoConnectDisabled && ( - - - - Connect your wallet in the app below - - - ) } + { isAutoConnectDisabled && } { + const bgColor = useColorModeValue('orange.100', 'orange.900'); + + return ( + + + + Connect your wallet in the app below + + + ); +}; + +export default React.memo(WalletAutoConnectDisabledAlert); diff --git a/ui/snippets/wallet/WalletButton.tsx b/ui/snippets/wallet/WalletButton.tsx new file mode 100644 index 0000000000..f5b5de548b --- /dev/null +++ b/ui/snippets/wallet/WalletButton.tsx @@ -0,0 +1,66 @@ +import type { ButtonProps } from '@chakra-ui/react'; +import { Button, Box, HStack, Tooltip } from '@chakra-ui/react'; +import React from 'react'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import shortenString from 'lib/shortenString'; +import ProfileAddressIcon from 'ui/snippets/profile/ProfileAddressIcon'; + +interface Props { + size?: ButtonProps['size']; + variant?: ButtonProps['variant']; + onClick?: () => void; + isPending?: boolean; + isAutoConnectDisabled?: boolean; + address?: string; + domain?: string; +} + +const WalletButton = ({ size, variant, onClick, isPending, isAutoConnectDisabled, address, domain }: Props, ref: React.ForwardedRef) => { + + const isMobile = useIsMobile(); + + const content = (() => { + if (!address) { + return 'Connect'; + } + + const text = domain || shortenString(address); + + return ( + + + { text } + + ); + })(); + + return ( + Connect your wallet
to Blockscout for
full-featured access } + textAlign="center" + padding={ 2 } + isDisabled={ isMobile || Boolean(address) } + openDelay={ 500 } + > + +
+ ); +}; + +export default React.memo(React.forwardRef(WalletButton)); diff --git a/ui/snippets/wallet/WalletDesktop.tsx b/ui/snippets/wallet/WalletDesktop.tsx new file mode 100644 index 0000000000..cde57955c9 --- /dev/null +++ b/ui/snippets/wallet/WalletDesktop.tsx @@ -0,0 +1,68 @@ +import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type ButtonProps } from '@chakra-ui/react'; +import React from 'react'; + +import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import Popover from 'ui/shared/chakra/Popover'; +import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; +import useWallet from 'ui/snippets/walletMenu/useWallet'; + +import WalletButton from './WalletButton'; +import WalletMenuContent from './WalletMenuContent'; + +interface Props { + buttonSize?: ButtonProps['size']; + buttonVariant?: ButtonProps['variant']; +} + +const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { + const walletMenu = useDisclosure(); + + const walletUtils = useWallet({ source: 'Header' }); + const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); + const { isAutoConnectDisabled } = useMarketplaceContext(); + + const isPending = + (walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || + (!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); + + const handleOpenWalletClick = React.useCallback(() => { + walletUtils.openModal(); + walletMenu.onClose(); + }, [ walletUtils, walletMenu ]); + + const handleDisconnectClick = React.useCallback(() => { + walletUtils.disconnect(); + walletMenu.onClose(); + }, [ walletUtils, walletMenu ]); + + return ( + + + + + { web3AccountWithDomain.address && ( + + + + + + ) } + + ); +}; + +export default React.memo(WalletDesktop); diff --git a/ui/snippets/wallet/WalletMenuContent.tsx b/ui/snippets/wallet/WalletMenuContent.tsx new file mode 100644 index 0000000000..7f7dbecf75 --- /dev/null +++ b/ui/snippets/wallet/WalletMenuContent.tsx @@ -0,0 +1,57 @@ +import { Box, Button, Flex, IconButton, Text } from '@chakra-ui/react'; +import React from 'react'; + +import delay from 'lib/delay'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import IconSvg from 'ui/shared/IconSvg'; + +import WalletAutoConnectDisabledAlert from './WalletAutoConnectDisabledAlert'; + +interface Props { + address: string; + domain?: string; + isAutoConnectDisabled?: boolean; + onDisconnect: () => void; + onOpenWallet: () => void; +} + +const WalletMenuContent = ({ isAutoConnectDisabled, address, domain, onDisconnect, onOpenWallet }: Props) => { + + const handleOpenWalletClick = React.useCallback(async() => { + await delay(100); + onOpenWallet(); + }, [ onOpenWallet ]); + + return ( + + { isAutoConnectDisabled && } + My wallet + + Your wallet is used to interact with apps and contracts in the explorer. + + + + } + variant="simple" + color="icon_info" + boxSize={ 5 } + onClick={ handleOpenWalletClick } + flexShrink={ 0 } + /> + + + + ); +}; + +export default React.memo(WalletMenuContent); diff --git a/ui/snippets/wallet/WalletMobile.tsx b/ui/snippets/wallet/WalletMobile.tsx new file mode 100644 index 0000000000..17431ea1db --- /dev/null +++ b/ui/snippets/wallet/WalletMobile.tsx @@ -0,0 +1,66 @@ +import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; +import useWallet from 'ui/snippets/walletMenu/useWallet'; + +import WalletButton from './WalletButton'; +import WalletMenuContent from './WalletMenuContent'; + +const WalletMobile = () => { + const walletMenu = useDisclosure(); + + const walletUtils = useWallet({ source: 'Header' }); + const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); + const { isAutoConnectDisabled } = useMarketplaceContext(); + + const isPending = + (walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || + (!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); + + const handleOpenWalletClick = React.useCallback(() => { + walletUtils.openModal(); + walletMenu.onClose(); + }, [ walletUtils, walletMenu ]); + + const handleDisconnectClick = React.useCallback(() => { + walletUtils.disconnect(); + walletMenu.onClose(); + }, [ walletUtils, walletMenu ]); + + return ( + <> + + { web3AccountWithDomain.address && ( + + + + + + + + + ) } + + ); +}; + +export default React.memo(WalletMobile); From 4b18a6563c868f55e7c3bebb7dfb06f2d0746364 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 16:29:16 +0200 Subject: [PATCH 16/49] handle case when wc is disabled --- configs/app/features/account.ts | 1 + ui/pages/MyProfile.tsx | 3 ++- ui/snippets/auth/useSignInWithWallet.tsx | 9 ++++++++- ui/snippets/profile/ProfileDesktop.tsx | 8 +++++++- ui/snippets/profile/ProfileMenuContent.tsx | 2 +- ui/snippets/profile/ProfileMenuWallet.tsx | 5 ----- ui/snippets/profile/ProfileMobile.tsx | 8 +++++++- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/configs/app/features/account.ts b/configs/app/features/account.ts index 120a94b7e9..34d07d95a5 100644 --- a/configs/app/features/account.ts +++ b/configs/app/features/account.ts @@ -28,6 +28,7 @@ const logoutUrl = (() => { })(); const title = 'My account'; +// TODO @tom2drum add condition for recaptcha const config: Feature<{ authUrl: string; logoutUrl: string }> = (() => { if ( diff --git a/ui/pages/MyProfile.tsx b/ui/pages/MyProfile.tsx index bc7fe5d58e..0b12b7ce3c 100644 --- a/ui/pages/MyProfile.tsx +++ b/ui/pages/MyProfile.tsx @@ -1,6 +1,7 @@ import { Flex } from '@chakra-ui/react'; import React from 'react'; +import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; import MyProfileEmail from 'ui/myProfile/MyProfileEmail'; @@ -25,7 +26,7 @@ const MyProfile = () => { return ( - + { config.features.blockchainInteraction.isEnabled && } ); })(); diff --git a/ui/snippets/auth/useSignInWithWallet.tsx b/ui/snippets/auth/useSignInWithWallet.tsx index 761a07feff..6258052b8c 100644 --- a/ui/snippets/auth/useSignInWithWallet.tsx +++ b/ui/snippets/auth/useSignInWithWallet.tsx @@ -2,6 +2,7 @@ import { useWeb3Modal } from '@web3modal/wagmi/react'; import React from 'react'; import { useSignMessage } from 'wagmi'; +import config from 'configs/app'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; import useToast from 'lib/hooks/useToast'; @@ -12,7 +13,7 @@ interface Props { onError?: () => void; } -export default function useSignInWithWallet({ onSuccess, onError }: Props) { +function useSignInWithWallet({ onSuccess, onError }: Props) { const [ isPending, setIsPending ] = React.useState(false); const isConnectingWalletRef = React.useRef(false); @@ -65,3 +66,9 @@ export default function useSignInWithWallet({ onSuccess, onError }: Props) { return React.useMemo(() => ({ start, isPending }), [ start, isPending ]); } + +function useSignInWithWalletFallback() { + return React.useMemo(() => ({ start: () => {}, isPending: false }), [ ]); +} + +export default config.features.blockchainInteraction.isEnabled ? useSignInWithWallet : useSignInWithWalletFallback; diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/profile/ProfileDesktop.tsx index 60a5dded44..d690dc51a4 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/profile/ProfileDesktop.tsx @@ -2,6 +2,7 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button import { useRouter } from 'next/router'; import React from 'react'; +import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import Popover from 'ui/shared/chakra/Popover'; import AuthModal from 'ui/snippets/auth/AuthModal'; @@ -58,7 +59,12 @@ const ProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { ) } - { authModal.isOpen && } + { authModal.isOpen && ( + + ) } ); }; diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/profile/ProfileMenuContent.tsx index e33377d141..fc266f323d 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/profile/ProfileMenuContent.tsx @@ -66,7 +66,7 @@ const ProfileMenuContent = ({ data, onClose }: Props) => { { data?.email && { getUserHandle(data.email) } }
- + { config.features.blockchainInteraction.isEnabled ? : } { navLinks.map((item) => ( diff --git a/ui/snippets/profile/ProfileMenuWallet.tsx b/ui/snippets/profile/ProfileMenuWallet.tsx index a44d23ad77..2ea4d61ed3 100644 --- a/ui/snippets/profile/ProfileMenuWallet.tsx +++ b/ui/snippets/profile/ProfileMenuWallet.tsx @@ -1,7 +1,6 @@ import { Button, Divider, Flex, IconButton } from '@chakra-ui/react'; import React from 'react'; -import config from 'configs/app'; import delay from 'lib/delay'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; @@ -30,10 +29,6 @@ const ProfileMenuWallet = ({ onClose }: Props) => { onClose?.(); }, [ wallet, onClose ]); - if (!config.features.blockchainInteraction.isEnabled) { - return ; - } - if (wallet.isWalletConnected && web3AccountWithDomain.address) { return ( <> diff --git a/ui/snippets/profile/ProfileMobile.tsx b/ui/snippets/profile/ProfileMobile.tsx index 1d0e8ca3fa..0337d1ef20 100644 --- a/ui/snippets/profile/ProfileMobile.tsx +++ b/ui/snippets/profile/ProfileMobile.tsx @@ -2,6 +2,7 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from import { useRouter } from 'next/router'; import React from 'react'; +import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import AuthModal from 'ui/snippets/auth/AuthModal'; import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet'; @@ -54,7 +55,12 @@ const ProfileMobile = () => { ) } - { authModal.isOpen && } + { authModal.isOpen && ( + + ) } ); }; From 5b91364b357e8247b0b65eee92f088251a949752 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 19 Sep 2024 19:20:31 +0200 Subject: [PATCH 17/49] remove old components --- lib/hooks/useLoginUrl.tsx | 1 + lib/hooks/useRedirectForInvalidAuthToken.tsx | 1 + lib/mixpanel/utils.ts | 4 +- .../useWallet.tsx => lib/web3/useWallet.ts | 25 ++- mocks/user/profile.ts | 8 +- .../methods/ContractConnectWallet.tsx | 16 +- ui/home/HeroBanner.pw.tsx | 3 +- ui/marketplace/useAutoConnectWallet.tsx | 14 +- .../horizontal/NavigationDesktop.pw.tsx | 3 +- ui/snippets/profile/ProfileMenuWallet.tsx | 20 ++- .../profileMenu/ProfileMenuContent.tsx | 72 --------- .../profileMenu/ProfileMenuDesktop.pw.tsx | 35 ----- .../profileMenu/ProfileMenuDesktop.tsx | 85 ---------- .../profileMenu/ProfileMenuMobile.pw.tsx | 40 ----- ui/snippets/profileMenu/ProfileMenuMobile.tsx | 74 --------- ...w.tsx_dark-color-mode_auth-dark-mode-1.png | Bin 24946 -> 0 bytes ...esktop.pw.tsx_default_auth-dark-mode-1.png | Bin 25393 -> 0 bytes ...Mobile.pw.tsx_default_auth-base-view-1.png | Bin 23197 -> 0 bytes ...ileMenuMobile.pw.tsx_default_no-auth-1.png | Bin 2261 -> 0 bytes ui/snippets/wallet/WalletDesktop.tsx | 20 +-- ui/snippets/wallet/WalletMobile.tsx | 20 +-- ui/snippets/walletMenu/WalletIdenticon.tsx | 45 ------ ui/snippets/walletMenu/WalletMenuContent.tsx | 102 ------------ .../walletMenu/WalletMenuDesktop.pw.tsx | 70 --------- ui/snippets/walletMenu/WalletMenuDesktop.tsx | 147 ------------------ .../walletMenu/WalletMenuMobile.pw.tsx | 58 ------- ui/snippets/walletMenu/WalletMenuMobile.tsx | 118 -------------- ui/snippets/walletMenu/WalletTooltip.tsx | 83 ---------- ...olor-mode_wallet-connected-dark-mode-1.png | Bin 16655 -> 0 bytes ...wallet-connected-home-page-dark-mode-1.png | Bin 16915 -> 0 bytes ...de_wallet-is-not-connected-dark-mode-1.png | Bin 2490 -> 0 bytes ...is-not-connected-home-page-dark-mode-1.png | Bin 2288 -> 0 bytes ...x_default_wallet-connected-dark-mode-1.png | Bin 16432 -> 0 bytes ...wallet-connected-home-page-dark-mode-1.png | Bin 16441 -> 0 bytes ...top.pw.tsx_default_wallet-is-loading-1.png | Bin 2137 -> 0 bytes ...lt_wallet-is-not-connected-dark-mode-1.png | Bin 2372 -> 0 bytes ...is-not-connected-home-page-dark-mode-1.png | Bin 2261 -> 0 bytes ...sx_default_wallet-with-ENS-connected-1.png | Bin 14734 -> 0 bytes ...olor-mode_wallet-connected-dark-mode-1.png | Bin 16546 -> 0 bytes ...de_wallet-is-not-connected-dark-mode-1.png | Bin 3679 -> 0 bytes ...x_default_wallet-connected-dark-mode-1.png | Bin 16017 -> 0 bytes ...ile.pw.tsx_default_wallet-is-loading-1.png | Bin 3161 -> 0 bytes ...lt_wallet-is-not-connected-dark-mode-1.png | Bin 3646 -> 0 bytes ...sx_default_wallet-with-ENS-connected-1.png | Bin 14517 -> 0 bytes 44 files changed, 68 insertions(+), 996 deletions(-) rename ui/snippets/walletMenu/useWallet.tsx => lib/web3/useWallet.ts (76%) delete mode 100644 ui/snippets/profileMenu/ProfileMenuContent.tsx delete mode 100644 ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx delete mode 100644 ui/snippets/profileMenu/ProfileMenuDesktop.tsx delete mode 100644 ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx delete mode 100644 ui/snippets/profileMenu/ProfileMenuMobile.tsx delete mode 100644 ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png delete mode 100644 ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_default_auth-dark-mode-1.png delete mode 100644 ui/snippets/profileMenu/__screenshots__/ProfileMenuMobile.pw.tsx_default_auth-base-view-1.png delete mode 100644 ui/snippets/profileMenu/__screenshots__/ProfileMenuMobile.pw.tsx_default_no-auth-1.png delete mode 100644 ui/snippets/walletMenu/WalletIdenticon.tsx delete mode 100644 ui/snippets/walletMenu/WalletMenuContent.tsx delete mode 100644 ui/snippets/walletMenu/WalletMenuDesktop.pw.tsx delete mode 100644 ui/snippets/walletMenu/WalletMenuDesktop.tsx delete mode 100644 ui/snippets/walletMenu/WalletMenuMobile.pw.tsx delete mode 100644 ui/snippets/walletMenu/WalletMenuMobile.tsx delete mode 100644 ui/snippets/walletMenu/WalletTooltip.tsx delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-connected-home-page-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-is-not-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-is-not-connected-home-page-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-home-page-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-is-loading-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-is-not-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-is-not-connected-home-page-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-with-ENS-connected-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_dark-color-mode_wallet-is-not-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-loading-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-not-connected-dark-mode-1.png delete mode 100644 ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-with-ENS-connected-1.png diff --git a/lib/hooks/useLoginUrl.tsx b/lib/hooks/useLoginUrl.tsx index a111d1eebb..cbfc5ea7c8 100644 --- a/lib/hooks/useLoginUrl.tsx +++ b/lib/hooks/useLoginUrl.tsx @@ -6,6 +6,7 @@ import config from 'configs/app'; const feature = config.features.account; +// TODO @tom2drum remove this hook export default function useLoginUrl() { const router = useRouter(); return feature.isEnabled ? diff --git a/lib/hooks/useRedirectForInvalidAuthToken.tsx b/lib/hooks/useRedirectForInvalidAuthToken.tsx index 24b74ace64..41a37c452b 100644 --- a/lib/hooks/useRedirectForInvalidAuthToken.tsx +++ b/lib/hooks/useRedirectForInvalidAuthToken.tsx @@ -7,6 +7,7 @@ import type { ResourceError } from 'lib/api/resources'; import * as cookies from 'lib/cookies'; import useLoginUrl from 'lib/hooks/useLoginUrl'; +// TODO @tom2drum remove or revise this hook export default function useRedirectForInvalidAuthToken() { const queryClient = useQueryClient(); diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index 4d59ec007e..57283d8e2b 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -6,11 +6,11 @@ export enum EventTypes { SEARCH_QUERY = 'Search query', LOCAL_SEARCH = 'Local search', ADD_TO_WALLET = 'Add to wallet', - ACCOUNT_ACCESS = 'Account access', + ACCOUNT_ACCESS = 'Account access', // deprecated PRIVATE_TAG = 'Private tag', VERIFY_ADDRESS = 'Verify address', VERIFY_TOKEN = 'Verify token', - WALLET_CONNECT = 'Wallet connect', + WALLET_CONNECT = 'Wallet connect', // 🦆 WALLET_ACTION = 'Wallet action', CONTRACT_INTERACTION = 'Contract interaction', CONTRACT_VERIFICATION = 'Contract verification', diff --git a/ui/snippets/walletMenu/useWallet.tsx b/lib/web3/useWallet.ts similarity index 76% rename from ui/snippets/walletMenu/useWallet.tsx rename to lib/web3/useWallet.ts index 9b252dded9..379ca7a5f1 100644 --- a/ui/snippets/walletMenu/useWallet.tsx +++ b/lib/web3/useWallet.ts @@ -8,11 +8,11 @@ interface Params { source: mixpanel.EventPayload['Source']; } -export default function useWallet({ source }: Params) { - const { open } = useWeb3Modal(); +export default function useWeb3Wallet({ source }: Params) { + const { open: openModal } = useWeb3Modal(); const { open: isOpen } = useWeb3ModalState(); const { disconnect } = useDisconnect(); - const [ isModalOpening, setIsModalOpening ] = React.useState(false); + const [ isOpening, setIsOpening ] = React.useState(false); const [ isClientLoaded, setIsClientLoaded ] = React.useState(false); const isConnectionStarted = React.useRef(false); @@ -21,12 +21,12 @@ export default function useWallet({ source }: Params) { }, []); const handleConnect = React.useCallback(async() => { - setIsModalOpening(true); - await open(); - setIsModalOpening(false); + setIsOpening(true); + await openModal(); + setIsOpening(false); mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' }); isConnectionStarted.current = true; - }, [ open, source ]); + }, [ openModal, source ]); const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { if (!isReconnected && isConnectionStarted.current) { @@ -46,15 +46,14 @@ export default function useWallet({ source }: Params) { const { address, isDisconnected } = useAccount(); - const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; + const isConnected = isClientLoaded && !isDisconnected && address !== undefined; return { - openModal: open, - isWalletConnected, - address: address || '', connect: handleConnect, disconnect: handleDisconnect, - isModalOpening, - isModalOpen: isOpen, + isOpen: isOpening || isOpen, + isConnected, + address, + openModal, }; } diff --git a/mocks/user/profile.ts b/mocks/user/profile.ts index 955f872e01..e0178bc140 100644 --- a/mocks/user/profile.ts +++ b/mocks/user/profile.ts @@ -1,13 +1,17 @@ -export const base = { +import type { UserInfo } from 'types/api/account'; + +export const base: UserInfo = { avatar: 'https://avatars.githubusercontent.com/u/22130104', email: 'tom@ohhhh.me', name: 'tom goriunov', nickname: 'tom2drum', + address_hash: null, }; -export const withoutEmail = { +export const withoutEmail: UserInfo = { avatar: 'https://avatars.githubusercontent.com/u/22130104', email: null, name: 'tom goriunov', nickname: 'tom2drum', + address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', }; diff --git a/ui/address/contract/methods/ContractConnectWallet.tsx b/ui/address/contract/methods/ContractConnectWallet.tsx index 9fc87de735..c00bca97a3 100644 --- a/ui/address/contract/methods/ContractConnectWallet.tsx +++ b/ui/address/contract/methods/ContractConnectWallet.tsx @@ -3,28 +3,28 @@ import React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; +import useWeb3Wallet from 'lib/web3/useWallet'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import useWallet from 'ui/snippets/walletMenu/useWallet'; interface Props { isLoading?: boolean; } const ContractConnectWallet = ({ isLoading }: Props) => { - const { isModalOpening, isModalOpen, connect, disconnect, address, isWalletConnected } = useWallet({ source: 'Smart contracts' }); + const web3Wallet = useWeb3Wallet({ source: 'Smart contracts' }); const isMobile = useIsMobile(); const content = (() => { - if (!isWalletConnected) { + if (!web3Wallet.isConnected) { return ( <> Disconnected + ); })(); return ( - + { content } diff --git a/ui/home/HeroBanner.pw.tsx b/ui/home/HeroBanner.pw.tsx index ea9735a78f..9e510644fc 100644 --- a/ui/home/HeroBanner.pw.tsx +++ b/ui/home/HeroBanner.pw.tsx @@ -12,7 +12,7 @@ const authTest = test.extend<{ context: BrowserContext }>({ context: contextWithAuth, }); -authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiResponse, mockAssetResponse }) => { +authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiResponse }) => { const IMAGE_URL = 'https://localhost:3000/my-image.png'; await mockEnvs([ @@ -28,7 +28,6 @@ authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiRes }); await mockApiResponse('user_info', profileMock.base); - await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg'); const component = await render(); diff --git a/ui/marketplace/useAutoConnectWallet.tsx b/ui/marketplace/useAutoConnectWallet.tsx index 0321694f81..52b5a0c8ee 100644 --- a/ui/marketplace/useAutoConnectWallet.tsx +++ b/ui/marketplace/useAutoConnectWallet.tsx @@ -3,11 +3,11 @@ import { useEffect, useRef } from 'react'; import removeQueryParam from 'lib/router/removeQueryParam'; import updateQueryParam from 'lib/router/updateQueryParam'; -import useWallet from 'ui/snippets/walletMenu/useWallet'; +import useWeb3Wallet from 'lib/web3/useWallet'; export default function useAutoConnectWallet() { const router = useRouter(); - const { isWalletConnected, isModalOpen, connect } = useWallet({ source: 'Swap button' }); + const web3Wallet = useWeb3Wallet({ source: 'Swap button' }); const isConnectionStarted = useRef(false); useEffect(() => { @@ -17,11 +17,11 @@ export default function useAutoConnectWallet() { let timer: ReturnType; - if (!isWalletConnected && !isModalOpen) { + if (!web3Wallet.isConnected && !web3Wallet.isOpen) { if (!isConnectionStarted.current) { timer = setTimeout(() => { - if (!isWalletConnected) { - connect(); + if (!web3Wallet.isConnected) { + web3Wallet.connect(); isConnectionStarted.current = true; } }, 500); @@ -29,11 +29,11 @@ export default function useAutoConnectWallet() { isConnectionStarted.current = false; updateQueryParam(router, 'action', 'tooltip'); } - } else if (isWalletConnected) { + } else if (web3Wallet.isConnected) { isConnectionStarted.current = false; removeQueryParam(router, 'action'); } return () => clearTimeout(timer); - }, [ isWalletConnected, isModalOpen, connect, router ]); + }, [ router, web3Wallet ]); } diff --git a/ui/snippets/navigation/horizontal/NavigationDesktop.pw.tsx b/ui/snippets/navigation/horizontal/NavigationDesktop.pw.tsx index 60b1710024..7b03175fe5 100644 --- a/ui/snippets/navigation/horizontal/NavigationDesktop.pw.tsx +++ b/ui/snippets/navigation/horizontal/NavigationDesktop.pw.tsx @@ -12,7 +12,7 @@ const testWithAuth = test.extend<{ context: BrowserContext }>({ context: contextWithAuth, }); -testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAssetResponse, mockEnvs, page }) => { +testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockEnvs, page }) => { const hooksConfig = { router: { route: '/blocks', @@ -21,7 +21,6 @@ testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAsset }; await mockApiResponse('user_info', profileMock.base); - await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg'); await mockEnvs([ ...ENVS_MAP.userOps, ...ENVS_MAP.nameService, diff --git a/ui/snippets/profile/ProfileMenuWallet.tsx b/ui/snippets/profile/ProfileMenuWallet.tsx index 2ea4d61ed3..1347408d62 100644 --- a/ui/snippets/profile/ProfileMenuWallet.tsx +++ b/ui/snippets/profile/ProfileMenuWallet.tsx @@ -2,10 +2,10 @@ import { Button, Divider, Flex, IconButton } from '@chakra-ui/react'; import React from 'react'; import delay from 'lib/delay'; +import useWeb3Wallet from 'lib/web3/useWallet'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; -import useWallet from '../walletMenu/useWallet'; import useWeb3AccountWithDomain from './useWeb3AccountWithDomain'; interface Props { @@ -13,23 +13,23 @@ interface Props { } const ProfileMenuWallet = ({ onClose }: Props) => { - const wallet = useWallet({ source: 'Header' }); + const web3Wallet = useWeb3Wallet({ source: 'Header' }); const web3AccountWithDomain = useWeb3AccountWithDomain(true); const handleConnectWalletClick = React.useCallback(async() => { - wallet.openModal(); + web3Wallet.openModal(); await delay(300); onClose?.(); - }, [ wallet, onClose ]); + }, [ web3Wallet, onClose ]); const handleOpenWalletClick = React.useCallback(async() => { - wallet.openModal(); + web3Wallet.openModal(); await delay(300); onClose?.(); - }, [ wallet, onClose ]); + }, [ web3Wallet, onClose ]); - if (wallet.isWalletConnected && web3AccountWithDomain.address) { + if (web3Wallet.isConnected && web3AccountWithDomain.address) { return ( <> @@ -49,7 +49,7 @@ const ProfileMenuWallet = ({ onClose }: Props) => { color="icon_info" boxSize={ 5 } onClick={ handleOpenWalletClick } - isLoading={ wallet.isModalOpening } + isLoading={ web3Wallet.isOpen } flexShrink={ 0 } /> @@ -58,13 +58,11 @@ const ProfileMenuWallet = ({ onClose }: Props) => { ); } - const isLoading = wallet.isModalOpening || wallet.isModalOpen; - return ( -
- - ); -}; - -export default ProfileMenuContent; diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx deleted file mode 100644 index 5b1f67d618..0000000000 --- a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { BrowserContext } from '@playwright/test'; -import React from 'react'; - -import config from 'configs/app'; -import * as profileMock from 'mocks/user/profile'; -import { contextWithAuth } from 'playwright/fixtures/auth'; -import { test, expect } from 'playwright/lib'; - -import ProfileMenuDesktop from './ProfileMenuDesktop'; - -test('no auth', async({ render, page }) => { - const hooksConfig = { - router: { - asPath: '/', - pathname: '/', - }, - }; - const component = await render(, { hooksConfig }); - await component.locator('a').click(); - - expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`); -}); - -const authTest = test.extend<{ context: BrowserContext }>({ - context: contextWithAuth, -}); -authTest('auth +@dark-mode', async({ render, page, mockApiResponse, mockAssetResponse }) => { - await mockApiResponse('user_info', profileMock.base); - await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg'); - - const component = await render(); - await component.getByAltText(/Profile picture/i).click(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } }); -}); diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.tsx deleted file mode 100644 index d0e0e05fcd..0000000000 --- a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import type { IconButtonProps } from '@chakra-ui/react'; -import { PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react'; -import React from 'react'; - -import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; -import * as mixpanel from 'lib/mixpanel/index'; -import Popover from 'ui/shared/chakra/Popover'; -import UserAvatar from 'ui/shared/UserAvatar'; -import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; - -type Props = { - isHomePage?: boolean; - className?: string; - fallbackIconSize?: number; - buttonBoxSize?: string; -}; - -const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize, buttonBoxSize }: Props) => { - const { data, error, isPending } = useFetchProfileInfo(); - const loginUrl = useLoginUrl(); - const [ hasMenu, setHasMenu ] = React.useState(false); - - React.useEffect(() => { - if (!isPending) { - setHasMenu(Boolean(data)); - } - }, [ data, error?.status, isPending ]); - - const handleSignInClick = React.useCallback(() => { - mixpanel.logEvent( - mixpanel.EventTypes.ACCOUNT_ACCESS, - { Action: 'Auth0 init' }, - { send_immediately: true }, - ); - }, []); - - const iconButtonProps: Partial = (() => { - if (hasMenu || !loginUrl) { - return {}; - } - - return { - as: 'a', - href: loginUrl, - onClick: handleSignInClick, - }; - })(); - - return ( - - Sign in to My Account to add tags,
create watchlists, access API keys and more } - textAlign="center" - padding={ 2 } - isDisabled={ hasMenu } - openDelay={ 500 } - > - - - } - variant={ isHomePage ? 'hero' : 'header' } - data-selected={ hasMenu } - boxSize={ buttonBoxSize ?? '40px' } - flexShrink={ 0 } - { ...iconButtonProps } - /> - - -
- { hasMenu && ( - - - - - - ) } -
- ); -}; - -export default chakra(ProfileMenuDesktop); diff --git a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx b/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx deleted file mode 100644 index 0867bf2bd8..0000000000 --- a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { BrowserContext } from '@playwright/test'; -import React from 'react'; - -import config from 'configs/app'; -import * as profileMock from 'mocks/user/profile'; -import { contextWithAuth } from 'playwright/fixtures/auth'; -import { test, expect, devices } from 'playwright/lib'; - -import ProfileMenuMobile from './ProfileMenuMobile'; - -test('no auth', async({ render, page }) => { - const hooksConfig = { - router: { - asPath: '/', - pathname: '/', - }, - }; - const component = await render(, { hooksConfig }); - await component.locator('a').click(); - - expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`); -}); - -test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - -const authTest = test.extend<{ context: BrowserContext }>({ - context: contextWithAuth, -}); - -authTest.describe('auth', () => { - authTest('base view', async({ render, page, mockApiResponse, mockAssetResponse }) => { - await mockApiResponse('user_info', profileMock.base); - await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg'); - - const component = await render(); - await component.getByAltText(/Profile picture/i).click(); - - await expect(page).toHaveScreenshot(); - }); -}); diff --git a/ui/snippets/profileMenu/ProfileMenuMobile.tsx b/ui/snippets/profileMenu/ProfileMenuMobile.tsx deleted file mode 100644 index 51feea6f4f..0000000000 --- a/ui/snippets/profileMenu/ProfileMenuMobile.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react'; -import type { IconButtonProps } from '@chakra-ui/react'; -import React from 'react'; - -import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; -import * as mixpanel from 'lib/mixpanel/index'; -import UserAvatar from 'ui/shared/UserAvatar'; -import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; - -const ProfileMenuMobile = () => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const { data, error, isPending } = useFetchProfileInfo(); - const loginUrl = useLoginUrl(); - const [ hasMenu, setHasMenu ] = React.useState(false); - - const handleSignInClick = React.useCallback(() => { - mixpanel.logEvent( - mixpanel.EventTypes.ACCOUNT_ACCESS, - { Action: 'Auth0 init' }, - { send_immediately: true }, - ); - }, []); - - React.useEffect(() => { - if (!isPending) { - setHasMenu(Boolean(data)); - } - }, [ data, error?.status, isPending ]); - - const iconButtonProps: Partial = (() => { - if (hasMenu || !loginUrl) { - return {}; - } - - return { - as: 'a', - href: loginUrl, - onClick: handleSignInClick, - }; - })(); - - return ( - <> - } - variant="header" - data-selected={ hasMenu } - boxSize="40px" - flexShrink={ 0 } - onClick={ hasMenu ? onOpen : undefined } - { ...iconButtonProps } - /> - { hasMenu && ( - - - - - - - - - ) } - - ); -}; - -export default ProfileMenuMobile; diff --git a/ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png b/ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png deleted file mode 100644 index 5e247adba1f9ec0e5f041a0a7071d16d8dd16480..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24946 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 diff --git a/ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_default_auth-dark-mode-1.png b/ui/snippets/profileMenu/__screenshots__/ProfileMenuDesktop.pw.tsx_default_auth-dark-mode-1.png deleted file mode 100644 index da481493f46aab90d2f92ef9129a393a3d791df8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25393 zcmc$_1yCGO+ch|ZAi>=wB)GdvAOQjdhu|LEWspIG1}C_?1$VcgA-FpPm%-fz{^tF^ zZ>4ItwrXpoYG8V%r@Qa%`^b6Dc{)@{K^h%}1O)^Fq04;!qyoI(gFvq|kr9C{xuSF-o=%0=)&vd=gi4Pd{97(^bEs95|a3xY1AiVS~-b*mOf*zr52>SC@0g zRleLX&B-OlHI&FCEyN|wJIySe+RQ_DTwcuo;lqcW_T9%PkiEZugeP2k*6yab!`S0; z+KFe!u4iH{v}KfqTfWDJHxDY9TKLnlZiFGszoewNB<#202X*z<3Poh;{AH5M>1j3V zKd1#nlD{pujX6_LOkonsm3SiPgM)(>sbA->K-ndXqobqrVJh#6 z9*QDU*?eRv$zdGUL#8_BE9o<4i2XhZZvX59_r{S;C0r#=q&8PK4eJKyO$!#r2bQpr z>$ET@TZv`4twTI2GCz(^RHZr=`g@~s#cYcK5Q5$tDGh(|^56>ITKNKrAHu43%S}HU zud98q159zke2_u%a~~$g!3Zu0cwC4Pd$KId{f^o3qf=JP%gg87Sy&rl9w;rj zdvexCs{I|%hus@>snN_iV(xMO!jvYj!K_%gIexbsqRI%tp<884CxMN3M~lfE&AxE5 zDP)x@4SNz>2BVF8EeHX3B6uPUM^#BrYaGWcOR*M{fBbXvolavU;$}$~T3FzoH{MVO zuNj_^`m$S2Q(PZLf8yxGZ0S?5mqvD^6F33SMQ88jB=g|PnBg8PyxD)R=H;ap8y8vJ zB0Ew-&`|!%KdZA_u2OehoDyHrT=6f22A6CjtmI@MmlCd^aAh?f>s=n1{RBP?iv&fd zp@iGoHP+OZU~|$Gfy<0!nycpU=Cz6nI*~lV-=xNWhsOP#@mvpT@SZChci*%955p(wCkPhiGbxtdKCI@ei4M@iZCne{ZkH`sc8 zR^Xk=$s;TnYAKXY^Xd6vrEc7WMKBaxKANnKA7AG_M4

KOx-v63>oWLN+#~E0!@e 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 diff --git a/ui/snippets/profileMenu/__screenshots__/ProfileMenuMobile.pw.tsx_default_auth-base-view-1.png b/ui/snippets/profileMenu/__screenshots__/ProfileMenuMobile.pw.tsx_default_auth-base-view-1.png deleted file mode 100644 index e0a36caf2b8adb0247a24f67d45659167e6aee1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23197 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 diff --git a/ui/snippets/wallet/WalletDesktop.tsx b/ui/snippets/wallet/WalletDesktop.tsx index cde57955c9..7f8e8adbe4 100644 --- a/ui/snippets/wallet/WalletDesktop.tsx +++ b/ui/snippets/wallet/WalletDesktop.tsx @@ -2,9 +2,9 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button import React from 'react'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import useWeb3Wallet from 'lib/web3/useWallet'; import Popover from 'ui/shared/chakra/Popover'; import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; -import useWallet from 'ui/snippets/walletMenu/useWallet'; import WalletButton from './WalletButton'; import WalletMenuContent from './WalletMenuContent'; @@ -17,23 +17,23 @@ interface Props { const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { const walletMenu = useDisclosure(); - const walletUtils = useWallet({ source: 'Header' }); - const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); + const web3Wallet = useWeb3Wallet({ source: 'Header' }); + const web3AccountWithDomain = useWeb3AccountWithDomain(web3Wallet.isConnected); const { isAutoConnectDisabled } = useMarketplaceContext(); const isPending = - (walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || - (!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); + (web3Wallet.isConnected && web3AccountWithDomain.isLoading) || + (!web3Wallet.isConnected && web3Wallet.isOpen); const handleOpenWalletClick = React.useCallback(() => { - walletUtils.openModal(); + web3Wallet.openModal(); walletMenu.onClose(); - }, [ walletUtils, walletMenu ]); + }, [ web3Wallet, walletMenu ]); const handleDisconnectClick = React.useCallback(() => { - walletUtils.disconnect(); + web3Wallet.disconnect(); walletMenu.onClose(); - }, [ walletUtils, walletMenu ]); + }, [ web3Wallet, walletMenu ]); return ( @@ -41,7 +41,7 @@ const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { { const walletMenu = useDisclosure(); - const walletUtils = useWallet({ source: 'Header' }); - const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); + const web3Wallet = useWeb3Wallet({ source: 'Header' }); + const web3AccountWithDomain = useWeb3AccountWithDomain(web3Wallet.isConnected); const { isAutoConnectDisabled } = useMarketplaceContext(); const isPending = - (walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || - (!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); + (web3Wallet.isConnected && web3AccountWithDomain.isLoading) || + (!web3Wallet.isConnected && web3Wallet.isOpen); const handleOpenWalletClick = React.useCallback(() => { - walletUtils.openModal(); + web3Wallet.openModal(); walletMenu.onClose(); - }, [ walletUtils, walletMenu ]); + }, [ web3Wallet, walletMenu ]); const handleDisconnectClick = React.useCallback(() => { - walletUtils.disconnect(); + web3Wallet.disconnect(); walletMenu.onClose(); - }, [ walletUtils, walletMenu ]); + }, [ web3Wallet, walletMenu ]); return ( <> { - const isMobile = useIsMobile(); - const borderColor = useColorModeValue('orange.100', 'orange.900'); - - return ( - - - { isAutoConnectDisabled && ( - - - - ) } - - ); -}; - -export default chakra(WalletIdenticon); diff --git a/ui/snippets/walletMenu/WalletMenuContent.tsx b/ui/snippets/walletMenu/WalletMenuContent.tsx deleted file mode 100644 index 68a551f840..0000000000 --- a/ui/snippets/walletMenu/WalletMenuContent.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Box, Button, Text, Flex, IconButton, useColorModeValue } from '@chakra-ui/react'; -import React from 'react'; - -import * as mixpanel from 'lib/mixpanel/index'; -import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; -import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import IconSvg from 'ui/shared/IconSvg'; - -type Props = { - address?: string; - ensDomainName?: string | null; - disconnect?: () => void; - isAutoConnectDisabled?: boolean; - openWeb3Modal: () => void; - closeWalletMenu: () => void; -}; - -const WalletMenuContent = ({ address, ensDomainName, disconnect, isAutoConnectDisabled, openWeb3Modal, closeWalletMenu }: Props) => { - const bgColor = useColorModeValue('orange.100', 'orange.900'); - const [ isModalOpening, setIsModalOpening ] = React.useState(false); - - const onAddressClick = React.useCallback(() => { - mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Address click' }); - }, []); - - const handleOpenWeb3Modal = React.useCallback(async() => { - setIsModalOpening(true); - await openWeb3Modal(); - setTimeout(closeWalletMenu, 300); - }, [ openWeb3Modal, closeWalletMenu ]); - - return ( - - { isAutoConnectDisabled && ( - - - - Connect your wallet in the app below - - - ) } - - My wallet - - - Your wallet is used to interact with apps and contracts in the explorer. - - { address && ( - - - } - variant="simple" - h="20px" - w="20px" - ml={ 1 } - onClick={ handleOpenWeb3Modal } - isLoading={ isModalOpening } - /> - - ) } - - - ); -}; - -export default WalletMenuContent; diff --git a/ui/snippets/walletMenu/WalletMenuDesktop.pw.tsx b/ui/snippets/walletMenu/WalletMenuDesktop.pw.tsx deleted file mode 100644 index eed4e34883..0000000000 --- a/ui/snippets/walletMenu/WalletMenuDesktop.pw.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; - -import type * as bens from '@blockscout/bens-types'; - -import config from 'configs/app'; -import * as addressMock from 'mocks/address/address'; -import * as domainMock from 'mocks/ens/domain'; -import { test, expect } from 'playwright/lib'; - -import { WalletMenuDesktop } from './WalletMenuDesktop'; - -const props = { - isWalletConnected: false, - address: '', - connect: () => {}, - disconnect: () => {}, - isModalOpening: false, - isModalOpen: false, - openModal: () => {}, -}; - -test.use({ viewport: { width: 1440, height: 750 } }); // xl - -test('wallet is not connected +@dark-mode', async({ page, render }) => { - await render(); - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } }); -}); - -test('wallet is not connected (home page) +@dark-mode', async({ page, render }) => { - await render(); - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } }); -}); - -test('wallet is loading', async({ page, render }) => { - await render(); - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } }); -}); - -test('wallet connected +@dark-mode', async({ page, render, mockApiResponse }) => { - await mockApiResponse( - 'address_domain', - { domain: undefined, resolved_domains_count: 0 } as bens.GetAddressResponse, - { pathParams: { address: addressMock.hash, chainId: config.chain.id } }, - ); - - const component = await render(); - await component.locator('button').click(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } }); -}); - -test('wallet connected (home page) +@dark-mode', async({ page, render }) => { - const component = await render(); - await component.locator('button').click(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } }); -}); - -test('wallet with ENS connected', async({ page, render, mockApiResponse }) => { - await mockApiResponse( - 'address_domain', - { domain: domainMock.ensDomainB, resolved_domains_count: 1 } as bens.GetAddressResponse, - { pathParams: { address: addressMock.hash, chainId: config.chain.id } }, - ); - - const component = await render(); - await component.locator('button').click(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } }); -}); diff --git a/ui/snippets/walletMenu/WalletMenuDesktop.tsx b/ui/snippets/walletMenu/WalletMenuDesktop.tsx deleted file mode 100644 index b60a9f2c4a..0000000000 --- a/ui/snippets/walletMenu/WalletMenuDesktop.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean, chakra } from '@chakra-ui/react'; -import React from 'react'; - -import config from 'configs/app'; -import useApiQuery from 'lib/api/useApiQuery'; -import { useMarketplaceContext } from 'lib/contexts/marketplace'; -import useIsMobile from 'lib/hooks/useIsMobile'; -import * as mixpanel from 'lib/mixpanel/index'; -import Popover from 'ui/shared/chakra/Popover'; -import HashStringShorten from 'ui/shared/HashStringShorten'; -import IconSvg from 'ui/shared/IconSvg'; -import useWallet from 'ui/snippets/walletMenu/useWallet'; -import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent'; - -import WalletIdenticon from './WalletIdenticon'; -import WalletTooltip from './WalletTooltip'; - -type Props = { - isHomePage?: boolean; - className?: string; - size?: 'sm' | 'md'; -}; - -type ComponentProps = Props & { - isWalletConnected: boolean; - address: string; - connect: () => void; - disconnect: () => void; - isModalOpening: boolean; - isModalOpen: boolean; - openModal: () => void; -}; - -export const WalletMenuDesktop = ({ - isHomePage, className, size = 'md', isWalletConnected, address, connect, - disconnect, isModalOpening, isModalOpen, openModal, -}: ComponentProps) => { - const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false); - const isMobile = useIsMobile(); - const { isAutoConnectDisabled } = useMarketplaceContext(); - const addressDomainQuery = useApiQuery('address_domain', { - pathParams: { - chainId: config.chain.id, - address, - }, - queryOptions: { - enabled: config.features.nameService.isEnabled, - }, - }); - - const openPopover = React.useCallback(() => { - mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' }); - setIsPopoverOpen.toggle(); - }, [ setIsPopoverOpen ]); - - return ( - - - - - - - - - { isWalletConnected && ( - - - - - - ) } - - ); -}; - -// separated the useWallet hook from the main component because it's hard to mock it in tests -const WalletMenuDesktopWrapper = ({ isHomePage, className, size = 'md' }: Props) => { - const { - isWalletConnected, address, connect, disconnect, - isModalOpening, isModalOpen, openModal, - } = useWallet({ source: 'Header' }); - - return ( - - ); -}; - -export default chakra(WalletMenuDesktopWrapper); diff --git a/ui/snippets/walletMenu/WalletMenuMobile.pw.tsx b/ui/snippets/walletMenu/WalletMenuMobile.pw.tsx deleted file mode 100644 index 82236189c5..0000000000 --- a/ui/snippets/walletMenu/WalletMenuMobile.pw.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; - -import type * as bens from '@blockscout/bens-types'; - -import config from 'configs/app'; -import * as addressMock from 'mocks/address/address'; -import * as domainMock from 'mocks/ens/domain'; -import { test, expect, devices } from 'playwright/lib'; - -import { WalletMenuMobile } from './WalletMenuMobile'; - -const props = { - isWalletConnected: false, - address: '', - connect: () => {}, - disconnect: () => {}, - isModalOpening: false, - isModalOpen: false, - openModal: () => {}, -}; - -test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - -test('wallet is not connected +@dark-mode', async({ page, render }) => { - await render(); - await expect(page).toHaveScreenshot(); -}); - -test('wallet is loading', async({ page, render }) => { - await render(); - await expect(page).toHaveScreenshot(); -}); - -test('wallet connected +@dark-mode', async({ page, render, mockApiResponse }) => { - await mockApiResponse( - 'address_domain', - { domain: undefined, resolved_domains_count: 0 } as bens.GetAddressResponse, - { pathParams: { address: addressMock.hash, chainId: config.chain.id } }, - ); - - const component = await render(); - await component.locator('button').click(); - - await expect(page).toHaveScreenshot(); -}); - -test('wallet with ENS connected', async({ page, render, mockApiResponse }) => { - await mockApiResponse( - 'address_domain', - { domain: domainMock.ensDomainB, resolved_domains_count: 1 } as bens.GetAddressResponse, - { pathParams: { address: addressMock.hash, chainId: config.chain.id } }, - ); - - const component = await render(); - await component.locator('button').click(); - - await expect(page).toHaveScreenshot(); -}); diff --git a/ui/snippets/walletMenu/WalletMenuMobile.tsx b/ui/snippets/walletMenu/WalletMenuMobile.tsx deleted file mode 100644 index 153551b52d..0000000000 --- a/ui/snippets/walletMenu/WalletMenuMobile.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react'; -import React from 'react'; - -import config from 'configs/app'; -import useApiQuery from 'lib/api/useApiQuery'; -import { useMarketplaceContext } from 'lib/contexts/marketplace'; -import useIsMobile from 'lib/hooks/useIsMobile'; -import * as mixpanel from 'lib/mixpanel/index'; -import IconSvg from 'ui/shared/IconSvg'; -import useWallet from 'ui/snippets/walletMenu/useWallet'; -import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent'; - -import WalletIdenticon from './WalletIdenticon'; -import WalletTooltip from './WalletTooltip'; - -type ComponentProps = { - isWalletConnected: boolean; - address: string; - connect: () => void; - disconnect: () => void; - isModalOpening: boolean; - isModalOpen: boolean; - openModal: () => void; -}; - -export const WalletMenuMobile = ( - { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen, openModal }: ComponentProps, -) => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const isMobile = useIsMobile(); - const { isAutoConnectDisabled } = useMarketplaceContext(); - const addressDomainQuery = useApiQuery('address_domain', { - pathParams: { - chainId: config.chain.id, - address, - }, - queryOptions: { - enabled: config.features.nameService.isEnabled, - }, - }); - - const openPopover = React.useCallback(() => { - mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' }); - onOpen(); - }, [ onOpen ]); - - return ( - <> - - : - - } - variant="header" - data-selected={ isWalletConnected } - data-warning={ isAutoConnectDisabled } - boxSize="40px" - flexShrink={ 0 } - onClick={ isWalletConnected ? openPopover : connect } - isLoading={ - ((isModalOpening || isModalOpen) && !isWalletConnected) || - (addressDomainQuery.isLoading && isWalletConnected) - } - /> - - { isWalletConnected && ( - - - - - - - - - ) } - - ); -}; - -const WalletMenuMobileWrapper = () => { - const { - isWalletConnected, address, connect, disconnect, - isModalOpening, isModalOpen, openModal, - } = useWallet({ source: 'Header' }); - - return ( - - ); -}; - -export default WalletMenuMobileWrapper; diff --git a/ui/snippets/walletMenu/WalletTooltip.tsx b/ui/snippets/walletMenu/WalletTooltip.tsx deleted file mode 100644 index 10942fc321..0000000000 --- a/ui/snippets/walletMenu/WalletTooltip.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Tooltip, useBoolean, useOutsideClick, Box } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import { SECOND } from 'lib/consts'; -import removeQueryParam from 'lib/router/removeQueryParam'; - -type Props = { - children: React.ReactNode; - isDisabled?: boolean; - isMobile?: boolean; - isWalletConnected?: boolean; - isAutoConnectDisabled?: boolean; -}; - -const LOCAL_STORAGE_KEY = 'wallet-connect-tooltip-shown'; - -const WalletTooltip = ({ children, isDisabled, isMobile, isWalletConnected, isAutoConnectDisabled }: Props, ref: React.ForwardedRef) => { - const router = useRouter(); - const [ isTooltipShown, setIsTooltipShown ] = useBoolean(false); - const innerRef = React.useRef(null); - useOutsideClick({ ref: innerRef, handler: setIsTooltipShown.off }); - - const label = React.useMemo(() => { - if (isWalletConnected) { - if (isAutoConnectDisabled) { - return Your wallet is not
connected to this app.
Connect your wallet
in the app directly
; - } - return null; - } - return Connect your wallet
to Blockscout for
full-featured access
; - }, [ isWalletConnected, isAutoConnectDisabled ]); - - const isAppPage = router.pathname === '/apps/[id]'; - - React.useEffect(() => { - const wasShown = window.localStorage.getItem(LOCAL_STORAGE_KEY); - const isMarketplacePage = router.pathname === '/apps'; - const isTooltipShowAction = router.query.action === 'tooltip'; - const isConnectWalletAction = router.query.action === 'connect'; - const needToShow = (isAppPage && !isConnectWalletAction) || isTooltipShowAction || (!wasShown && isMarketplacePage); - let timer1: ReturnType; - let timer2: ReturnType; - - if (!isDisabled && needToShow) { - timer1 = setTimeout(() => { - setIsTooltipShown.on(); - timer2 = setTimeout(() => setIsTooltipShown.off(), 3 * SECOND); - if (!wasShown && isMarketplacePage) { - window.localStorage.setItem(LOCAL_STORAGE_KEY, 'true'); - } - if (isTooltipShowAction) { - removeQueryParam(router, 'action'); - } - }, isTooltipShowAction ? 0 : SECOND); - } - - return () => { - clearTimeout(timer1); - clearTimeout(timer2); - }; - }, [ setIsTooltipShown, isDisabled, router, isAppPage ]); - - return ( - - - { children } - - - ); -}; - -export default React.forwardRef(WalletTooltip); diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png deleted file mode 100644 index 405eee49506d3b8ce5241c658f417651e188a3fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16655 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 Ef?YG%V*mgE diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-is-not-connected-home-page-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_dark-color-mode_wallet-is-not-connected-home-page-dark-mode-1.png deleted file mode 100644 index cbeefa84afe22979af04f4ce0fc5ca08123551e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2288 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 diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-dark-mode-1.png deleted file mode 100644 index 835d3779abf4f8640a6cf4984b09fea27f0564e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16432 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 diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-home-page-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuDesktop.pw.tsx_default_wallet-connected-home-page-dark-mode-1.png deleted file mode 100644 index f11da2a62e9df7f80afe679599eafa2a353978e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16441 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 diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_dark-color-mode_wallet-connected-dark-mode-1.png deleted file mode 100644 index 05dc391f4d80f07c7b8b80376faac228ce19b4f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16546 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 H;l_Uey1I{M diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-connected-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-connected-dark-mode-1.png deleted file mode 100644 index 95158695b9cdcb4113e188e0cadb7715f92f7823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16017 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 M`j5&VK70M&0NR5$jQ{`u diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-loading-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-loading-1.png deleted file mode 100644 index fbe46c9f22d8ffc30bea6cc7aec1bca69fa18ef3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3161 zcmeHK{ZE=l9KT2B>ssB?b~>u1TeGxFbUMwnzLGX}XxqTnZoaHe<{UVUz?6YV0lSxK zy4jkviIzcSuIbE1)wZ}2MZuObHGoVVI)?P2TCo;+QHF{XpFM8-Czf!(T<-F{``+jC z{e13n7v3S@cSd3&0RTG-^7Bps05t&+bTvE_ovH65T|qmDaSDGF^o++X0kC6NLEe$~ z*dn`mjCKZ(IX8h^#v~lRJU)QOrDi6KQoe#_=bu^FDeD-Ih*?s73rHleQOt{mTb z4~ZR*!*(w|X8TX-!z{1t!1mH9a!n5qMQR?`YB@vS z#T*=IefA01x-+uKLvTI##;e%t)+)xbnhuq3NRa`gKpy?G<@hBBQTNkD}3;Ctm`@N~Usx z+sEHJG@F$<5U-&f+yRKxU*ylLsvDN6R-A*MAg?@|6=^Yfk}HZ8KP1n#E_lm?AsZ}S zjtvjEd%lLwy$}lKh~oY7!9mfw6V@9Ho>CGCg*Z00BOgc$XF9ofV(P$jxdQ-$` z(|f(%($Z3`9*1NXVSBB9-#R4{rF{O=ia$f+=itmEDj^r462^A5+@Ao*7c~%Lf4QiB$F3Xu*=(?G_072L`rgM&V~mM&x}LqU~W4!q#ldoiv)tHsTi^EFzEQ>UTTv*LgoL_y|gse z*&1xQz-7f2N(>2_RTpa#aX(TudtHyP(hm~d{&g6eNdO(AM^c-7Meie{xSMA2hude5 z!(y?k=w`niIVf%?3&0J#M_v4s32O-e6Gxiwi4W_dA=TQtcT;ujM( z>8Z`W;%6>ZbR(q&WOw42_O-4P$CWU>xNRX?d@0moTj@et%X1^11C2bz1QcEl_M;Uq zj;~XzE3zJk76Ccyz=!^<*+O2ocGdkOX-XG-Qh z_MaAXtSpn+t;eL9%r!E(pv3wq^;h8fy-;&UyE4I{PS>!=m=E;J@O>~W z)9D%DXe{^-EvoOpAi%-)Jr~eHfP;Vz{(bWUUI=*MfA58@kO(ysA6XrL68$THf@6fd Jo}(Xq`8PRksM-Jk diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-not-connected-dark-mode-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-is-not-connected-dark-mode-1.png deleted file mode 100644 index 874b76d86a6941eb7af7b56594321ce7fa03060b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3646 zcmeHK{a4ag7{BDkTGQHD>t&kR)>c21=~8B>WN*tnWfuBX(<#=DWoV*hkRhnOV6LTe zPHj$b<#h84qJ%<%2vG>?^+VVLzQ+$!rv%m&WO&&eNcN$cMXD6moa zFoq4oq#2`Dui+^zQ4zK}J#8Tt|=E#(j?KJK(N%j}&7WB|=l#GHQO#z@!jPsa& zx-o*qi6|&YO-tjvadlNcD-JQ-9|5s0sVf-|9z1YgyOvv0^2|CmkwJSiZ~}wDtmw)9 z`$mDOkt8|d=_%})b_I{U3x?J8k(l7E`1R}8W3~q439Rjswo|rcxv?|0`DQd4P3)2F zz)i?pFjK5ag=kwRn?ZaAW zZmt`0DVc1&My0YaY1P?|vGW$W&@>f8Xc(LVoL1=aur1gAEfagD4*i}@!@^PD+Oj{n zxXB=GXlUSieDi7BUvFoVG`-7l#*o09Sei)Q>8lA^&iA-p?me0;p+%xJe( zOfh?VzbUw2ANp;yS{9wv@TmQ=i(blIJJQCIPMMBj{AN*L{7 z7I#HdZ97b0VpV@6qwHRo&s}S3YOJpxO6yrHmNwHY;hmZ_Y23X$QvbXKIQfiA!%WIh z!`$55c^gwG<&O&++wg>{;DIa2s8|;_Y?m(%mmrZ)0-n~QQkob!$Ls_7E>`V}T<%4) zB!=om^A}tzz7}vS4D{~X-I4J`mM=P;%$cK4BzXqg6Vt?5V|Yx!jDVD<(|p&dP-i&{ za>~ogqo-y^tLL+EHa-9BtbOv_tU_@JL9to;7F7C+_H2t0hLJc)oV-fO%2FF7)Muoe zoSS5`ftIRTq{v_g*7s>kx2_K6u~nGu zts?4j_oFXO2f`1?qa!0Dt*3@=DW3s-W~ELS$Ci?)5X20m@HW&P4@kRSSU=!+J$o01 zP0|zbEkbM;Xfjlmr{_#7-%^j!!E=fQW{m;DRss z@DgfaX#tIe*jPx11#(#6!-eXwP!RuX4?3hMgQDrH`$?sDkq;pV`}*J>?yjT@{{Xoc Bfztp0 diff --git a/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-with-ENS-connected-1.png b/ui/snippets/walletMenu/__screenshots__/WalletMenuMobile.pw.tsx_default_wallet-with-ENS-connected-1.png deleted file mode 100644 index 8968c8030ea5dbfdb3c270948e23987d2e33403a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14517 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 Date: Thu, 19 Sep 2024 19:42:22 +0200 Subject: [PATCH 18/49] refactoring --- .../web3/useAccountWithDomain.ts | 5 +++-- ui/home/HeroBanner.tsx | 8 ++++---- ui/marketplace/MarketplaceAppTopBar.tsx | 8 ++++---- ui/snippets/header/HeaderDesktop.tsx | 8 ++++---- ui/snippets/header/HeaderMobile.tsx | 8 ++++---- .../horizontal/NavigationDesktop.tsx | 8 ++++---- ui/snippets/user/README.md | 2 ++ .../UserIdenticon.tsx} | 4 ++-- .../UserWalletAutoConnectAlert.tsx} | 4 ++-- .../profile/UserProfileButton.tsx} | 10 +++++----- .../profile/UserProfileContent.tsx} | 20 +++++++++---------- .../profile/UserProfileContentNavLink.tsx} | 4 ++-- .../profile/UserProfileContentWallet.tsx} | 7 +++---- .../profile/UserProfileDesktop.tsx} | 12 +++++------ .../profile/UserProfileMobile.tsx} | 12 +++++------ ui/snippets/{ => user}/profile/types.ts | 0 ui/snippets/{ => user}/profile/utils.ts | 0 .../wallet/UserWalletButton.tsx} | 9 +++++---- .../wallet/UserWalletDesktop.tsx} | 10 +++++----- .../wallet/UserWalletMenuContent.tsx} | 8 ++++---- .../wallet/UserWalletMobile.tsx} | 14 ++++++------- 21 files changed, 82 insertions(+), 79 deletions(-) rename ui/snippets/profile/useWeb3AccountWithDomain.tsx => lib/web3/useAccountWithDomain.ts (86%) create mode 100644 ui/snippets/user/README.md rename ui/snippets/{profile/ProfileAddressIcon.tsx => user/UserIdenticon.tsx} (88%) rename ui/snippets/{wallet/WalletAutoConnectDisabledAlert.tsx => user/UserWalletAutoConnectAlert.tsx} (85%) rename ui/snippets/{profile/ProfileButton.tsx => user/profile/UserProfileButton.tsx} (87%) rename ui/snippets/{profile/ProfileMenuContent.tsx => user/profile/UserProfileContent.tsx} (76%) rename ui/snippets/{profile/ProfileMenuNavLink.tsx => user/profile/UserProfileContentNavLink.tsx} (83%) rename ui/snippets/{profile/ProfileMenuWallet.tsx => user/profile/UserProfileContentWallet.tsx} (91%) rename ui/snippets/{profile/ProfileDesktop.tsx => user/profile/UserProfileDesktop.tsx} (85%) rename ui/snippets/{profile/ProfileMobile.tsx => user/profile/UserProfileMobile.tsx} (86%) rename ui/snippets/{ => user}/profile/types.ts (100%) rename ui/snippets/{ => user}/profile/utils.ts (100%) rename ui/snippets/{wallet/WalletButton.tsx => user/wallet/UserWalletButton.tsx} (79%) rename ui/snippets/{wallet/WalletDesktop.tsx => user/wallet/UserWalletDesktop.tsx} (90%) rename ui/snippets/{wallet/WalletMenuContent.tsx => user/wallet/UserWalletMenuContent.tsx} (82%) rename ui/snippets/{wallet/WalletMobile.tsx => user/wallet/UserWalletMobile.tsx} (85%) diff --git a/ui/snippets/profile/useWeb3AccountWithDomain.tsx b/lib/web3/useAccountWithDomain.ts similarity index 86% rename from ui/snippets/profile/useWeb3AccountWithDomain.tsx rename to lib/web3/useAccountWithDomain.ts index c20c683430..0eeb18e4c5 100644 --- a/ui/snippets/profile/useWeb3AccountWithDomain.tsx +++ b/lib/web3/useAccountWithDomain.ts @@ -2,9 +2,10 @@ import React from 'react'; import config from 'configs/app'; import useApiQuery from 'lib/api/useApiQuery'; -import useAccount from 'lib/web3/useAccount'; -export default function useWeb3AccountWithDomain(isEnabled: boolean) { +import useAccount from './useAccount'; + +export default function useAccountWithDomain(isEnabled: boolean) { const { address } = useAccount(); const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled); diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index b12df6f579..63cc30ff1e 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -3,9 +3,9 @@ import React from 'react'; import config from 'configs/app'; import AdBanner from 'ui/shared/ad/AdBanner'; -import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; +import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; +import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; const BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; const TEXT_COLOR_DEFAULT = 'white'; @@ -55,8 +55,8 @@ const HeroBanner = () => { { config.UI.navigation.layout === 'vertical' && ( { - (config.features.account.isEnabled && ) || - (config.features.blockchainInteraction.isEnabled && ) + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) } ) } diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 9adc267078..c335616bd7 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -12,8 +12,8 @@ import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; +import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; +import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; import AppSecurityReport from './AppSecurityReport'; import ContractListModal from './ContractListModal'; @@ -100,8 +100,8 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) { !isMobile && ( { - (config.features.account.isEnabled && ) || - (config.features.blockchainInteraction.isEnabled && ) + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) } ) } diff --git a/ui/snippets/header/HeaderDesktop.tsx b/ui/snippets/header/HeaderDesktop.tsx index 690d6ea6a9..470fb26a1c 100644 --- a/ui/snippets/header/HeaderDesktop.tsx +++ b/ui/snippets/header/HeaderDesktop.tsx @@ -3,9 +3,9 @@ import React from 'react'; import config from 'configs/app'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; +import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; +import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; import Burger from './Burger'; @@ -39,8 +39,8 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => { { config.UI.navigation.layout === 'vertical' && ( { - (config.features.account.isEnabled && ) || - (config.features.blockchainInteraction.isEnabled && ) + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) } ) } diff --git a/ui/snippets/header/HeaderMobile.tsx b/ui/snippets/header/HeaderMobile.tsx index 288ffbe6f9..58a619f2e1 100644 --- a/ui/snippets/header/HeaderMobile.tsx +++ b/ui/snippets/header/HeaderMobile.tsx @@ -5,9 +5,9 @@ import { useInView } from 'react-intersection-observer'; import config from 'configs/app'; import { useScrollDirection } from 'lib/contexts/scrollDirection'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileMobile from 'ui/snippets/profile/ProfileMobile'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; -import WalletMobile from 'ui/snippets/wallet/WalletMobile'; +import UserProfileMobile from 'ui/snippets/user/profile/UserProfileMobile'; +import UserWalletMobile from 'ui/snippets/user/wallet/UserWalletMobile'; import Burger from './Burger'; @@ -49,8 +49,8 @@ const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => { { - (config.features.account.isEnabled && ) || - (config.features.blockchainInteraction.isEnabled && ) || + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) || } diff --git a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx index 238347c109..bb67c3045f 100644 --- a/ui/snippets/navigation/horizontal/NavigationDesktop.tsx +++ b/ui/snippets/navigation/horizontal/NavigationDesktop.tsx @@ -5,8 +5,8 @@ import config from 'configs/app'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileDesktop from 'ui/snippets/profile/ProfileDesktop'; -import WalletDesktop from 'ui/snippets/wallet/WalletDesktop'; +import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; +import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; import TestnetBadge from '../TestnetBadge'; import NavLink from './NavLink'; @@ -39,8 +39,8 @@ const NavigationDesktop = () => { { - (config.features.account.isEnabled && ) || - (config.features.blockchainInteraction.isEnabled && ) + (config.features.account.isEnabled && ) || + (config.features.blockchainInteraction.isEnabled && ) } diff --git a/ui/snippets/user/README.md b/ui/snippets/user/README.md new file mode 100644 index 0000000000..262923e500 --- /dev/null +++ b/ui/snippets/user/README.md @@ -0,0 +1,2 @@ +Component in `/profile` directory are used when Account and WalletConnect features are enabled. +Component in `/wallet` directory are used when only WalletConnect features is enabled. \ No newline at end of file diff --git a/ui/snippets/profile/ProfileAddressIcon.tsx b/ui/snippets/user/UserIdenticon.tsx similarity index 88% rename from ui/snippets/profile/ProfileAddressIcon.tsx rename to ui/snippets/user/UserIdenticon.tsx index 0bad509d91..788befed22 100644 --- a/ui/snippets/profile/ProfileAddressIcon.tsx +++ b/ui/snippets/user/UserIdenticon.tsx @@ -9,7 +9,7 @@ type Props = { isAutoConnectDisabled?: boolean; }; -const ProfileAddressIcon = ({ address, isAutoConnectDisabled }: Props) => { +const UserIdenticon = ({ address, isAutoConnectDisabled }: Props) => { const borderColor = useColorModeValue('orange.100', 'orange.900'); return ( @@ -37,4 +37,4 @@ const ProfileAddressIcon = ({ address, isAutoConnectDisabled }: Props) => { ); }; -export default React.memo(ProfileAddressIcon); +export default React.memo(UserIdenticon); diff --git a/ui/snippets/wallet/WalletAutoConnectDisabledAlert.tsx b/ui/snippets/user/UserWalletAutoConnectAlert.tsx similarity index 85% rename from ui/snippets/wallet/WalletAutoConnectDisabledAlert.tsx rename to ui/snippets/user/UserWalletAutoConnectAlert.tsx index 85835aa86e..6b6583b7cf 100644 --- a/ui/snippets/wallet/WalletAutoConnectDisabledAlert.tsx +++ b/ui/snippets/user/UserWalletAutoConnectAlert.tsx @@ -3,7 +3,7 @@ import React from 'react'; import IconSvg from 'ui/shared/IconSvg'; -const WalletAutoConnectDisabledAlert = () => { +const UserWalletAutoConnectAlert = () => { const bgColor = useColorModeValue('orange.100', 'orange.900'); return ( @@ -28,4 +28,4 @@ const WalletAutoConnectDisabledAlert = () => { ); }; -export default React.memo(WalletAutoConnectDisabledAlert); +export default React.memo(UserWalletAutoConnectAlert); diff --git a/ui/snippets/profile/ProfileButton.tsx b/ui/snippets/user/profile/UserProfileButton.tsx similarity index 87% rename from ui/snippets/profile/ProfileButton.tsx rename to ui/snippets/user/profile/UserProfileButton.tsx index 984dddbc7b..e0648ed814 100644 --- a/ui/snippets/profile/ProfileButton.tsx +++ b/ui/snippets/user/profile/UserProfileButton.tsx @@ -8,10 +8,10 @@ import type { UserInfo } from 'types/api/account'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; import useIsMobile from 'lib/hooks/useIsMobile'; import shortenString from 'lib/shortenString'; +import useWeb3AccountWithDomain from 'lib/web3/useAccountWithDomain'; import IconSvg from 'ui/shared/IconSvg'; -import ProfileAddressIcon from './ProfileAddressIcon'; -import useWeb3AccountWithDomain from './useWeb3AccountWithDomain'; +import UserIdenticon from '../UserIdenticon'; import { getUserHandle } from './utils'; interface Props { @@ -22,7 +22,7 @@ interface Props { isPending?: boolean; } -const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Props, ref: React.ForwardedRef) => { +const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Props, ref: React.ForwardedRef) => { const [ isFetched, setIsFetched ] = React.useState(false); const isMobile = useIsMobile(); @@ -57,7 +57,7 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop return ( - + { text } ); @@ -104,4 +104,4 @@ const ProfileButton = ({ profileQuery, size, variant, onClick, isPending }: Prop ); }; -export default React.memo(React.forwardRef(ProfileButton)); +export default React.memo(React.forwardRef(UserProfileButton)); diff --git a/ui/snippets/profile/ProfileMenuContent.tsx b/ui/snippets/user/profile/UserProfileContent.tsx similarity index 76% rename from ui/snippets/profile/ProfileMenuContent.tsx rename to ui/snippets/user/profile/UserProfileContent.tsx index fc266f323d..d1ca949ad6 100644 --- a/ui/snippets/profile/ProfileMenuContent.tsx +++ b/ui/snippets/user/profile/UserProfileContent.tsx @@ -9,10 +9,10 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; import useLogout from 'ui/snippets/auth/useLogout'; -import WalletAutoConnectDisabledAlert from 'ui/snippets/wallet/WalletAutoConnectDisabledAlert'; -import ProfileMenuNavLink from './ProfileMenuNavLink'; -import ProfileMenuWallet from './ProfileMenuWallet'; +import UserWalletAutoConnectAlert from '../UserWalletAutoConnectAlert'; +import UserProfileContentNavLink from './UserProfileContentNavLink'; +import UserProfileContentWallet from './UserProfileContentWallet'; import { getUserHandle } from './utils'; const navLinks: Array = [ @@ -48,16 +48,16 @@ interface Props { onClose?: () => void; } -const ProfileMenuContent = ({ data, onClose }: Props) => { +const UserProfileContent = ({ data, onClose }: Props) => { const { isAutoConnectDisabled } = useMarketplaceContext(); const logout = useLogout(); return ( - { isAutoConnectDisabled && } + { isAutoConnectDisabled && } - { { data?.email && { getUserHandle(data.email) } } - { config.features.blockchainInteraction.isEnabled ? : } + { config.features.blockchainInteraction.isEnabled ? : } { navLinks.map((item) => ( - { - { ); }; -export default React.memo(ProfileMenuContent); +export default React.memo(UserProfileContent); diff --git a/ui/snippets/profile/ProfileMenuNavLink.tsx b/ui/snippets/user/profile/UserProfileContentNavLink.tsx similarity index 83% rename from ui/snippets/profile/ProfileMenuNavLink.tsx rename to ui/snippets/user/profile/UserProfileContentNavLink.tsx index 7dd7842fa7..ec4f356cd6 100644 --- a/ui/snippets/profile/ProfileMenuNavLink.tsx +++ b/ui/snippets/user/profile/UserProfileContentNavLink.tsx @@ -8,7 +8,7 @@ import LinkInternal from 'ui/shared/links/LinkInternal'; type Props = NavLink -const ProfileMenuNavLink = ({ href, icon, text, onClick }: Props) => { +const UserProfileContentNavLink = ({ href, icon, text, onClick }: Props) => { return ( { ); }; -export default React.memo(ProfileMenuNavLink); +export default React.memo(UserProfileContentNavLink); diff --git a/ui/snippets/profile/ProfileMenuWallet.tsx b/ui/snippets/user/profile/UserProfileContentWallet.tsx similarity index 91% rename from ui/snippets/profile/ProfileMenuWallet.tsx rename to ui/snippets/user/profile/UserProfileContentWallet.tsx index 1347408d62..7ef4846cbd 100644 --- a/ui/snippets/profile/ProfileMenuWallet.tsx +++ b/ui/snippets/user/profile/UserProfileContentWallet.tsx @@ -2,17 +2,16 @@ import { Button, Divider, Flex, IconButton } from '@chakra-ui/react'; import React from 'react'; import delay from 'lib/delay'; +import useWeb3AccountWithDomain from 'lib/web3/useAccountWithDomain'; import useWeb3Wallet from 'lib/web3/useWallet'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; -import useWeb3AccountWithDomain from './useWeb3AccountWithDomain'; - interface Props { onClose?: () => void; } -const ProfileMenuWallet = ({ onClose }: Props) => { +const UserProfileContentWallet = ({ onClose }: Props) => { const web3Wallet = useWeb3Wallet({ source: 'Header' }); const web3AccountWithDomain = useWeb3AccountWithDomain(true); @@ -72,4 +71,4 @@ const ProfileMenuWallet = ({ onClose }: Props) => { ); }; -export default React.memo(ProfileMenuWallet); +export default React.memo(UserProfileContentWallet); diff --git a/ui/snippets/profile/ProfileDesktop.tsx b/ui/snippets/user/profile/UserProfileDesktop.tsx similarity index 85% rename from ui/snippets/profile/ProfileDesktop.tsx rename to ui/snippets/user/profile/UserProfileDesktop.tsx index d690dc51a4..832d19449b 100644 --- a/ui/snippets/profile/ProfileDesktop.tsx +++ b/ui/snippets/user/profile/UserProfileDesktop.tsx @@ -8,15 +8,15 @@ import Popover from 'ui/shared/chakra/Popover'; import AuthModal from 'ui/snippets/auth/AuthModal'; import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet'; -import ProfileButton from './ProfileButton'; -import ProfileMenuContent from './ProfileMenuContent'; +import UserProfileButton from './UserProfileButton'; +import UserProfileContent from './UserProfileContent'; interface Props { buttonSize?: ButtonProps['size']; buttonVariant?: ButtonProps['variant']; } -const ProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { +const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { const router = useRouter(); const authModal = useDisclosure(); @@ -43,7 +43,7 @@ const ProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { <> - { { profileQuery.data && ( - + ) } @@ -69,4 +69,4 @@ const ProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { ); }; -export default React.memo(ProfileDesktop); +export default React.memo(UserProfileDesktop); diff --git a/ui/snippets/profile/ProfileMobile.tsx b/ui/snippets/user/profile/UserProfileMobile.tsx similarity index 86% rename from ui/snippets/profile/ProfileMobile.tsx rename to ui/snippets/user/profile/UserProfileMobile.tsx index 0337d1ef20..79dc1a46e6 100644 --- a/ui/snippets/profile/ProfileMobile.tsx +++ b/ui/snippets/user/profile/UserProfileMobile.tsx @@ -7,10 +7,10 @@ import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import AuthModal from 'ui/snippets/auth/AuthModal'; import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet'; -import ProfileButton from './ProfileButton'; -import ProfileMenuContent from './ProfileMenuContent'; +import UserProfileButton from './UserProfileButton'; +import UserProfileContent from './UserProfileContent'; -const ProfileMobile = () => { +const UserProfileMobile = () => { const router = useRouter(); const authModal = useDisclosure(); @@ -35,7 +35,7 @@ const ProfileMobile = () => { return ( <> - { - + @@ -65,4 +65,4 @@ const ProfileMobile = () => { ); }; -export default React.memo(ProfileMobile); +export default React.memo(UserProfileMobile); diff --git a/ui/snippets/profile/types.ts b/ui/snippets/user/profile/types.ts similarity index 100% rename from ui/snippets/profile/types.ts rename to ui/snippets/user/profile/types.ts diff --git a/ui/snippets/profile/utils.ts b/ui/snippets/user/profile/utils.ts similarity index 100% rename from ui/snippets/profile/utils.ts rename to ui/snippets/user/profile/utils.ts diff --git a/ui/snippets/wallet/WalletButton.tsx b/ui/snippets/user/wallet/UserWalletButton.tsx similarity index 79% rename from ui/snippets/wallet/WalletButton.tsx rename to ui/snippets/user/wallet/UserWalletButton.tsx index f5b5de548b..b1f1ee9755 100644 --- a/ui/snippets/wallet/WalletButton.tsx +++ b/ui/snippets/user/wallet/UserWalletButton.tsx @@ -4,7 +4,8 @@ import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; import shortenString from 'lib/shortenString'; -import ProfileAddressIcon from 'ui/snippets/profile/ProfileAddressIcon'; + +import UserIdenticon from '../UserIdenticon'; interface Props { size?: ButtonProps['size']; @@ -16,7 +17,7 @@ interface Props { domain?: string; } -const WalletButton = ({ size, variant, onClick, isPending, isAutoConnectDisabled, address, domain }: Props, ref: React.ForwardedRef) => { +const UserWalletButton = ({ size, variant, onClick, isPending, isAutoConnectDisabled, address, domain }: Props, ref: React.ForwardedRef) => { const isMobile = useIsMobile(); @@ -29,7 +30,7 @@ const WalletButton = ({ size, variant, onClick, isPending, isAutoConnectDisabled return ( - + { text } ); @@ -63,4 +64,4 @@ const WalletButton = ({ size, variant, onClick, isPending, isAutoConnectDisabled ); }; -export default React.memo(React.forwardRef(WalletButton)); +export default React.memo(React.forwardRef(UserWalletButton)); diff --git a/ui/snippets/wallet/WalletDesktop.tsx b/ui/snippets/user/wallet/UserWalletDesktop.tsx similarity index 90% rename from ui/snippets/wallet/WalletDesktop.tsx rename to ui/snippets/user/wallet/UserWalletDesktop.tsx index 7f8e8adbe4..4c712be18e 100644 --- a/ui/snippets/wallet/WalletDesktop.tsx +++ b/ui/snippets/user/wallet/UserWalletDesktop.tsx @@ -2,12 +2,12 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button import React from 'react'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import useWeb3AccountWithDomain from 'lib/web3/useAccountWithDomain'; import useWeb3Wallet from 'lib/web3/useWallet'; import Popover from 'ui/shared/chakra/Popover'; -import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; -import WalletButton from './WalletButton'; -import WalletMenuContent from './WalletMenuContent'; +import UserWalletButton from './UserWalletButton'; +import UserWalletMenuContent from './UserWalletMenuContent'; interface Props { buttonSize?: ButtonProps['size']; @@ -38,7 +38,7 @@ const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { return ( - { { web3AccountWithDomain.address && ( - void; } -const WalletMenuContent = ({ isAutoConnectDisabled, address, domain, onDisconnect, onOpenWallet }: Props) => { +const UserWalletMenuContent = ({ isAutoConnectDisabled, address, domain, onDisconnect, onOpenWallet }: Props) => { const handleOpenWalletClick = React.useCallback(async() => { await delay(100); @@ -24,7 +24,7 @@ const WalletMenuContent = ({ isAutoConnectDisabled, address, domain, onDisconnec return ( - { isAutoConnectDisabled && } + { isAutoConnectDisabled && } My wallet Your wallet is used to interact with apps and contracts in the explorer. @@ -54,4 +54,4 @@ const WalletMenuContent = ({ isAutoConnectDisabled, address, domain, onDisconnec ); }; -export default React.memo(WalletMenuContent); +export default React.memo(UserWalletMenuContent); diff --git a/ui/snippets/wallet/WalletMobile.tsx b/ui/snippets/user/wallet/UserWalletMobile.tsx similarity index 85% rename from ui/snippets/wallet/WalletMobile.tsx rename to ui/snippets/user/wallet/UserWalletMobile.tsx index 5e7172eea0..da6631bc81 100644 --- a/ui/snippets/wallet/WalletMobile.tsx +++ b/ui/snippets/user/wallet/UserWalletMobile.tsx @@ -2,13 +2,13 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from import React from 'react'; import { useMarketplaceContext } from 'lib/contexts/marketplace'; +import useWeb3AccountWithDomain from 'lib/web3/useAccountWithDomain'; import useWeb3Wallet from 'lib/web3/useWallet'; -import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; -import WalletButton from './WalletButton'; -import WalletMenuContent from './WalletMenuContent'; +import UserWalletButton from './UserWalletButton'; +import UserWalletMenuContent from './UserWalletMenuContent'; -const WalletMobile = () => { +const UserWalletMobile = () => { const walletMenu = useDisclosure(); const web3Wallet = useWeb3Wallet({ source: 'Header' }); @@ -31,7 +31,7 @@ const WalletMobile = () => { return ( <> - { - { ); }; -export default React.memo(WalletMobile); +export default React.memo(UserWalletMobile); From 1970de13659e866086fd3801d4bd792c91ff38ad Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 20 Sep 2024 13:30:14 +0200 Subject: [PATCH 19/49] workflow to link wallet or email to account --- lib/hooks/useFetchProfileInfo.tsx | 1 + lib/hooks/useHasAccount.ts | 6 ++- ui/snippets/auth/AuthModal.tsx | 52 ++++++++++++++----- .../screens/AuthModalScreenConnectWallet.tsx | 11 ++-- .../auth/screens/AuthModalScreenEmail.tsx | 7 +-- .../auth/screens/AuthModalScreenOtpCode.tsx | 11 ++-- .../AuthModalScreenSuccessCreatedEmail.tsx | 14 ----- .../AuthModalScreenSuccessCreatedWallet.tsx | 31 ----------- .../screens/AuthModalScreenSuccessEmail.tsx | 45 ++++++++++++++++ .../screens/AuthModalScreenSuccessWallet.tsx | 48 +++++++++++++++++ ui/snippets/auth/types.ts | 24 ++++++--- ui/snippets/auth/useLogout.tsx | 14 +++-- .../user/profile/UserProfileContent.tsx | 12 +++-- .../user/profile/UserProfileDesktop.tsx | 14 ++++- .../user/profile/UserProfileMobile.tsx | 14 ++++- 15 files changed, 212 insertions(+), 92 deletions(-) delete mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx delete mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx create mode 100644 ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx diff --git a/lib/hooks/useFetchProfileInfo.tsx b/lib/hooks/useFetchProfileInfo.tsx index 4df2f8d57b..b412c660f8 100644 --- a/lib/hooks/useFetchProfileInfo.tsx +++ b/lib/hooks/useFetchProfileInfo.tsx @@ -1,6 +1,7 @@ import useApiQuery from 'lib/api/useApiQuery'; import * as cookies from 'lib/cookies'; +// TODO @tom2drum move to auth export default function useFetchProfileInfo() { return useApiQuery('user_info', { queryOptions: { diff --git a/lib/hooks/useHasAccount.ts b/lib/hooks/useHasAccount.ts index d314f6f36a..447c5a1ca0 100644 --- a/lib/hooks/useHasAccount.ts +++ b/lib/hooks/useHasAccount.ts @@ -2,14 +2,18 @@ import config from 'configs/app'; import { useAppContext } from 'lib/contexts/app'; import * as cookies from 'lib/cookies'; +import useFetchProfileInfo from './useFetchProfileInfo'; + +// TODO @tom2drum move to auth export default function useHasAccount() { const appProps = useAppContext(); + const profileQuery = useFetchProfileInfo(); if (!config.features.account.isEnabled) { return false; } const cookiesString = appProps.cookies; - const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, cookiesString)); + const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, cookiesString) || profileQuery.data); return hasAuth; } diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx index 22e0c09f86..468d40a167 100644 --- a/ui/snippets/auth/AuthModal.tsx +++ b/ui/snippets/auth/AuthModal.tsx @@ -1,16 +1,17 @@ import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import React from 'react'; -import type { Screen } from './types'; +import type { Screen, ScreenSuccess } from './types'; +import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import IconSvg from 'ui/shared/IconSvg'; import AuthModalScreenConnectWallet from './screens/AuthModalScreenConnectWallet'; import AuthModalScreenEmail from './screens/AuthModalScreenEmail'; import AuthModalScreenOtpCode from './screens/AuthModalScreenOtpCode'; import AuthModalScreenSelectMethod from './screens/AuthModalScreenSelectMethod'; -import AuthModalScreenSuccessCreatedEmail from './screens/AuthModalScreenSuccessCreatedEmail'; -import AuthModalScreenSuccessCreatedWallet from './screens/AuthModalScreenSuccessCreatedWallet'; +import AuthModalScreenSuccessEmail from './screens/AuthModalScreenSuccessEmail'; +import AuthModalScreenSuccessWallet from './screens/AuthModalScreenSuccessWallet'; interface Props { initialScreen: Screen; @@ -19,6 +20,7 @@ interface Props { const AuthModal = ({ initialScreen, onClose }: Props) => { const [ steps, setSteps ] = React.useState>([ initialScreen ]); + const profileQuery = useFetchProfileInfo(); const onNextStep = React.useCallback((screen: Screen) => { setSteps((prev) => [ ...prev, screen ]); @@ -32,19 +34,27 @@ const AuthModal = ({ initialScreen, onClose }: Props) => { setSteps([ initialScreen ]); }, [ initialScreen ]); + const onAuthSuccess = React.useCallback(async(screen: ScreenSuccess) => { + const { data } = await profileQuery.refetch(); + if (data) { + onNextStep({ ...screen, profile: data }); + } + // TODO @tom2drum handle error case + }, [ onNextStep, profileQuery ]); + const header = (() => { const currentStep = steps[steps.length - 1]; switch (currentStep.type) { case 'select_method': return 'Select a way to connect'; case 'connect_wallet': - return 'Continue with wallet'; + return currentStep.isAuth ? 'Add wallet' : 'Continue with wallet'; case 'email': - return currentStep.isAccountExists ? 'Add email' : 'Continue with email'; + return currentStep.isAuth ? 'Add email' : 'Continue with email'; case 'otp_code': return 'Confirmation code'; - case 'success_created_email': - case 'success_created_wallet': + case 'success_email': + case 'success_wallet': return 'Congrats!'; } })(); @@ -55,15 +65,29 @@ const AuthModal = ({ initialScreen, onClose }: Props) => { case 'select_method': return ; case 'connect_wallet': - return ; + return ; case 'email': - return ; + return ; case 'otp_code': - return ; - case 'success_created_email': - return ; - case 'success_created_wallet': - return ; + return ; + case 'success_email': + return ( + + ); + case 'success_wallet': + return ( + + ); } })(); diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx index d2d9efc661..67cd37c94c 100644 --- a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -1,21 +1,22 @@ import { Center, Spinner } from '@chakra-ui/react'; import React from 'react'; -import type { Screen } from '../types'; +import type { ScreenSuccess } from '../types'; import useSignInWithWallet from '../useSignInWithWallet'; interface Props { - onSuccess: (screen: Screen) => void; + onSuccess: (screen: ScreenSuccess) => void; onError: () => void; + isAuth?: boolean; } -const AuthModalScreenConnectWallet = ({ onSuccess, onError }: Props) => { +const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth }: Props) => { const isStartedRef = React.useRef(false); const handleSignInSuccess = React.useCallback(({ address }: { address: string }) => { - onSuccess({ type: 'success_created_wallet', address }); - }, [ onSuccess ]); + onSuccess({ type: 'success_wallet', address, isAuth }); + }, [ onSuccess, isAuth ]); const handleSignInError = React.useCallback(() => { onError(); diff --git a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx index 5595cd6d14..c03cfc828a 100644 --- a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx @@ -14,9 +14,10 @@ import AuthModalFieldEmail from '../fields/AuthModalFieldEmail'; interface Props { onSubmit: (screen: Screen) => void; + isAuth?: boolean; } -const AuthModalScreenEmail = ({ onSubmit }: Props) => { +const AuthModalScreenEmail = ({ onSubmit, isAuth }: Props) => { const apiFetch = useApiFetch(); const toast = useToast(); @@ -39,7 +40,7 @@ const AuthModalScreenEmail = ({ onSubmit }: Props) => { }, }) .then(() => { - onSubmit({ type: 'otp_code', email: formData.email }); + onSubmit({ type: 'otp_code', email: formData.email, isAuth }); }) .catch((error) => { toast({ @@ -48,7 +49,7 @@ const AuthModalScreenEmail = ({ onSubmit }: Props) => { description: getErrorMessage(error) || 'Something went wrong', }); }); - }, [ apiFetch, onSubmit, toast ]); + }, [ apiFetch, onSubmit, toast, isAuth ]); return ( diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx index bebefeaffc..a932b07f70 100644 --- a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form'; -import type { OtpCodeFormFields, Screen } from '../types'; +import type { OtpCodeFormFields, ScreenSuccess } from '../types'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; @@ -14,10 +14,11 @@ import AuthModalFieldOtpCode from '../fields/AuthModalFieldOtpCode'; interface Props { email: string; - onSubmit: (screen: Screen) => void; + onSuccess: (screen: ScreenSuccess) => void; + isAuth?: boolean; } -const AuthModalScreenOtpCode = ({ email, onSubmit }: Props) => { +const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { const apiFetch = useApiFetch(); const toast = useToast(); @@ -40,7 +41,7 @@ const AuthModalScreenOtpCode = ({ email, onSubmit }: Props) => { }, }) .then(() => { - onSubmit({ type: 'success_created_email' }); + onSuccess({ type: 'success_email', email, isAuth }); }) .catch((error) => { // TODO @tom2drum handle incorrect code error @@ -50,7 +51,7 @@ const AuthModalScreenOtpCode = ({ email, onSubmit }: Props) => { description: getErrorMessage(error) || 'Something went wrong', }); }); - }, [ apiFetch, email, onSubmit, toast ]); + }, [ apiFetch, email, onSuccess, toast, isAuth ]); const handleResendCodeClick = React.useCallback(() => { return apiFetch('auth_send_otp', { diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx deleted file mode 100644 index cf6de510cc..0000000000 --- a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedEmail.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Box, Text, Button } from '@chakra-ui/react'; -import React from 'react'; - -const AuthModalScreenSuccessCreatedEmail = () => { - return ( - - Your account was successfully created! - Connect a web3 wallet to safely interact with smart contracts and dapps inside Blockscout. - - - ); -}; - -export default React.memo(AuthModalScreenSuccessCreatedEmail); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx deleted file mode 100644 index f2c3085e4f..0000000000 --- a/ui/snippets/auth/screens/AuthModalScreenSuccessCreatedWallet.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { chakra, Box, Text, Button } from '@chakra-ui/react'; -import React from 'react'; - -import type { Screen } from '../types'; - -import shortenString from 'lib/shortenString'; - -interface Props { - address: string; - onAddEmail: (screen: Screen) => void; -} - -const AuthModalScreenSuccessCreatedWallet = ({ address, onAddEmail }: Props) => { - const handleAddEmailClick = React.useCallback(() => { - onAddEmail({ type: 'email', isAccountExists: true }); - }, [ onAddEmail ]); - - return ( - - - Your account was linked to{ ' ' } - { shortenString(address) }{ ' ' } - wallet. Use for the next login. - - Add your email to receive notifications about addresses in your watch list. - - - ); -}; - -export default React.memo(AuthModalScreenSuccessCreatedWallet); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx new file mode 100644 index 0000000000..df21346ce6 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx @@ -0,0 +1,45 @@ +import { chakra, Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; +import type { UserInfo } from 'types/api/account'; + +interface Props { + email: string; + onConnectWallet: (screen: Screen) => void; + isAuth?: boolean; + profile: UserInfo | undefined; +} + +const AuthModalScreenSuccessEmail = ({ email, onConnectWallet, isAuth, profile }: Props) => { + const handleConnectWalletClick = React.useCallback(() => { + onConnectWallet({ type: 'connect_wallet', isAuth: true }); + }, [ onConnectWallet ]); + + if (isAuth) { + return ( + + Your account was linked to{ ' ' } + { email }{ ' ' } + email. Use for the next login. + + ); + } + + return ( + + + { email }{ ' ' } + email has been successfully used to log in to your Blockscout account. + + { !profile?.address_hash && ( + <> + Add your web3 wallet to safely interact with smart contracts and dapps inside Blockscout. + + + ) } + + ); +}; + +export default React.memo(AuthModalScreenSuccessEmail); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx new file mode 100644 index 0000000000..75c59a5cfe --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx @@ -0,0 +1,48 @@ +import { chakra, Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; +import type { UserInfo } from 'types/api/account'; + +import shortenString from 'lib/shortenString'; + +interface Props { + address: string; + onAddEmail: (screen: Screen) => void; + isAuth?: boolean; + profile: UserInfo | undefined; +} + +const AuthModalScreenSuccessWallet = ({ address, onAddEmail, isAuth, profile }: Props) => { + const handleAddEmailClick = React.useCallback(() => { + onAddEmail({ type: 'email', isAuth: true }); + }, [ onAddEmail ]); + + if (isAuth) { + return ( + + Your account was linked to{ ' ' } + { shortenString(address) }{ ' ' } + wallet. Use for the next login. + + ); + } + + return ( + + + Wallet{ ' ' } + { shortenString(address) }{ ' ' } + has been successfully used to log in to your Blockscout account. + + { !profile?.email && ( + <> + Add your email to receive notifications about addresses in your watch list. + + + ) } + + ); +}; + +export default React.memo(AuthModalScreenSuccessWallet); diff --git a/ui/snippets/auth/types.ts b/ui/snippets/auth/types.ts index 901f0c2263..9e1648085e 100644 --- a/ui/snippets/auth/types.ts +++ b/ui/snippets/auth/types.ts @@ -1,19 +1,29 @@ +import type { UserInfo } from 'types/api/account'; + +export type ScreenSuccess = { + type: 'success_email'; + email: string; + profile?: UserInfo; + isAuth?: boolean; +} | { + type: 'success_wallet'; + address: string; + profile?: UserInfo; + isAuth?: boolean; +} export type Screen = { type: 'select_method'; } | { type: 'connect_wallet'; + isAuth?: boolean; } | { type: 'email'; - isAccountExists?: boolean; + isAuth?: boolean; } | { type: 'otp_code'; email: string; -} | { - type: 'success_created_email'; -} | { - type: 'success_created_wallet'; - address: string; -} + isAuth?: boolean; +} | ScreenSuccess; export interface EmailFormFields { email: string; diff --git a/ui/snippets/auth/useLogout.tsx b/ui/snippets/auth/useLogout.tsx index d4ca6d8a14..1c759effbe 100644 --- a/ui/snippets/auth/useLogout.tsx +++ b/ui/snippets/auth/useLogout.tsx @@ -1,8 +1,10 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; import type { Route } from 'nextjs-routes'; +import { getResourceKey } from 'lib/api/useApiQuery'; import * as cookies from 'lib/cookies'; const PROTECTED_ROUTES: Array = [ @@ -17,17 +19,21 @@ const PROTECTED_ROUTES: Array = [ export default function useLogout() { const router = useRouter(); + const queryClient = useQueryClient(); return React.useCallback(async() => { cookies.remove(cookies.NAMES.API_TOKEN); + queryClient.resetQueries({ + queryKey: getResourceKey('user_info'), + exact: true, + }); + if ( PROTECTED_ROUTES.includes(router.pathname) || (router.pathname === '/txs' && router.query.tab === 'watchlist') ) { - window.location.assign('/'); - } else { - window.location.reload(); + router.push({ pathname: '/' }, undefined, { shallow: true }); } - }, [ router ]); + }, [ queryClient, router ]); } diff --git a/ui/snippets/user/profile/UserProfileContent.tsx b/ui/snippets/user/profile/UserProfileContent.tsx index d1ca949ad6..420295ea5c 100644 --- a/ui/snippets/user/profile/UserProfileContent.tsx +++ b/ui/snippets/user/profile/UserProfileContent.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, Flex, Text, VStack } from '@chakra-ui/react'; +import { Box, Divider, Flex, Link, Text, VStack } from '@chakra-ui/react'; import React from 'react'; import type { NavLink } from './types'; @@ -45,10 +45,11 @@ const navLinks: Array = [ interface Props { data: UserInfo; - onClose?: () => void; + onClose: () => void; + onAddEmail: () => void; } -const UserProfileContent = ({ data, onClose }: Props) => { +const UserProfileContent = ({ data, onClose, onAddEmail }: Props) => { const { isAutoConnectDisabled } = useMarketplaceContext(); const logout = useLogout(); @@ -63,7 +64,10 @@ const UserProfileContent = ({ data, onClose }: Props) => { icon="profile" onClick={ onClose } /> - { data?.email && { getUserHandle(data.email) } } + { data?.email ? + { getUserHandle(data.email) } : + Add email + } { config.features.blockchainInteraction.isEnabled ? : } diff --git a/ui/snippets/user/profile/UserProfileDesktop.tsx b/ui/snippets/user/profile/UserProfileDesktop.tsx index 832d19449b..ea800e701a 100644 --- a/ui/snippets/user/profile/UserProfileDesktop.tsx +++ b/ui/snippets/user/profile/UserProfileDesktop.tsx @@ -2,6 +2,8 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button import { useRouter } from 'next/router'; import React from 'react'; +import type { Screen } from 'ui/snippets/auth/types'; + import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import Popover from 'ui/shared/chakra/Popover'; @@ -17,6 +19,9 @@ interface Props { } const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { + const [ authInitialScreen, setAuthInitialScreen ] = React.useState({ + type: config.features.blockchainInteraction.isEnabled ? 'select_method' : 'email', + }); const router = useRouter(); const authModal = useDisclosure(); @@ -39,6 +44,11 @@ const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => authModal.onOpen(); }, [ profileQuery.data, router.pathname, authModal, profileMenu, signInWithWallet ]); + const handleAddEmailClick = React.useCallback(() => { + setAuthInitialScreen({ type: 'email', isAuth: true }); + authModal.onOpen(); + }, [ authModal ]); + return ( <> @@ -54,7 +64,7 @@ const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { profileQuery.data && ( - + ) } @@ -62,7 +72,7 @@ const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { authModal.isOpen && ( ) } diff --git a/ui/snippets/user/profile/UserProfileMobile.tsx b/ui/snippets/user/profile/UserProfileMobile.tsx index 79dc1a46e6..7c14a35870 100644 --- a/ui/snippets/user/profile/UserProfileMobile.tsx +++ b/ui/snippets/user/profile/UserProfileMobile.tsx @@ -2,6 +2,8 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from import { useRouter } from 'next/router'; import React from 'react'; +import type { Screen } from 'ui/snippets/auth/types'; + import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import AuthModal from 'ui/snippets/auth/AuthModal'; @@ -11,6 +13,9 @@ import UserProfileButton from './UserProfileButton'; import UserProfileContent from './UserProfileContent'; const UserProfileMobile = () => { + const [ authInitialScreen, setAuthInitialScreen ] = React.useState({ + type: config.features.blockchainInteraction.isEnabled ? 'select_method' : 'email', + }); const router = useRouter(); const authModal = useDisclosure(); @@ -33,6 +38,11 @@ const UserProfileMobile = () => { authModal.onOpen(); }, [ profileQuery.data, router.pathname, authModal, profileMenu, signInWithWallet ]); + const handleAddEmailClick = React.useCallback(() => { + setAuthInitialScreen({ type: 'email', isAuth: true }); + authModal.onOpen(); + }, [ authModal ]); + return ( <> { - + @@ -58,7 +68,7 @@ const UserProfileMobile = () => { { authModal.isOpen && ( ) } From ee71acd810c4bd77039008cfd15a71d6491d499b Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 20 Sep 2024 14:04:11 +0200 Subject: [PATCH 20/49] link wallet from profile --- ui/myProfile/MyProfileWallet.tsx | 18 +++++++++++--- ui/pages/MyProfile.tsx | 24 +++++++++++++++---- ui/snippets/auth/AuthModal.tsx | 6 ++--- .../screens/AuthModalScreenConnectWallet.tsx | 6 ++--- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ui/myProfile/MyProfileWallet.tsx b/ui/myProfile/MyProfileWallet.tsx index c034193a51..d7871ee4ec 100644 --- a/ui/myProfile/MyProfileWallet.tsx +++ b/ui/myProfile/MyProfileWallet.tsx @@ -1,18 +1,30 @@ -import { Button, Heading } from '@chakra-ui/react'; +import { Box, Button, Heading, useColorModeValue } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; import type { UserInfo } from 'types/api/account'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; + interface Props { profileQuery: UseQueryResult; + onAddWallet: () => void; } -const MyProfileWallet = ({ profileQuery }: Props) => { +const MyProfileWallet = ({ profileQuery, onAddWallet }: Props) => { + const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + return (

My linked wallet - { !profileQuery.data?.address_hash && } + { profileQuery.data?.address_hash ? ( + + + + ) : }
); }; diff --git a/ui/pages/MyProfile.tsx b/ui/pages/MyProfile.tsx index 0b12b7ce3c..0912cad5c2 100644 --- a/ui/pages/MyProfile.tsx +++ b/ui/pages/MyProfile.tsx @@ -1,6 +1,8 @@ -import { Flex } from '@chakra-ui/react'; +import { Flex, useDisclosure } from '@chakra-ui/react'; import React from 'react'; +import type { Screen } from 'ui/snippets/auth/types'; + import config from 'configs/app'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; @@ -9,11 +11,20 @@ import MyProfileWallet from 'ui/myProfile/MyProfileWallet'; import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import PageTitle from 'ui/shared/Page/PageTitle'; +import AuthModal from 'ui/snippets/auth/AuthModal'; const MyProfile = () => { + const [ authInitialScreen, setAuthInitialScreen ] = React.useState(); + const authModal = useDisclosure(); + const profileQuery = useFetchProfileInfo(); useRedirectForInvalidAuthToken(); + const handleAddWalletClick = React.useCallback(() => { + setAuthInitialScreen({ type: 'connect_wallet', isAuth: true }); + authModal.onOpen(); + }, [ authModal ]); + const content = (() => { if (profileQuery.isPending) { return ; @@ -24,10 +35,13 @@ const MyProfile = () => { } return ( - - - { config.features.blockchainInteraction.isEnabled && } - + <> + + + { config.features.blockchainInteraction.isEnabled && } + + { authModal.isOpen && authInitialScreen && } + ); })(); diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx index 468d40a167..59617a2bc5 100644 --- a/ui/snippets/auth/AuthModal.tsx +++ b/ui/snippets/auth/AuthModal.tsx @@ -30,9 +30,9 @@ const AuthModal = ({ initialScreen, onClose }: Props) => { setSteps((prev) => prev.length > 1 ? prev.slice(0, -1) : prev); }, []); - const onReset = React.useCallback(() => { - setSteps([ initialScreen ]); - }, [ initialScreen ]); + const onReset = React.useCallback((isAuth?: boolean) => { + isAuth ? onClose() : setSteps([ initialScreen ]); + }, [ initialScreen, onClose ]); const onAuthSuccess = React.useCallback(async(screen: ScreenSuccess) => { const { data } = await profileQuery.refetch(); diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx index 67cd37c94c..93018f73ee 100644 --- a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -7,7 +7,7 @@ import useSignInWithWallet from '../useSignInWithWallet'; interface Props { onSuccess: (screen: ScreenSuccess) => void; - onError: () => void; + onError: (isAuth?: boolean) => void; isAuth?: boolean; } @@ -19,8 +19,8 @@ const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth }: Props) => }, [ onSuccess, isAuth ]); const handleSignInError = React.useCallback(() => { - onError(); - }, [ onError ]); + onError(isAuth); + }, [ onError, isAuth ]); const { start } = useSignInWithWallet({ onSuccess: handleSignInSuccess, onError: handleSignInError }); From da42e08b32c41d9784f63ca7b6e8f8ca7ff90d90 Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 20 Sep 2024 14:21:17 +0200 Subject: [PATCH 21/49] show better OTP code errors --- .../auth/screens/AuthModalScreenOtpCode.tsx | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx index a932b07f70..6d11fc011a 100644 --- a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -7,6 +7,7 @@ import type { OtpCodeFormFields, ScreenSuccess } from '../types'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; +import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import useToast from 'lib/hooks/useToast'; import IconSvg from 'ui/shared/IconSvg'; @@ -44,39 +45,47 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { onSuccess({ type: 'success_email', email, isAuth }); }) .catch((error) => { - // TODO @tom2drum handle incorrect code error + const apiError = getErrorObjPayload<{ message: string }>(error); + + if (apiError?.message) { + formApi.setError('code', { message: apiError.message }); + return; + } + toast({ status: 'error', title: 'Error', description: getErrorMessage(error) || 'Something went wrong', }); }); - }, [ apiFetch, email, onSuccess, toast, isAuth ]); + }, [ apiFetch, email, onSuccess, isAuth, toast, formApi ]); - const handleResendCodeClick = React.useCallback(() => { - return apiFetch('auth_send_otp', { - fetchParams: { - method: 'POST', - body: { - email, + const handleResendCodeClick = React.useCallback(async() => { + try { + formApi.clearErrors('code'); + await apiFetch('auth_send_otp', { + fetchParams: { + method: 'POST', + body: { email }, }, - }, - }) - .then(() => { - toast({ - status: 'success', - title: 'Code sent', - description: 'Code has been sent to your email', - }); - }) - .catch((error) => { - toast({ - status: 'error', - title: 'Error', - description: getErrorMessage(error) || 'Something went wrong', - }); }); - }, [ apiFetch, email, toast ]); + + toast({ + status: 'success', + title: 'Success', + description: 'Code has been sent to your email', + }); + } catch (error) { + // TODO @tom2drum check cool down error + const apiError = getErrorObjPayload<{ message: string }>(error); + + toast({ + status: 'error', + title: 'Error', + description: apiError?.message || getErrorMessage(error) || 'Something went wrong', + }); + } + }, [ apiFetch, email, formApi, toast ]); return ( From e2f7ab6dad48d99f7c3f936c0cdcd5aa4c92f6c1 Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 20 Sep 2024 15:29:55 +0200 Subject: [PATCH 22/49] add email alert on watchlist and verified addresses pages --- ui/pages/VerifiedAddresses.tsx | 9 +-- .../VerifiedAddressesEmailAlert.tsx | 28 +++++++++ ui/watchlist/AddressModal/AddressForm.tsx | 57 ++++++++++++++----- 3 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 ui/verifiedAddresses/VerifiedAddressesEmailAlert.tsx diff --git a/ui/pages/VerifiedAddresses.tsx b/ui/pages/VerifiedAddresses.tsx index 417ad31710..afd4a3f987 100644 --- a/ui/pages/VerifiedAddresses.tsx +++ b/ui/pages/VerifiedAddresses.tsx @@ -1,4 +1,4 @@ -import { OrderedList, ListItem, chakra, Button, useDisclosure, Show, Hide, Skeleton, Link, Alert } from '@chakra-ui/react'; +import { OrderedList, ListItem, chakra, Button, useDisclosure, Show, Hide, Skeleton, Link } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -18,6 +18,7 @@ import DataListDisplay from 'ui/shared/DataListDisplay'; import PageTitle from 'ui/shared/Page/PageTitle'; import AdminSupportText from 'ui/shared/texts/AdminSupportText'; import TokenInfoForm from 'ui/tokenInfo/TokenInfoForm'; +import VerifiedAddressesEmailAlert from 'ui/verifiedAddresses/VerifiedAddressesEmailAlert'; import VerifiedAddressesListItem from 'ui/verifiedAddresses/VerifiedAddressesListItem'; import VerifiedAddressesTable from 'ui/verifiedAddresses/VerifiedAddressesTable'; @@ -193,11 +194,7 @@ const VerifiedAddresses = () => { return ( <> - { userWithoutEmail && ( - - You need a valid email address to verify addresses. Please logout of MyAccount then login using your email to proceed. - - ) } + { userWithoutEmail && } Verify ownership of a smart contract address to easily update information in Blockscout. diff --git a/ui/verifiedAddresses/VerifiedAddressesEmailAlert.tsx b/ui/verifiedAddresses/VerifiedAddressesEmailAlert.tsx new file mode 100644 index 0000000000..7720a4dd89 --- /dev/null +++ b/ui/verifiedAddresses/VerifiedAddressesEmailAlert.tsx @@ -0,0 +1,28 @@ +import { Alert, Button, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import AuthModal from 'ui/snippets/auth/AuthModal'; + +const VerifiedAddressesEmailAlert = () => { + const authModal = useDisclosure(); + + return ( + <> + + You need a valid email address to verify contracts. Please add your email to your account. + + + { authModal.isOpen && } + + ); +}; + +export default React.memo(VerifiedAddressesEmailAlert); diff --git a/ui/watchlist/AddressModal/AddressForm.tsx b/ui/watchlist/AddressModal/AddressForm.tsx index 23c880f136..b319310aec 100644 --- a/ui/watchlist/AddressModal/AddressForm.tsx +++ b/ui/watchlist/AddressModal/AddressForm.tsx @@ -1,7 +1,9 @@ import { + Alert, Box, Button, Text, + useDisclosure, } from '@chakra-ui/react'; import { useMutation } from '@tanstack/react-query'; import React, { useCallback, useState } from 'react'; @@ -13,10 +15,12 @@ import type { WatchlistAddress, WatchlistErrors } from 'types/api/account'; import type { ResourceErrorAccount } from 'lib/api/resources'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/getErrorMessage'; +import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import { ADDRESS_REGEXP } from 'lib/validations/address'; import AddressInput from 'ui/shared/AddressInput'; import CheckboxInput from 'ui/shared/CheckboxInput'; import TagInput from 'ui/shared/TagInput'; +import AuthModal from 'ui/snippets/auth/AuthModal'; import AddressFormNotifications from './AddressFormNotifications'; @@ -69,9 +73,13 @@ type Checkboxes = 'notification' | const AddressForm: React.FC = ({ data, onSuccess, setAlertVisible, isAdd }) => { const [ pending, setPending ] = useState(false); + const profileQuery = useFetchProfileInfo(); + const userWithoutEmail = profileQuery.data && !profileQuery.data.email; + const authModal = useDisclosure(); + let notificationsDefault = {} as Inputs['notification_settings']; if (!data?.notification_settings) { - NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: true, outcoming: true }); + NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: !userWithoutEmail, outcoming: !userWithoutEmail }); } else { notificationsDefault = data.notification_settings; } @@ -80,7 +88,7 @@ const AddressForm: React.FC = ({ data, onSuccess, setAlertVisible, isAdd defaultValues: { address: data?.address_hash || '', tag: data?.name || '', - notification: data?.notification_methods ? data.notification_methods.email : true, + notification: data?.notification_methods ? data.notification_methods.email : !userWithoutEmail, notification_settings: notificationsDefault, }, mode: 'onTouched', @@ -179,18 +187,39 @@ const AddressForm: React.FC = ({ data, onSuccess, setAlertVisible, isAdd render={ renderTagInput } /> - - Please select what types of notifications you will receive - - - - - Notification methods - + { userWithoutEmail ? ( + <> + + To receive notifications you need to add an email to your profile. + + + { authModal.isOpen && } + + ) : ( + <> + + Please select what types of notifications you will receive + + + + + Notification methods + + + ) }