Skip to content

Commit

Permalink
Merge branch 'master' of github.com:binary-com/deriv-app into suisin/…
Browse files Browse the repository at this point in the history
…utkarsha/UPM819/phone_verification_flow
  • Loading branch information
suisin-deriv committed Apr 22, 2024
2 parents 39c8894 + a46d04a commit 35d2099
Show file tree
Hide file tree
Showing 102 changed files with 2,082 additions and 493 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/account-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
"@deriv-com/utils": "^0.0.14",
"@deriv/api-types": "^1.0.172",
"@deriv/api-v2": "^1.0.0",
"@deriv/quill-icons": "^1.21.0",
"@deriv/quill-icons": "^1.21.1",
"class-variance-authority": "^0.7.0",
"dayjs": "^1.11.10",
"formik": "^2.1.4",
"i18n-iso-countries": "^6.8.0",
"i18next": "^22.4.6",
"qrcode.react": "^3.1.0",
"react": "^17.0.2",
"react-calendar": "^4.7.0",
"react-dom": "^17.0.2",
Expand Down
60 changes: 60 additions & 0 deletions packages/account-v2/src/components/Clipboard/Clipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'usehooks-ts';
import { LabelPairedCircleCheckCaptionFillIcon, StandaloneCopyRegularIcon } from '@deriv/quill-icons';
import { Tooltip, useDevice } from '@deriv-com/ui';

type TProps = {
infoMessage?: string;
popoverAlignment?: 'bottom' | 'left' | 'right' | 'top';
successMessage?: string;
textCopy: string;
};

/**
* @depricated TODO: remove this when it's available in @deriv-com/ui. This is temporary.
*/
const Clipboard: React.FC<TProps> = ({
infoMessage = 'Copy',
popoverAlignment,
successMessage = 'Copied',
textCopy,
}) => {
const [, copy] = useCopyToClipboard();
const [isCopied, setIsCopied] = useState(false);
const { isMobile } = useDevice();
const timeoutClipboard = useRef<ReturnType<typeof setTimeout>>();

const onClick = () => {
setIsCopied(true);
copy(textCopy);
timeoutClipboard.current = setTimeout(() => {
setIsCopied(false);
}, 2000);
};

useEffect(() => {
return () => clearTimeout(timeoutClipboard.current);
}, []);

return (
<Tooltip
message={isCopied ? successMessage : infoMessage}
position={popoverAlignment}
triggerAction={isMobile ? 'click' : 'hover'}
>
<button className='flex items-center cursor-pointer px-0 mx-0 border-0 w-fit' onClick={onClick}>
{isCopied ? (
<LabelPairedCircleCheckCaptionFillIcon
className='fill-status-light-success'
height={24}
width={24}
/>
) : (
<StandaloneCopyRegularIcon height={24} iconSize='sm' width={24} />
)}
</button>
</Tooltip>
);
};

export default Clipboard;
1 change: 1 addition & 0 deletions packages/account-v2/src/components/Clipboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Clipboard } from './Clipboard';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { ComponentProps } from 'react';
import { Button } from '@deriv-com/ui';
import { FormInputField } from '../FormFields';

type InputWithButtonProps = ComponentProps<typeof FormInputField> & {
buttonText: string;
isDisabled?: boolean;
isLoading?: boolean;
onClick?: () => void;
};

export const InputWithButton = ({
buttonText,
isDisabled,
isLoading,
label,
name,
onClick,
validationSchema,
...rest
}: InputWithButtonProps) => {
return (
<div className='flex w-[400px] sm:max-w-[400px] sm:w-full mt-[27px] mx-auto mb-0'>
<FormInputField
{...rest}
className='rounded-ee-none rounded-se-none border-r-0 h-40'
label={label}
name={name}
validationSchema={validationSchema}
wrapperClassName='w-full'
/>
<Button
className='rounded-es-none rounded-ss-none h-40 min-w-64'
disabled={isDisabled}
isLoading={isLoading}
onClick={onClick}
>
{buttonText}
</Button>
</div>
);
};

InputWithButton.displayName = 'InputWithButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { Formik } from 'formik';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { InputWithButton } from '../InputWithButton';

describe('InputWithButton', () => {
it('should render the InputWithButton component', () => {
render(
<Formik initialValues={{}} onSubmit={jest.fn()}>
<InputWithButton buttonText='Enable' label='Authentication code' name='digitCode' />
</Formik>
);
expect(screen.getByLabelText('Authentication code')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Enable' })).toBeInTheDocument();
});
it('should handle onClick when the button is clicked', () => {
const mockHandleClick = jest.fn();
render(
<Formik initialValues={{}} onSubmit={jest.fn()}>
<InputWithButton
buttonText='Enable'
label='Authentication code'
name='digitCode'
onClick={mockHandleClick}
/>
</Formik>
);
const button = screen.getByRole('button', { name: 'Enable' });
userEvent.click(button);
expect(screen.getByRole('button', { name: 'Enable' })).toBeInTheDocument();
expect(mockHandleClick).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { InputWithButton } from './InputWithButton';
52 changes: 52 additions & 0 deletions packages/account-v2/src/components/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { ComponentProps, HTMLAttributes, PropsWithChildren, ReactElement } from 'react';
import { twMerge } from 'tailwind-merge';
import { Text } from '@deriv-com/ui';

type TTimelineItemProps = HTMLAttributes<HTMLDivElement> & { itemTitle: React.ReactNode };

type TTimelineProps = HTMLAttributes<HTMLOListElement> & {
children: ReactElement<TTimelineItemProps>[];
lineHeight?: ComponentProps<typeof Text>['lineHeight'];
};

const TimelineItem = ({ children }: PropsWithChildren<TTimelineItemProps>) => {
return <div>{children}</div>;
};

const Marker = ({ label }: { label: number }) => {
return (
<div className='border absolute w-24 h-24 pl-px border-solid-red-0 rounded-full bg-solid-red-0 leading-[23.5px] mr-8 text-center -left-12'>
<Text className='relative text-white align-middle text-lg leading-normal' size='md' weight='bold'>
{label}
</Text>
</div>
);
};
/**
* @deprecated TODO: Replace this component with the one from @deriv-com/ui is implemented.
*/
export const Timeline = ({ children, className, lineHeight }: TTimelineProps) => {
if (!Array.isArray(children)) return null;
return (
<ol className={twMerge('ml-12', className)}>
{children.map((child, idx) => {
return (
<li
className='relative mb-0 ms-0 block w-full border-solid-red-0 border-l-1 border-solid last-of-type:border-l-0 pb-16'
key={idx}
>
<Marker label={idx + 1} />
<div className='ml-20 w-full'>
<Text as='h2' className='max-w-[500px]' color='prominent' lineHeight={lineHeight} size='xs'>
{child.props.itemTitle}
</Text>
<div className='my-16 mx-0 text-system-light-prominent-text last-of-type:mb-0'>{child}</div>
</div>
</li>
);
})}
</ol>
);
};

Timeline.Item = TimelineItem;
1 change: 1 addition & 0 deletions packages/account-v2/src/components/Timeline/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Timeline } from './Timeline';
1 change: 1 addition & 0 deletions packages/account-v2/src/constants/errorMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export const API_ERROR_CODES = {
code: null,
message: 'Sorry, an internal error occurred. Hit the above checkbox to try again.',
},
invalidOTP: { code: 'InvalidOTP', message: "That's not the right code. Please try again." },
} as const;
1 change: 1 addition & 0 deletions packages/account-v2/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './constants';
export * from './errorMessages';
export * from './manualFormConstants';
export * from './routes';
export * from './twoFactorAuthenticationConstants';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const TWO_FACTOR_AUTHENTICATION_URLS = {
authy: 'https://authy.com/',
googleAuthenticator: 'https://github.com/google/google-authenticator/wiki#implementations',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useEffect, useRef } from 'react';
import { Form, Formik, FormikProps } from 'formik';
import * as Yup from 'yup';
import { useIsTwoFactorAuthenticationEnabled, useTwoFactorAuthentication } from '@deriv/api-v2';
import { InputWithButton } from '../../components/InputWithButton';
import { API_ERROR_CODES } from '../../constants';
import { getTwoFactorAuthenticationFormValidationSchema } from '../../utils';

type TTwoFactorData = Yup.InferType<ReturnType<typeof getTwoFactorAuthenticationFormValidationSchema>>;

export const TwoFactorAuthenticationForm = () => {
const { data: isTwoFactorAuthenticationEnabled, isLoading: isStatusLoading } =
useIsTwoFactorAuthenticationEnabled();
const { error, isLoading: isMutationLoading, mutate } = useTwoFactorAuthentication();
const validationSchema = getTwoFactorAuthenticationFormValidationSchema();
// TODO: Remember to translate these
const buttonText = !isStatusLoading && isTwoFactorAuthenticationEnabled ? 'Disable' : 'Enable';
const initialValues: TTwoFactorData = {
digitCode: '',
};
const formRef = useRef<FormikProps<TTwoFactorData>>(null);
useEffect(() => {
if (error && error.error.code === API_ERROR_CODES.invalidOTP.code) {
formRef.current?.setFieldError('digitCode', API_ERROR_CODES.invalidOTP.message);
}
formRef.current?.setSubmitting(false);
}, [error]);
const handleSubmit = (values: TTwoFactorData) => {
const totpAction = isTwoFactorAuthenticationEnabled ? 'disable' : 'enable';
mutate({ otp: values.digitCode, totp_action: totpAction });
// TODO: Handle notification
};
return (
<Formik initialValues={initialValues} innerRef={formRef} onSubmit={handleSubmit}>
{({ dirty, handleBlur, handleChange, isSubmitting, isValid }) => (
<Form noValidate>
<InputWithButton
buttonText={buttonText}
isDisabled={isSubmitting || !isValid || !dirty || isMutationLoading}
isLoading={isSubmitting}
label='Authentication code'
maxLength={6}
name='digitCode'
onBlur={handleBlur}
onChange={handleChange}
validationSchema={validationSchema.fields.digitCode}
/>
</Form>
)}
</Formik>
);
};
Loading

0 comments on commit 35d2099

Please sign in to comment.