Commit 64946979 authored by tom's avatar tom

connect email and code screens to API

parent e2abc9e5
......@@ -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',
......
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;
}
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<EmailFormFields>();
const { field, fieldState, formState } = useController<EmailFormFields, 'email'>({
control,
name: 'email',
rules: { required: true, pattern: EMAIL_REGEXP },
});
const isDisabled = formState.isSubmitting;
return (
<FormControl className={ className } variant="floating" isDisabled={ isDisabled } isRequired size="md">
<Input
{ ...field }
required
isInvalid={ Boolean(fieldState.error) }
isDisabled={ isDisabled }
autoComplete="off"
/>
<InputPlaceholder text="Email" error={ fieldState.error }/>
</FormControl>
);
};
export default React.memo(chakra(AuthModalFieldEmail));
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<OtpCodeFormFields>();
const { field, fieldState, formState } = useController<OtpCodeFormFields, 'code'>({
control,
name: 'code',
rules: { required: true, minLength: 6, maxLength: 6 },
});
const isDisabled = formState.isSubmitting;
return (
<FormControl variant="floating" isDisabled={ isDisabled } isRequired size="md">
<Input
{ ...field }
required
isInvalid={ Boolean(fieldState.error) }
isDisabled={ isDisabled }
type="number"
minLength={ 6 }
maxLength={ 6 }
autoComplete="one-time-code"
/>
<InputPlaceholder text="Confirmation code" error={ fieldState.error }/>
</FormControl>
);
};
export default React.memo(AuthModalFieldOtpCode);
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<EmailFormFields>({
mode: 'onBlur',
defaultValues: {
email: '',
},
});
const onFormSubmit: SubmitHandler<EmailFormFields> = 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 (
<Box>
<Text>Account email, used for transaction notifications from your watchlist.</Text>
<Input placeholder="Email" mt={ 6 }/>
<Button mt={ 6 } onClick={ handleSubmitClick }>Send a code</Button>
</Box>
<FormProvider { ...formApi }>
<chakra.form
noValidate
onSubmit={ formApi.handleSubmit(onFormSubmit) }
>
<Text>Account email, used for transaction notifications from your watchlist.</Text>
<AuthModalFieldEmail mt={ 6 } mb={ 3 }/>
<FormFieldReCaptcha/>
<Button
mt={ 6 }
type="submit"
isDisabled={ formApi.formState.isSubmitting || !formApi.formState.isValid }
isLoading={ formApi.formState.isSubmitting }
loadingText="Send a code"
>
Send a code
</Button>
</chakra.form>
</FormProvider>
);
};
......
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<OtpCodeFormFields>({
mode: 'onBlur',
defaultValues: {
code: '',
},
});
const onFormSubmit: SubmitHandler<OtpCodeFormFields> = 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 (
<Box>
<Text>
Please check{ ' ' }
<chakra.span fontWeight="700">{ email }</chakra.span>{ ' ' }
and enter your code below.
</Text>
<Input placeholder="Confirmation code" mt={ 6 }/>
<Link display="flex" alignItems="center" gap={ 2 } mt={ 3 } w="fit-content">
<IconSvg name="repeat" boxSize={ 5 }/>
<Box fontSize="sm">Resend code</Box>
</Link>
<Button mt={ 6 } onClick={ handleSubmitClick }>Submit</Button>
</Box>
<FormProvider { ...formApi }>
<chakra.form
noValidate
onSubmit={ formApi.handleSubmit(onFormSubmit) }
>
<Text mb={ 6 }>
Please check{ ' ' }
<chakra.span fontWeight="700">{ email }</chakra.span>{ ' ' }
and enter your code below.
</Text>
<AuthModalFieldOtpCode/>
<Link
display="flex"
alignItems="center"
gap={ 2 }
mt={ 3 }
w="fit-content"
onClick={ handleResendCodeClick }
>
<IconSvg name="repeat" boxSize={ 5 }/>
<Box fontSize="sm">Resend code</Box>
</Link>
<Button
mt={ 6 }
type="submit"
isLoading={ formApi.formState.isSubmitting }
onClick={ formApi.handleSubmit(onFormSubmit) }
>
Submit
</Button>
</chakra.form>
</FormProvider>
);
};
......
......@@ -8,3 +8,12 @@ export type Screen = {
} | {
type: 'success_created_email';
}
export interface EmailFormFields {
email: string;
reCaptcha: string;
}
export interface OtpCodeFormFields {
code: string;
}
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