Skip to content

Commit

Permalink
[FEQ] / Ameerul / FEQ-1713 Create the order details section (deriv-co…
Browse files Browse the repository at this point in the history
…m#14236)

* chore: added OrderDetails, Header, Info components

* chore: added PaymentMethodAccordion component

* chore: updated styles for PaymentMethodAccordion

* chore: added routing and functionality for order details

* chore: added review/rating block

* chore: added responsive view

* chore: fixed footer styles and added key in ActiveOrderInfo

* fix: routing issues from BuySellForm to orderDetails

* chore: added TOrder type for p2p_order_info

* chore: combined chat with order details, added orderDetails provider

* chore: added useState to handle showing chat on responsive

* chore: added test cases for OrderDetails related components

* fix: review order not showing disabled prompt

* chore: updated TODO

* chore: added error handler, updated ui issues

* fix: failing test cases

* chore: added suggestions

* fix: remove useOrderInfo from useSendbird, pass order info using provider

* chore: moved OrderDetailsProvider to providers folder under src, added comments

* chore: remove !important

* chore: remove TOrders

* chore: added comment explainining recommended

* chore: empty commit

* chore: separated component out for recommended status

* chore: updated test case for recommended status

* chore: empty commit

* chore: added comment
  • Loading branch information
ameerul-deriv authored Mar 25, 2024
1 parent 515cc24 commit 0c4900f
Show file tree
Hide file tree
Showing 47 changed files with 1,556 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMemo } from 'react';
import useQuery from '../../../../../useQuery';
import useAuthorize from '../../../../useAuthorize';

// TODO: Convert this to use useSubscribe as it is a subscribable endpoint
/** This custom hook that returns information about the given order ID */
const useOrderInfo = (id: string) => {
const { isSuccess } = useAuthorize();
Expand Down Expand Up @@ -53,13 +54,15 @@ const useOrderInfo = (id: string) => {
is_reviewable: Boolean(is_reviewable),
/** Indicates if the latest order changes have been seen by the current client. */
is_seen: Boolean(is_seen),
review_details: {
...review_details,
/** Indicates if the advertiser is recommended or not. */
is_recommended: Boolean(review_details?.recommended),
/** Indicates that the advertiser has not been recommended yet. */
has_not_been_recommended: review_details?.recommended === null,
},
review_details: review_details
? {
...review_details,
/** Indicates if the advertiser is recommended or not. */
is_recommended: Boolean(review_details?.recommended),
/** Indicates that the advertiser has not been recommended yet. */
has_not_been_recommended: review_details?.recommended === null,
}
: undefined,
/** Indicates that the seller in the process of confirming the order. */
is_verification_pending: Boolean(verification_pending),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import { THooks } from 'types';
import { PaymentMethodWithIcon } from '@/components';
import { formatTime } from '@/utils';
import { p2p } from '@deriv/api-v2';
import { Text, useDevice } from '@deriv-com/ui';
import { PaymentMethodWithIcon } from '../../PaymentMethodWithIcon';
import './BuySellData.scss';

type TBuySellDataProps = {
Expand Down Expand Up @@ -64,6 +63,7 @@ const BuySellData = ({
{paymentMethodNames?.length
? paymentMethodNames.map(method => (
<PaymentMethodWithIcon
className='mb-[0.8rem]'
key={method}
name={method}
type={paymentMethodTypes?.[method] as TType}
Expand Down
15 changes: 12 additions & 3 deletions packages/p2p-v2/src/components/BuySellForm/BuySellForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable camelcase */
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Control, Controller, FieldValues, useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';
import { TAdvertType, THooks } from 'types';
import { BUY_SELL, RATE_TYPE, VALID_SYMBOLS_PATTERN } from '@/constants';
import { BASE_URL, BUY_SELL, RATE_TYPE, VALID_SYMBOLS_PATTERN } from '@/constants';
import {
getPaymentMethodObjects,
getTextFieldError,
Expand Down Expand Up @@ -59,7 +60,7 @@ const BuySellForm = ({
onRequestClose,
paymentMethods,
}: TBuySellFormProps) => {
const { mutate } = p2p.order.useCreate();
const { data: orderCreatedInfo, isSuccess, mutate } = p2p.order.useCreate();
const [selectedPaymentMethods, setSelectedPaymentMethods] = useState<number[]>([]);

const {
Expand Down Expand Up @@ -89,6 +90,7 @@ const BuySellForm = ({
};
});

const history = useHistory();
const { isMobile } = useDevice();
const isBuy = type === BUY_SELL.BUY;

Expand Down Expand Up @@ -144,6 +146,13 @@ const BuySellForm = ({
}
};

useEffect(() => {
if (isSuccess && orderCreatedInfo) {
history.push(`${BASE_URL}/orders?order=${orderCreatedInfo.id}`);
onRequestClose();
}
}, [isSuccess, orderCreatedInfo, history, onRequestClose]);

return (
<form onSubmit={handleSubmit(onSubmit)}>
<BuySellFormDisplayWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ const BuySellFormFooter = ({ isDisabled, onClickCancel, onSubmit }: TBuySellForm
const { isMobile } = useDevice();
return (
<div className='flex justify-end gap-[1rem]'>
<Button onClick={onClickCancel} size='lg' textSize={isMobile ? 'md' : 'sm'} variant='outlined'>
<Button
className='border-2'
color='black'
onClick={onClickCancel}
size='lg'
textSize={isMobile ? 'md' : 'sm'}
variant='outlined'
>
Cancel
</Button>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import React, { ComponentType, SVGAttributes } from 'react';
import clsx from 'clsx';
import { THooks } from 'types';
import { TGenericSizes } from '@/utils';
import { Text } from '@deriv-com/ui';
import IcCashierBankTransfer from '../../public/ic-cashier-bank-transfer.svg';
import IcCashierEwallet from '../../public/ic-cashier-ewallet.svg';
import IcCashierOther from '../../public/ic-cashier-other.svg';

type TPaymentMethodWithIconProps = {
className: string;
name: string;
textSize?: TGenericSizes;
type: THooks.AdvertiserPaymentMethods.Get[number]['type'];
};
const PaymentMethodWithIcon = ({ name, type }: TPaymentMethodWithIconProps) => {
const PaymentMethodWithIcon = ({ className, name, textSize = 'sm', type }: TPaymentMethodWithIconProps) => {
let Icon: ComponentType<SVGAttributes<SVGElement>> = IcCashierOther;
if (type === 'bank') {
Icon = IcCashierBankTransfer;
} else if (type === 'ewallet') {
Icon = IcCashierEwallet;
}
return (
<div className='flex items-center gap-[0.8rem] mb-[0.8rem]'>
<div className={clsx('flex items-center gap-[0.8rem]', className)}>
<Icon data-testid='dt_p2p_v2_payment_method_card_header_icon' height={16} width={16} />
<Text size='sm'>{name}</Text>
<Text size={textSize}>{name}</Text>
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/p2p-v2/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './PaymentMethodForm';
export * from './PaymentMethodLabel';
export * from './PaymentMethodsFormFooter';
export * from './PaymentMethodsHeader';
export * from './PaymentMethodWithIcon';
export * from './PopoverDropdown';
export * from './ProfileContent';
export * from './RadioGroup';
Expand Down
30 changes: 28 additions & 2 deletions packages/p2p-v2/src/hooks/useExtendedOrderDetails.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { THooks, TServerTime } from 'types';
import { BUY_SELL, ORDERS_STATUS } from '@/constants'; // Update your import path
import { convertToMillis, getFormattedDateString, toMoment } from '@/utils';
import {
convertToMillis,
getFormattedDateString,
removeTrailingZeros,
roundOffDecimal,
setDecimalPlaces,
toMoment,
} from '@/utils';
import { FormatUtils } from '@deriv-com/utils';

type TOrder = THooks.Order.GetList[number];
type TOrder = THooks.Order.Get;

type TUserDetails = TOrder['advertiser_details'] | TOrder['client_details'];

type TObject = Record<string, string>;
interface ExtendedOrderDetails extends TOrder {
counterpartyAdStatusString: TObject;
displayPaymentAmount: string;
hasReviewDetails: boolean;
hasTimerExpired: boolean;
isActiveOrder: boolean;
Expand Down Expand Up @@ -36,6 +45,7 @@ interface ExtendedOrderDetails extends TOrder {
orderExpiryMilliseconds: number;
otherUserDetails: TUserDetails;
purchaseTime: string;
rateAmount: string;
remainingSeconds: number;
shouldHighlightAlert: boolean;
shouldHighlightDanger: boolean;
Expand Down Expand Up @@ -79,6 +89,14 @@ const useExtendedOrderDetails = ({
rightSendOrReceive: this.isBuyOrder ? 'Receive' : 'Send',
};
},
get displayPaymentAmount() {
return removeTrailingZeros(
FormatUtils.formatMoney(
Number(this.amount_display) * Number(roundOffDecimal(this.rate, setDecimalPlaces(this.rate, 6))),
{ currency: this.local_currency }
)
);
},
get hasReviewDetails() {
return !!this.review_details;
},
Expand Down Expand Up @@ -182,6 +200,14 @@ const useExtendedOrderDetails = ({
this.isInactiveOrder
);
},
get rateAmount() {
return removeTrailingZeros(
FormatUtils.formatMoney(this.rate, {
currency: this.local_currency,
decimalPlaces: setDecimalPlaces(this.rate, 6),
})
);
},
get remainingSeconds() {
const serverTimeAmount = serverTime?.server_time_moment;
const expiryTimeMoment = toMoment(this.expiry_time);
Expand Down
15 changes: 8 additions & 7 deletions packages/p2p-v2/src/hooks/useSendbird.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { p2p, useChatCreate, useOrderInfo, useSendbirdServiceToken, useServerTime } from '@deriv/api-v2';
import { useOrderDetails } from '@/providers/OrderDetailsProvider';
import { p2p, useChatCreate, useSendbirdServiceToken, useServerTime } from '@deriv/api-v2';
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 @@ -89,7 +90,7 @@ const useSendbird = (orderId: string) => {
const { data: advertiserInfo, isSuccess: isSuccessAdvertiserInfo } = p2p.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 } = useChatCreate();
const { data: orderInfo, isError: isErrorOrderInfo } = useOrderInfo(orderId);
const { isErrorOrderInfo, orderDetails } = useOrderDetails();
const { data: serverTime, isError: isErrorServerTime } = useServerTime();

const getUser = async (userId: string, token: string) => {
Expand Down Expand Up @@ -239,11 +240,11 @@ const useSendbird = (orderId: string) => {
const user = await getUser(advertiserInfo.chat_user_id, token || '');
if (!user) {
setIsChatError(true);
} else if (orderInfo?.chat_channel_url) {
} else if (orderDetails?.chat_channel_url) {
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(orderInfo.chat_channel_url);
const channel = await getChannel(orderDetails.chat_channel_url);
if (!channel) {
setIsChatError(true);
} else {
Expand All @@ -264,7 +265,7 @@ const useSendbird = (orderId: string) => {
isSuccessAdvertiserInfo,
sendbirdServiceToken,
advertiserInfo?.chat_user_id,
orderInfo?.chat_channel_url,
orderDetails?.chat_channel_url,
getMessages,
]);

Expand All @@ -275,14 +276,14 @@ 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 (!orderInfo?.chat_channel_url) {
if (!orderDetails?.chat_channel_url) {
createChat({
order_id: orderId,
});
} else {
initialiseChat();
}
}, [orderId, orderInfo?.chat_channel_url]);
}, [orderId, orderDetails?.chat_channel_url]);

return {
activeChatChannel: chatChannel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.p2p-v2-order-details-card {
display: flex;
flex-direction: column;
background-color: #fff;
border: 8px solid #f2f3f4;
border-radius: 8px;
width: 45.6rem;
height: fit-content;

@include mobile {
width: 100%;
border: none;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { Divider, useDevice } from '@deriv-com/ui';
import { OrderDetailsCardFooter } from './OrderDetailsCardFooter';
import { OrderDetailsCardHeader } from './OrderDetailsCardHeader';
import { OrderDetailsCardInfo } from './OrderDetailsCardInfo';
import { OrderDetailsCardReview } from './OrderDetailsCardReview';
import './OrderDetailsCard.scss';

const OrderDetailsCard = () => {
const { isDesktop } = useDevice();

return (
<div className='p2p-v2-order-details-card'>
<OrderDetailsCardHeader />
<Divider color='#f2f3f4' />
<OrderDetailsCardInfo />
<Divider color='#f2f3f4' />
<OrderDetailsCardReview />
{isDesktop && <OrderDetailsCardFooter />}
</div>
);
};

export default OrderDetailsCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.p2p-v2-order-details-card-footer {
display: flex;
padding: 1.6rem;

@include mobile {
border-top: 2px solid #f2f3f4;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { useOrderDetails } from '@/providers/OrderDetailsProvider';
import { Button, useDevice } from '@deriv-com/ui';
import './OrderDetailsCardFooter.scss';

// TODO: Implement functionality for each button when integrating with the API and disable buttons while chat is loading
const OrderDetailsCardFooter = () => {
const { orderDetails } = useOrderDetails();
const {
shouldShowCancelAndPaidButton,
shouldShowComplainAndReceivedButton,
shouldShowOnlyComplainButton,
shouldShowOnlyReceivedButton,
} = orderDetails;
const { isMobile } = useDevice();
const textSize = isMobile ? 'md' : 'sm';

if (shouldShowCancelAndPaidButton)
return (
<div className='p2p-v2-order-details-card-footer justify-end gap-3'>
<Button className='border-2' color='black' size='lg' textSize={textSize} variant='outlined'>
Cancel order
</Button>
<Button size='lg' textSize={textSize}>
I’ve paid
</Button>
</div>
);

if (shouldShowComplainAndReceivedButton)
return (
<div className='p2p-v2-order-details-card-footer justify-between'>
<Button className='border-2' color='primary-light' size='lg' textSize={textSize} variant='ghost'>
Complain
</Button>
<Button size='lg' textSize={textSize}>
I’ve received payment
</Button>
</div>
);

if (shouldShowOnlyComplainButton)
return (
<div className='p2p-v2-order-details-card-footer justify-end'>
<Button className='border-2' color='primary-light' size='lg' textSize={textSize} variant='ghost'>
Complain
</Button>
</div>
);

if (shouldShowOnlyReceivedButton)
return (
<div className='p2p-v2-order-details-card-footer justify-end'>
<Button size='lg' textSize={textSize}>
I’ve received payment
</Button>
</div>
);

return null;
};

export default OrderDetailsCardFooter;
Loading

0 comments on commit 0c4900f

Please sign in to comment.