Commit 460883d9 authored by tom's avatar tom

my profile page

parent a0c383ef
...@@ -3,12 +3,12 @@ import React from 'react'; ...@@ -3,12 +3,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// import MyProfile from 'ui/pages/MyProfile'; import MyProfile from 'ui/pages/MyProfile';
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/auth/profile"> <PageNextJs pathname="/auth/profile">
{ /* <MyProfile/> */ } <MyProfile/>
</PageNextJs> </PageNextJs>
); );
}; };
......
import { Button, chakra, Heading, useDisclosure } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
...@@ -11,8 +11,11 @@ import config from 'configs/app'; ...@@ -11,8 +11,11 @@ import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorMessage from 'lib/errors/getErrorMessage';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
import useToast from 'lib/hooks/useToast';
import * as mixpanel from 'lib/mixpanel'; import * as mixpanel from 'lib/mixpanel';
import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { toaster } from 'toolkit/chakra/toaster';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
...@@ -33,7 +36,6 @@ interface Props { ...@@ -33,7 +36,6 @@ interface Props {
const MyProfileEmail = ({ profileQuery }: Props) => { const MyProfileEmail = ({ profileQuery }: Props) => {
const authModal = useDisclosure(); const authModal = useDisclosure();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const toast = useToast();
const recaptcha = useReCaptcha(); const recaptcha = useReCaptcha();
const formApi = useForm<FormFields>({ const formApi = useForm<FormFields>({
...@@ -65,25 +67,24 @@ const MyProfileEmail = ({ profileQuery }: Props) => { ...@@ -65,25 +67,24 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
authModal.onOpen(); authModal.onOpen();
} catch (error) { } catch (error) {
const apiError = getErrorObjPayload<{ message: string }>(error); const apiError = getErrorObjPayload<{ message: string }>(error);
toast({ toaster.error({
status: 'error',
title: 'Error', title: 'Error',
description: apiError?.message || getErrorMessage(error) || 'Something went wrong', description: apiError?.message || getErrorMessage(error) || 'Something went wrong',
}); });
} }
}, [ apiFetch, authModal, toast, recaptcha ]); }, [ apiFetch, authModal, recaptcha ]);
const hasDirtyFields = Object.keys(formApi.formState.dirtyFields).length > 0; const hasDirtyFields = Object.keys(formApi.formState.dirtyFields).length > 0;
return ( return (
<section> <section>
<Heading as="h2" size="sm" mb={ 3 }>Notifications</Heading> <Heading level="2" mb={ 3 }>Notifications</Heading>
<FormProvider { ...formApi }> <FormProvider { ...formApi }>
<chakra.form <chakra.form
noValidate noValidate
onSubmit={ formApi.handleSubmit(onFormSubmit) } onSubmit={ formApi.handleSubmit(onFormSubmit) }
> >
<FormFieldText<FormFields> name="name" placeholder="Name" isReadOnly mb={ 3 }/> <FormFieldText<FormFields> name="name" placeholder="Name" readOnly mb={ 3 }/>
<MyProfileFieldsEmail <MyProfileFieldsEmail
isReadOnly={ !config.services.reCaptchaV2.siteKey || Boolean(profileQuery.data?.email) } isReadOnly={ !config.services.reCaptchaV2.siteKey || Boolean(profileQuery.data?.email) }
defaultValue={ profileQuery.data?.email || undefined } defaultValue={ profileQuery.data?.email || undefined }
...@@ -95,8 +96,8 @@ const MyProfileEmail = ({ profileQuery }: Props) => { ...@@ -95,8 +96,8 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
size="sm" size="sm"
variant="outline" variant="outline"
type="submit" type="submit"
isDisabled={ formApi.formState.isSubmitting || !hasDirtyFields } disabled={ formApi.formState.isSubmitting || !hasDirtyFields }
isLoading={ formApi.formState.isSubmitting } loading={ formApi.formState.isSubmitting }
loadingText="Save changes" loadingText="Save changes"
> >
Save changes Save changes
...@@ -104,7 +105,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { ...@@ -104,7 +105,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
) } ) }
</chakra.form> </chakra.form>
</FormProvider> </FormProvider>
{ authModal.isOpen && ( { authModal.open && (
<AuthModal <AuthModal
initialScreen={{ type: 'otp_code', isAuth: true, email: formApi.getValues('email') }} initialScreen={{ type: 'otp_code', isAuth: true, email: formApi.getValues('email') }}
onClose={ authModal.onClose } onClose={ authModal.onClose }
......
import { Box, Button, Heading, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Text } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { UserInfo } from 'types/api/account'; import type { UserInfo } from 'types/api/account';
import config from 'configs/app'; import config from 'configs/app';
import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { Link } from 'toolkit/chakra/link';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props { interface Props {
profileQuery: UseQueryResult<UserInfo, unknown>; profileQuery: UseQueryResult<UserInfo, unknown>;
...@@ -14,24 +16,23 @@ interface Props { ...@@ -14,24 +16,23 @@ interface Props {
} }
const MyProfileWallet = ({ profileQuery, onAddWallet }: Props) => { const MyProfileWallet = ({ profileQuery, onAddWallet }: Props) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
<section> <section>
<Heading as="h2" size="sm" mb={ 3 }>My linked wallet</Heading> <Heading level="2" mb={ 3 }>My linked wallet</Heading>
<Text mb={ 3 } > <Text mb={ 3 } >
This wallet address is used for login{ ' ' } This wallet address is used for login{ ' ' }
{ config.features.rewards.isEnabled && ( { config.features.rewards.isEnabled && (
<> <>
and participation in the Merits Program. and participation in the Merits Program.
<LinkExternal href="https://docs.blockscout.com/using-blockscout/merits" ml={ 1 }> <Link external href="https://docs.blockscout.com/using-blockscout/merits" ml={ 1 }>
Learn more Learn more
</LinkExternal> </Link>
</> </>
) } ) }
</Text> </Text>
{ profileQuery.data?.address_hash ? ( { profileQuery.data?.address_hash ? (
<Box px={ 3 } py="18px" bgColor={ bgColor } borderRadius="base"> <Box px={ 3 } py="18px" bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }} borderRadius="base">
<AddressEntity <AddressEntity
address={{ hash: profileQuery.data.address_hash }} address={{ hash: profileQuery.data.address_hash }}
fontWeight="500" fontWeight="500"
......
import { FormControl, Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useController, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import FormInputPlaceholder from 'ui/shared/forms/inputs/FormInputPlaceholder'; import FormFieldEmail from 'ui/shared/forms/fields/FormFieldEmail';
import { EMAIL_REGEXP } from 'ui/shared/forms/validators/email';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
...@@ -14,36 +11,21 @@ interface Props { ...@@ -14,36 +11,21 @@ interface Props {
} }
const MyProfileFieldsEmail = ({ isReadOnly, defaultValue }: Props) => { const MyProfileFieldsEmail = ({ isReadOnly, defaultValue }: Props) => {
const { control } = useFormContext<FormFields>();
const { field, fieldState, formState } = useController<FormFields, 'email'>({
control,
name: 'email',
rules: { required: true, pattern: EMAIL_REGEXP },
});
const isDisabled = formState.isSubmitting;
const isVerified = defaultValue && field.value === defaultValue;
return ( return (
<FormControl variant="floating" isDisabled={ isDisabled } isRequired size="md"> <FormFieldEmail<FormFields>
<InputGroup> name="email"
<Input placeholder="Email"
{ ...field } required
required readOnly={ isReadOnly }
isInvalid={ Boolean(fieldState.error) } helperText="Email for watch list notifications and private tags"
isDisabled={ isDisabled } group={{
isReadOnly={ isReadOnly } endElement: ({ field }) => {
autoComplete="off" const isVerified = defaultValue && field.value === defaultValue;
/> return isVerified ? <IconSvg name="certified" boxSize={ 5 } color="green.500"/> : null;
<FormInputPlaceholder text="Email" error={ fieldState.error }/> },
{ isVerified && ( }}
<InputRightElement h="100%"> />
<IconSvg name="certified" boxSize={ 5 } color="green.500"/>
</InputRightElement>
) }
</InputGroup>
<Text variant="secondary" mt={ 1 } fontSize="sm">Email for watch list notifications and private tags</Text>
</FormControl>
); );
}; };
......
import { Flex, useDisclosure } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Screen } from 'ui/snippets/auth/types'; import type { Screen } from 'ui/snippets/auth/types';
import config from 'configs/app'; import config from 'configs/app';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import MyProfileEmail from 'ui/myProfile/MyProfileEmail'; import MyProfileEmail from 'ui/myProfile/MyProfileEmail';
import MyProfileWallet from 'ui/myProfile/MyProfileWallet'; import MyProfileWallet from 'ui/myProfile/MyProfileWallet';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
...@@ -55,7 +56,7 @@ const MyProfile = () => { ...@@ -55,7 +56,7 @@ const MyProfile = () => {
{ config.features.blockchainInteraction.isEnabled && { config.features.blockchainInteraction.isEnabled &&
<MyProfileWallet profileQuery={ profileQuery } onAddWallet={ handleAddWalletClick }/> } <MyProfileWallet profileQuery={ profileQuery } onAddWallet={ handleAddWalletClick }/> }
</Flex> </Flex>
{ authModal.isOpen && authInitialScreen && { authModal.open && authInitialScreen &&
<AuthModal initialScreen={ authInitialScreen } onClose={ authModal.onClose } mixpanelConfig={ MIXPANEL_CONFIG }/> } <AuthModal initialScreen={ authInitialScreen } onClose={ authModal.onClose } mixpanelConfig={ MIXPANEL_CONFIG }/> }
</> </>
); );
......
...@@ -7,6 +7,7 @@ import type { FormFieldPropsBase } from './types'; ...@@ -7,6 +7,7 @@ import type { FormFieldPropsBase } from './types';
import { Field } from 'toolkit/chakra/field'; import { Field } from 'toolkit/chakra/field';
import type { InputProps } from 'toolkit/chakra/input'; import type { InputProps } from 'toolkit/chakra/input';
import { Input } from 'toolkit/chakra/input'; import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group';
import type { TextareaProps } from 'toolkit/chakra/textarea'; import type { TextareaProps } from 'toolkit/chakra/textarea';
import { Textarea } from 'toolkit/chakra/textarea'; import { Textarea } from 'toolkit/chakra/textarea';
...@@ -27,7 +28,7 @@ const FormFieldText = < ...@@ -27,7 +28,7 @@ const FormFieldText = <
placeholder, placeholder,
rules, rules,
onBlur, onBlur,
rightElement, group,
inputProps, inputProps,
asComponent, asComponent,
size = 'xl', size = 'xl',
...@@ -63,6 +64,15 @@ const FormFieldText = < ...@@ -63,6 +64,15 @@ const FormFieldText = <
/> />
); );
const content = group ? (
<InputGroup
{ ...group }
endElement={ typeof group.endElement === 'function' ? group.endElement({ field }) : group.endElement }
>
{ input }
</InputGroup>
) : input;
return ( return (
<Field <Field
label={ placeholder } label={ placeholder }
...@@ -73,35 +83,9 @@ const FormFieldText = < ...@@ -73,35 +83,9 @@ const FormFieldText = <
floating floating
{ ...restProps } { ...restProps }
> >
{ input } { content }
</Field> </Field>
); );
// TODO @tom2drum add input group
// return (
// <FormControl
// className={ className }
// variant="floating"
// isDisabled={ isDisabled }
// isRequired={ isRequired }
// size={ size }
// bgColor={ bgColor }
// >
// { rightElement ? (
// <InputGroup>
// { input }
// { inputPlaceholder }
// <InputRightElement h="100%"> { rightElement({ field }) } </InputRightElement>
// </InputGroup>
// ) : (
// <>
// { input }
// { inputPlaceholder }
// </>
// ) }
// </FormControl>
// );
}; };
export default React.memo(FormFieldText) as typeof FormFieldText; export default React.memo(FormFieldText) as typeof FormFieldText;
...@@ -3,6 +3,7 @@ import type { ControllerRenderProps, FieldValues, Path, RegisterOptions } from ' ...@@ -3,6 +3,7 @@ import type { ControllerRenderProps, FieldValues, Path, RegisterOptions } from '
import type { FieldProps } from 'toolkit/chakra/field'; import type { FieldProps } from 'toolkit/chakra/field';
import type { InputProps } from 'toolkit/chakra/input'; import type { InputProps } from 'toolkit/chakra/input';
import type { InputGroupProps } from 'toolkit/chakra/input-group';
import type { TextareaProps } from 'toolkit/chakra/textarea'; import type { TextareaProps } from 'toolkit/chakra/textarea';
export interface FormFieldPropsBase< export interface FormFieldPropsBase<
...@@ -14,6 +15,9 @@ export interface FormFieldPropsBase< ...@@ -14,6 +15,9 @@ export interface FormFieldPropsBase<
rules?: Omit<RegisterOptions<FormFields, Name>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>; rules?: Omit<RegisterOptions<FormFields, Name>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
onBlur?: () => void; onBlur?: () => void;
onChange?: () => void; onChange?: () => void;
rightElement?: ({ field }: { field: ControllerRenderProps<FormFields, Name> }) => React.ReactNode;
inputProps?: InputProps | TextareaProps; inputProps?: InputProps | TextareaProps;
group?: Omit<InputGroupProps, 'children' | 'endElement'> & {
endElement?: React.ReactNode | (({ field }: { field: ControllerRenderProps<FormFields, Name> }) => React.ReactNode);
};
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment