Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nada/feq 2188/order details confirm modal #63

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type TBuySellAmountProps = {
maxLimit: string;
minLimit: string;
paymentMethodNames?: string[];
setValue: ReturnType<typeof useForm>['setValue'];
};
const BuySellAmount = ({
accountCurrency,
Expand All @@ -31,6 +32,7 @@ const BuySellAmount = ({
maxLimit,
minLimit,
paymentMethodNames,
setValue,
}: TBuySellAmountProps) => {
const { isMobile } = useDevice();
const labelSize = isMobile ? 'sm' : 'xs';
Expand All @@ -49,7 +51,8 @@ const BuySellAmount = ({
// causing the amount to be 0
useEffect(() => {
setInputValue(minLimit);
}, [minLimit]);
setValue('amount', minLimit);
}, [minLimit, setValue]);

return (
<div className='flex flex-col gap-[2rem] py-[1.6rem]'>
Expand Down
2 changes: 2 additions & 0 deletions src/components/BuySellForm/BuySellForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const BuySellForm = ({ advertId, isModalOpen, onRequestClose }: TBuySellFormProp
formState: { isValid },
getValues,
handleSubmit,
setValue,
} = useForm({
defaultValues: {
amount: min_order_amount_limit ?? 1,
Expand Down Expand Up @@ -254,6 +255,7 @@ const BuySellForm = ({ advertId, isModalOpen, onRequestClose }: TBuySellFormProp
)}
minLimit={min_order_amount_limit_display ?? '0'}
paymentMethodNames={payment_method_names}
setValue={setValue as unknown as (name: string, value: string) => void}
/>
</BuySellFormDisplayWrapper>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ jest.mock('react-hook-form', () => ({
amount: 1,
})),
handleSubmit: mockHandleSubmit,
setValue: jest.fn(),
}),
}));

Expand Down
8 changes: 6 additions & 2 deletions src/components/FileDropzone/FileDropzone.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
@mixin file-dropzone-message {
display: block;
display: flex;
flex-direction: column;
align-items: center;
max-width: 16.8rem;
opacity: 1;
pointer-events: none;
position: absolute;
transform: translate3d(0, 0, 0);
transition: transform 0.25s ease, opacity 0.15s linear;
transition:
transform 0.25s ease,
opacity 0.15s linear;

@include mobile {
max-width: 26rem;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { useState } from 'react';
import { FileRejection } from 'react-dropzone';
import { FileUploaderComponent } from '@/components/FileUploaderComponent';
import { useOrderDetails } from '@/providers/OrderDetailsProvider';
import { getErrorMessage, maxPotFileSize, TFile } from '@/utils';
import { Button, InlineMessage, Modal, Text, useDevice } from '@deriv-com/ui';
import './OrderDetailsConfirmModal.scss';

type TOrderDetailsConfirmModalProps = {
isModalOpen: boolean;
onCancel: () => void;
onConfirm: () => void;
onRequestClose: () => void;
sendFile: (file: File) => void;
};

type TDocumentFile = {
errorMessage: string | null;
files: File[];
};

const OrderDetailsConfirmModal = ({ isModalOpen, onRequestClose }: TOrderDetailsConfirmModalProps) => {
const OrderDetailsConfirmModal = ({
isModalOpen,
onCancel,
onConfirm,
onRequestClose,
sendFile,
}: TOrderDetailsConfirmModalProps) => {
const [documentFile, setDocumentFile] = useState<TDocumentFile>({ errorMessage: null, files: [] });
const { orderDetails } = useOrderDetails();
const { displayPaymentAmount, local_currency: localCurrency, otherUserDetails } = orderDetails ?? {};
const { name } = otherUserDetails ?? {};
const { isMobile } = useDevice();
const buttonTextSize = isMobile ? 'md' : 'sm';

Expand All @@ -37,11 +50,6 @@ const OrderDetailsConfirmModal = ({ isModalOpen, onRequestClose }: TOrderDetails
});
};

// TODO: uncomment this when implementing the OrderDetailsConfirmModal
// const displayPaymentAmount = removeTrailingZeros(
// formatMoney(local_currency, amount_display * Number(roundOffDecimal(rate, setDecimalPlaces(rate, 6))), true)
// );

return (
<Modal
ariaHideApp={false}
Expand All @@ -59,8 +67,8 @@ const OrderDetailsConfirmModal = ({ isModalOpen, onRequestClose }: TOrderDetails
</Modal.Header>
<Modal.Body className='flex flex-col lg:px-[2.4rem] px-[1.6rem]'>
<Text size='sm'>
Please make sure that you’ve paid 9.99 IDR to client CR90000012, and upload the receipt as proof of
your payment
{`Please make sure that you’ve paid ${displayPaymentAmount} ${localCurrency} to client ${name}, and upload the receipt as proof of
your payment`}
</Text>
<Text className='pt-[0.8rem] pb-[2.4rem]' color='less-prominent' size='sm'>
We accept JPG, PDF, or PNG (up to 5MB).
Expand All @@ -72,8 +80,10 @@ const OrderDetailsConfirmModal = ({ isModalOpen, onRequestClose }: TOrderDetails
</InlineMessage>
<FileUploaderComponent
accept={{
'application/*': ['.pdf'],
'image/*': ['.jpg', '.jpeg', './png'],
'application/pdf': ['.pdf'],
'image/jpeg': ['.jpeg'],
'image/jpg': ['.jpg'],
'image/png': ['.png'],
}}
hoverMessage='Upload receipt here'
maxSize={maxPotFileSize}
Expand All @@ -86,12 +96,18 @@ const OrderDetailsConfirmModal = ({ isModalOpen, onRequestClose }: TOrderDetails
/>
</Modal.Body>
<Modal.Footer className='gap-4 border-none lg:p-[2.4rem] p-[1.6rem]'>
<Button className='border-2' color='black' size='lg' variant='outlined'>
<Button className='border-2' color='black' onClick={onCancel} size='lg' variant='outlined'>
<Text lineHeight='6xl' size={buttonTextSize} weight='bold'>
Go Back
</Text>
</Button>
<Button size='lg'>
<Button
onClick={() => {
sendFile(documentFile.files[0]);
onConfirm();
}}
size='lg'
>
<Text lineHeight='6xl' size={buttonTextSize} weight='bold'>
Confirm
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import OrderDetailsConfirmModal from '../OrderDetailsConfirmModal';

Expand All @@ -9,9 +9,22 @@ jest.mock('@deriv-com/ui', () => ({
}),
}));

jest.mock('@/providers/OrderDetailsProvider', () => ({
useOrderDetails: jest.fn().mockReturnValue({
orderDetails: {
displayPaymentAmount: '0.10',
local_currency: 'INR',
otherUserDetails: { name: 'John Doe' },
},
}),
}));

const mockProps = {
isModalOpen: true,
onCancel: jest.fn(),
onConfirm: jest.fn(),
onRequestClose: jest.fn(),
sendFile: jest.fn(),
};

describe('<OrderDetailsConfirmModal />', () => {
Expand All @@ -21,7 +34,7 @@ describe('<OrderDetailsConfirmModal />', () => {
expect(screen.getByText('Payment confirmation')).toBeInTheDocument();
expect(
screen.getByText(
/Please make sure that you’ve paid 9.99 IDR to client CR90000012, and upload the receipt as proof of your payment/
/Please make sure that you’ve paid 0.10 INR to client John Doe, and upload the receipt as proof of your payment/
)
).toBeInTheDocument();
expect(screen.getByText('We accept JPG, PDF, or PNG (up to 5MB).')).toBeInTheDocument();
Expand Down Expand Up @@ -67,10 +80,7 @@ describe('<OrderDetailsConfirmModal />', () => {
value: fileList,
});

// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
fireEvent.change(fileInput);
});
fireEvent.change(fileInput);

await waitFor(() => {
expect(screen.getByText('The file you uploaded is not supported. Upload another.')).toBeInTheDocument();
Expand All @@ -82,22 +92,7 @@ describe('<OrderDetailsConfirmModal />', () => {

const blob = new Blob([new Array(6 * 1024 * 1024).join('a')], { type: 'image/png' });
const file = new File([blob], 'test.png');
const fileInput = screen.getByTestId('dt_file_upload_input');

const fileList = {
0: file,
item: () => file,
length: 1,
};

Object.defineProperty(fileInput, 'files', {
value: fileList,
});

// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
fireEvent.change(fileInput);
});
await userEvent.upload(screen.getByTestId('dt_file_upload_input'), file);

expect(screen.getByText('Cannot upload a file over 5MB')).toBeInTheDocument();
});
Expand All @@ -108,19 +103,10 @@ describe('<OrderDetailsConfirmModal />', () => {
const file = new File(['test'], 'test.png', { type: 'image/png' });
const fileInput = screen.getByTestId('dt_file_upload_input');

const fileList = {
0: file,
item: () => file,
length: 1,
};

Object.defineProperty(fileInput, 'files', {
value: fileList,
});
await userEvent.upload(fileInput, file);

// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
fireEvent.change(fileInput);
await waitFor(() => {
expect(screen.getByText('test.png')).toBeInTheDocument();
});

const closeIcon = screen.getByTestId('dt_remove_file_icon');
Expand All @@ -131,4 +117,22 @@ describe('<OrderDetailsConfirmModal />', () => {
expect(screen.queryByText('test.png')).not.toBeInTheDocument();
});
});

it('should handle confirm button click', async () => {
render(<OrderDetailsConfirmModal {...mockProps} />);

const button = screen.getByRole('button', { name: 'Confirm' });
await userEvent.click(button);

expect(mockProps.onConfirm).toHaveBeenCalled();
});

it('should handle goback click', async () => {
render(<OrderDetailsConfirmModal {...mockProps} />);

const button = screen.getByRole('button', { name: 'Go Back' });
await userEvent.click(button);

expect(mockProps.onCancel).toHaveBeenCalled();
});
});
4 changes: 4 additions & 0 deletions src/constants/api-error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export const ERROR_CODES = {
ADVERTISER_NOT_FOUND: 'AdvertiserNotFound',
ADVERTISER_TEMP_BAN: 'advertiser_temp_ban',
DUPLICATE_ADVERT: 'DuplicateAdvert',
EXCESSIVE_VERIFICATION_FAILURES: 'ExcessiveVerificationFailures',
EXCESSIVE_VERIFICATION_REQUESTS: 'ExcessiveVerificationRequests',
INVALID_VERIFICATION_TOKEN: 'InvalidVerificationToken',
ORDER_EMAIL_VERIFICATION_REQUIRED: 'OrderEmailVerificationRequired',
} as const;
17 changes: 8 additions & 9 deletions src/hooks/custom-hooks/useSendbird.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { TFileType } from 'types';
import { useOrderDetails } from '@/providers/OrderDetailsProvider';
import SendbirdChat, { BaseChannel, User } from '@sendbird/chat';
import { GroupChannel, GroupChannelHandler, GroupChannelModule } from '@sendbird/chat/groupChannel';
import { BaseMessage, MessageType, MessageTypeFilter } from '@sendbird/chat/message';
Expand Down Expand Up @@ -72,7 +71,7 @@ function createChatMessage(sendbirdMessage: BaseMessage): ChatMessage {
};
}

const useSendbird = (orderId: string) => {
const useSendbird = (orderId: string, isErrorOrderInfo: boolean, chatChannelUrl: string) => {
const sendbirdApiRef = useRef<ReturnType<typeof SendbirdChat.init<GroupChannelModule[]>>>();

const [isChatLoading, setIsChatLoading] = useState(false);
Expand All @@ -91,7 +90,6 @@ const useSendbird = (orderId: string) => {
const { data: advertiserInfo } = api.advertiser.useGetInfo();
//TODO: p2p_chat_create endpoint to be removed once chat_channel_url is created from p2p_order_create
const { isError: isErrorChatCreate, mutate: createChat } = api.chat.useCreate();
const { isErrorOrderInfo, orderDetails } = useOrderDetails();
const { data: serverTime, isError: isErrorServerTime } = api.account.useServerTime();

const getUser = async (userId: string, token: string) => {
Expand Down Expand Up @@ -236,11 +234,11 @@ const useSendbird = (orderId: string) => {
const user = await getUser(advertiserInfo.chat_user_id, token || '');
if (!user) {
setIsChatError(true);
} else if (orderDetails?.chat_channel_url) {
} else if (chatChannelUrl) {
setUser(user);
// if there is no chat_channel_url, it needs to be created using useCreateChat hook first
// 2. Retrieve the P2P channel for the specific order
const channel = await getChannel(orderDetails.chat_channel_url);
const channel = await getChannel(chatChannelUrl);
if (!channel) {
setIsChatError(true);
} else {
Expand All @@ -260,7 +258,7 @@ const useSendbird = (orderId: string) => {
isSuccessSendbirdServiceToken,
sendbirdServiceToken,
advertiserInfo?.chat_user_id,
orderDetails?.chat_channel_url,
chatChannelUrl,
getMessages,
]);

Expand All @@ -271,14 +269,15 @@ const useSendbird = (orderId: string) => {

useEffect(() => {
// if the user has not created a chat URL for the order yet, create one using p2p_create_chat endpoint
if (!orderDetails?.chat_channel_url) {
if (orderId && !chatChannelUrl) {
createChat({
order_id: orderId,
});
} else {
} else if (sendbirdServiceToken?.app_id) {
initialiseChat();
}
}, [orderId, orderDetails?.chat_channel_url]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orderId, chatChannelUrl, sendbirdServiceToken?.app_id]);
ameerul-deriv marked this conversation as resolved.
Show resolved Hide resolved

return {
activeChatChannel: chatChannel,
Expand Down
5 changes: 1 addition & 4 deletions src/pages/orders/components/ChatMessages/ChatMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Fragment, SyntheticEvent, useEffect, useRef } from 'react';
import clsx from 'clsx';
import { TTextColors } from 'types';
import { TActiveChannel, TChatMessages, TTextColors } from 'types';
import { CHAT_FILE_TYPE, CHAT_MESSAGE_TYPE } from '@/constants';
import { useSendbird } from '@/hooks/custom-hooks';
import { convertToMB, formatMilliseconds } from '@/utils';
import { Text, useDevice } from '@deriv-com/ui';
import { ReactComponent as PDFIcon } from '../../../../public/ic-pdf.svg';
import { ChatMessageReceipt } from '../ChatMessageReceipt';
import { ChatMessageText } from '../ChatMessageText';
import './ChatMessages.scss';

type TActiveChannel = ReturnType<typeof useSendbird>['activeChatChannel'];
type TChatMessages = NonNullable<ReturnType<typeof useSendbird>['messages']>;
type TChatMessagesProps = {
chatChannel: TActiveChannel;
chatMessages: TChatMessages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OrderDetailsCardInfo } from './OrderDetailsCardInfo';
import { OrderDetailsCardReview } from './OrderDetailsCardReview';
import './OrderDetailsCard.scss';

const OrderDetailsCard = () => {
const OrderDetailsCard = ({ sendFile }: { sendFile: (file: File) => void }) => {
const { isDesktop } = useDevice();

return (
Expand All @@ -16,7 +16,7 @@ const OrderDetailsCard = () => {
<OrderDetailsCardInfo />
<LightDivider />
<OrderDetailsCardReview />
{isDesktop && <OrderDetailsCardFooter />}
{isDesktop && <OrderDetailsCardFooter sendFile={sendFile} />}
</div>
);
};
Expand Down
Loading
Loading