Commit d5cf7898 authored by tom's avatar tom

resend code and change email from profile page

parent b883f4c8
import { Button, chakra, FormControl, Heading, Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
import { Button, chakra, Heading, useDisclosure } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';
import type { SubmitHandler } from 'react-hook-form';
import { FormProvider, useForm } from 'react-hook-form';
import type { FormFields } from './types';
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';
import config from 'configs/app';
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 * as mixpanel from 'lib/mixpanel';
import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha';
import AuthModal from 'ui/snippets/auth/AuthModal';
interface FormFields {
email: string;
}
import MyProfileFieldsEmail from './fields/MyProfileFieldsEmail';
const MIXPANEL_CONFIG = {
account_link_info: {
source: 'Profile' as const,
},
};
interface Props {
profileQuery: UseQueryResult<UserInfo, unknown>;
}
const MyProfileEmail = ({ profileQuery }: Props) => {
const authModal = useDisclosure();
const apiFetch = useApiFetch();
const toast = useToast();
const formApi = useForm<FormFields>({
mode: 'onBlur',
defaultValues: {
......@@ -26,12 +41,32 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
},
});
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback((formData) => {
// eslint-disable-next-line no-console
console.log(formData);
}, [ ]);
const isDisabled = formApi.formState.isSubmitting;
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(formData) => {
try {
await apiFetch('auth_send_otp', {
fetchParams: {
method: 'POST',
body: {
email: formData.email,
recaptcha_v3_response: formData.reCaptcha,
},
},
});
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
Source: 'Profile',
Status: 'OTP sent',
Type: 'Email',
});
authModal.onOpen();
} catch (error) {
const apiError = getErrorObjPayload<{ message: string }>(error);
toast({
status: 'error',
title: 'Error',
description: apiError?.message || getErrorMessage(error) || 'Something went wrong',
});
}
}, [ apiFetch, authModal, toast ]);
return (
<section>
......@@ -41,24 +76,13 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
noValidate
onSubmit={ formApi.handleSubmit(onFormSubmit) }
>
<FormControl variant="floating" isDisabled={ isDisabled } isRequired size="md">
<InputGroup>
<Input
{ ...formApi.register('email', { required: true, pattern: EMAIL_REGEXP }) }
required
isInvalid={ Boolean(formApi.formState.errors.email) }
isDisabled={ isDisabled }
autoComplete="off"
/>
<InputPlaceholder text="Email" error={ formApi.formState.errors.email }/>
{ !formApi.formState.isDirty && (
<InputRightElement h="100%">
<IconSvg name="certified" boxSize={ 5 } color="green.500"/>
</InputRightElement>
<MyProfileFieldsEmail isReadOnly={ !config.services.reCaptchaV3.siteKey }/>
{ config.services.reCaptchaV3.siteKey && (
<GoogleReCaptchaProvider reCaptchaKey={ config.services.reCaptchaV3.siteKey }>
<FormFieldReCaptcha/>
</GoogleReCaptchaProvider>
) }
</InputGroup>
<Text variant="secondary" mt={ 1 } fontSize="sm">Email for watch list notifications and private tags</Text>
</FormControl>
{ config.services.reCaptchaV3.siteKey && (
<Button
mt={ 6 }
size="sm"
......@@ -70,8 +94,16 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
>
Save changes
</Button>
) }
</chakra.form>
</FormProvider>
{ authModal.isOpen && (
<AuthModal
initialScreen={{ type: 'otp_code', isAuth: true, email: formApi.getValues('email') }}
onClose={ authModal.onClose }
mixpanelConfig={ MIXPANEL_CONFIG }
/>
) }
</section>
);
};
......
import { FormControl, Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import { EMAIL_REGEXP } from 'lib/validations/email';
import IconSvg from 'ui/shared/IconSvg';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
interface Props {
isReadOnly?: boolean;
}
const MyProfileFieldsEmail = ({ isReadOnly }: 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;
return (
<FormControl variant="floating" isDisabled={ isDisabled } isRequired size="md">
<InputGroup>
<Input
{ ...field }
required
isInvalid={ Boolean(fieldState.error) }
isDisabled={ isDisabled }
isReadOnly={ isReadOnly }
autoComplete="off"
bgColor="dialog_bg"
/>
<InputPlaceholder text="Email" error={ fieldState.error }/>
{ !formState.isDirty && (
<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>
);
};
export default React.memo(MyProfileFieldsEmail);
export interface FormFields {
email: string;
reCaptcha: string;
}
......@@ -24,7 +24,7 @@ interface Props {
initialScreen: Screen;
onClose: (isSuccess?: boolean) => void;
mixpanelConfig?: {
'wallet_connect': {
'wallet_connect'?: {
source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
};
'account_link_info': {
......@@ -124,7 +124,7 @@ const AuthModal = ({ initialScreen, onClose, mixpanelConfig }: Props) => {
onSuccess={ onAuthSuccess }
onError={ onReset }
isAuth={ currentStep.isAuth }
source={ mixpanelConfig?.wallet_connect.source }
source={ mixpanelConfig?.wallet_connect?.source }
/>
);
case 'email':
......
......@@ -8,7 +8,11 @@ import PinInput from 'ui/shared/chakra/PinInput';
const CODE_LENGTH = 6;
const AuthModalFieldOtpCode = () => {
interface Props {
isDisabled?: boolean;
}
const AuthModalFieldOtpCode = ({ isDisabled: isDisabledProp }: Props) => {
const { control } = useFormContext<OtpCodeFormFields>();
const { field, fieldState, formState } = useController<OtpCodeFormFields, 'code'>({
control,
......@@ -16,7 +20,7 @@ const AuthModalFieldOtpCode = () => {
rules: { required: true, minLength: CODE_LENGTH, maxLength: CODE_LENGTH },
});
const isDisabled = formState.isSubmitting;
const isDisabled = isDisabledProp || formState.isSubmitting;
return (
<>
......
......@@ -37,9 +37,9 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
});
const onFormSubmit: SubmitHandler<EmailFormFields> = React.useCallback(async(formData) => {
try {
const token = await executeRecaptcha?.();
return apiFetch('auth_send_otp', {
await apiFetch('auth_send_otp', {
fetchParams: {
method: 'POST',
body: {
......@@ -47,10 +47,9 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
recaptcha_v3_response: token,
},
},
})
.then(() => {
});
if (isAuth) {
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
mixpanelConfig?.account_link_info.source !== 'Profile' && mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown',
Status: 'OTP sent',
Type: 'Email',
......@@ -62,14 +61,13 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
});
}
onSubmit({ type: 'otp_code', email: formData.email, isAuth });
})
.catch((error) => {
} catch (error) {
toast({
status: 'error',
title: 'Error',
description: getErrorMessage(error) || 'Something went wrong',
});
});
}
}, [ executeRecaptcha, apiFetch, isAuth, onSubmit, mixpanelConfig?.account_link_info.source, toast ]);
return (
......@@ -83,7 +81,7 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
<Button
mt={ 6 }
type="submit"
isDisabled={ formApi.formState.isSubmitting || !formApi.formState.isValid }
isDisabled={ formApi.formState.isSubmitting }
isLoading={ formApi.formState.isSubmitting }
loadingText="Send a code"
>
......
import { chakra, Box, Text, Button, Link } from '@chakra-ui/react';
import { chakra, Box, Text, Button } from '@chakra-ui/react';
import React from 'react';
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';
import type { SubmitHandler } from 'react-hook-form';
import { FormProvider, useForm } from 'react-hook-form';
......@@ -23,6 +24,8 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
const apiFetch = useApiFetch();
const toast = useToast();
const { executeRecaptcha } = useGoogleReCaptcha();
const [ isCodeSending, setIsCodeSending ] = React.useState(false);
const formApi = useForm<OtpCodeFormFields>({
mode: 'onBlur',
......@@ -63,10 +66,12 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
const handleResendCodeClick = React.useCallback(async() => {
try {
formApi.clearErrors('code');
setIsCodeSending(true);
const token = await executeRecaptcha?.();
await apiFetch('auth_send_otp', {
fetchParams: {
method: 'POST',
body: { email },
body: { email, recaptcha_v3_response: token },
},
});
......@@ -84,8 +89,10 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
title: 'Error',
description: apiError?.message || getErrorMessage(error) || 'Something went wrong',
});
} finally {
setIsCodeSending(false);
}
}, [ apiFetch, email, formApi, toast ]);
}, [ apiFetch, email, executeRecaptcha, formApi, toast ]);
return (
<FormProvider { ...formApi }>
......@@ -98,22 +105,27 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
<chakra.span fontWeight="700">{ email }</chakra.span>{ ' ' }
and enter your code below.
</Text>
<AuthModalFieldOtpCode/>
<Link
<AuthModalFieldOtpCode isDisabled={ isCodeSending }/>
<Button
variant="link"
display="flex"
alignItems="center"
gap={ 2 }
columnGap={ 2 }
mt={ 3 }
fontWeight="400"
w="fit-content"
isDisabled={ isCodeSending }
onClick={ handleResendCodeClick }
>
<IconSvg name="repeat" boxSize={ 5 }/>
<Box fontSize="sm">Resend code</Box>
</Link>
</Button>
<Button
mt={ 6 }
type="submit"
isLoading={ formApi.formState.isSubmitting }
isDisabled={ formApi.formState.isSubmitting || isCodeSending }
loadingText="Submit"
onClick={ formApi.handleSubmit(onFormSubmit) }
>
Submit
......
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