Skip to content

Commit

Permalink
[FEQ] / Ameerul / FEQ-1719 Implement the rating of order transaction (d…
Browse files Browse the repository at this point in the history
…eriv-com#14166)

* chore: created rating modal

* chore: added test cases, added props and change typing
  • Loading branch information
ameerul-deriv authored Mar 15, 2024
1 parent a7c039c commit 5459c3f
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const AdvertiserNameStats = ({ advertiserStats }: { advertiserStats: TAdvertiser
({ratingAverage})
</Text>
)}
<StarRating isReadonly ratingValue={ratingAverage} />
<StarRating allowHalfIcon isReadonly ratingValue={ratingAverage} />
<Text color='less-prominent' size='sm'>
({ratingCount} ratings)
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
align-items: center;

@include mobile {
grid-template-columns: 2fr 0.7fr;
grid-template-columns: 2fr 0.8fr;
}

&--advertiser {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,16 @@ const AdvertsTableRow = memo((props: TAdvertsTableRowRenderer) => {
<div className='flex items-center'>
{hasRating ? (
<>
<Text className='lg:mr-0 mr-[-1.2rem]' color='less-prominent' size='xs'>
<Text className='lg:mr-0' color='less-prominent' size='xs'>
{ratingAverageDecimal}
</Text>
<StarRating
allowHalfIcon
isReadonly
ratingValue={Number(ratingAverageDecimal)}
starsScale={isMobile ? 0.7 : 0.9}
/>
<Text className='lg:ml-1 ml-[-1rem]' color='less-prominent' size='xs'>
<Text className='lg:ml-[-0.5rem] ml-[-2.5rem]' color='less-prominent' size='xs'>
({rating_count})
</Text>
</>
Expand Down Expand Up @@ -160,12 +161,12 @@ const AdvertsTableRow = memo((props: TAdvertsTableRowRenderer) => {
})}
>
{isMobile && isBuySellPage && (
<LabelPairedChevronRightMdRegularIcon className='absolute top-0 right-4' />
<LabelPairedChevronRightMdRegularIcon className='absolute top-0 right-0' />
)}
<Button
className='lg:w-[7.5rem]'
onClick={() => setIsModalOpen(true)}
size={isMobile ? 'md' : 'sm'}
size={isMobile ? 'lg' : 'sm'}
textSize={isMobile ? 'md' : 'xs'}
>
{isBuyAdvert ? 'Buy' : 'Sell'} {account_currency}
Expand Down
38 changes: 38 additions & 0 deletions packages/p2p-v2/src/components/Modals/RatingModal/RatingModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.p2p-v2-rating-modal {
height: auto;
width: 44rem;
border-radius: 8px;

@include mobile {
max-width: calc(100vw - 3.2rem);
}

&__button {
gap: 0.2rem;
padding-left: 0.4rem;

&--disabled {
// stylelint-disable-next-line declaration-no-important
border-color: #d6dadb !important;

& .deriv-text > span {
// stylelint-disable-next-line declaration-no-important
color: #999 !important;
}
}
}

&__stars {
@include mobile {
width: 75%;
display: flex;
justify-content: center;

& .p2p-v2-star-rating {
& svg {
margin-right: 0.7rem;
}
}
}
}
}
123 changes: 123 additions & 0 deletions packages/p2p-v2/src/components/Modals/RatingModal/RatingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useEffect, useState } from 'react';
import clsx from 'clsx';
import { StarRating } from '@/components';
import { StandaloneThumbsDownRegularIcon, StandaloneThumbsUpRegularIcon } from '@deriv/quill-icons';
import { Button, Modal, Text, useDevice } from '@deriv-com/ui';
import './RatingModal.scss';

export type TRatingModalProps = {
isBuyOrder: boolean;
isModalOpen: boolean;
isRecommendedPreviously: number | null;
onRequestClose: () => void;
ratingValue: number;
};

const RatingModal = ({
isBuyOrder,
isModalOpen,
isRecommendedPreviously,
onRequestClose,
ratingValue,
}: TRatingModalProps) => {
const [rating, setRating] = useState<number>(ratingValue);
const [isNoSelected, setIsNoSelected] = useState(false);
const [isYesSelected, setIsYesSelected] = useState(false);

const { isMobile } = useDevice();
const buttonTextSize = isMobile ? 'sm' : 'xs';

const handleSelectYes = () => {
if (isNoSelected) {
setIsNoSelected(false);
}
setIsYesSelected(prevState => !prevState);
};

const handleSelectNo = () => {
if (isYesSelected) {
setIsYesSelected(false);
}
setIsNoSelected(prevState => !prevState);
};

useEffect(() => {
if (isRecommendedPreviously !== null) {
if (isRecommendedPreviously) {
setIsYesSelected(true);
} else {
setIsNoSelected(true);
}
}
}, []);

return (
<Modal ariaHideApp={false} className='p2p-v2-rating-modal' isOpen={isModalOpen} onRequestClose={onRequestClose}>
<Modal.Header hideBorder onRequestClose={onRequestClose}>
<Text size='md' weight='bold'>
How would you rate this transaction?
</Text>
</Modal.Header>
<Modal.Body className='px-0 py-4 lg:px-[2.4rem]'>
<div className='p2p-v2-rating-modal__stars' data-testid='dt_p2p_v2_rating_modal_stars'>
<StarRating allowHover onClick={setRating} ratingValue={rating} starsScale={1.6} />
</div>
{rating > 0 && (
<div className='lg:px-0 pt-8 px-[2.4rem]'>
<Text size='sm'>Would you recommend this {`${isBuyOrder ? 'buyer' : 'seller'}`}?</Text>
<div className='mt-6 flex gap-3'>
<Button
className={clsx('p2p-v2-rating-modal__button', {
'p2p-v2-rating-modal__button--disabled': !isYesSelected,
})}
color='black'
icon={
<StandaloneThumbsUpRegularIcon
fill={isYesSelected ? '#000' : '#999'}
iconSize='sm'
/>
}
onClick={handleSelectYes}
size='sm'
variant='outlined'
>
<Text size={buttonTextSize}>Yes</Text>
</Button>
<Button
className={clsx('p2p-v2-rating-modal__button', {
'p2p-v2-rating-modal__button--disabled': !isNoSelected,
})}
color='black'
icon={
<StandaloneThumbsDownRegularIcon
fill={isNoSelected ? '#000' : '#999'}
iconSize='sm'
/>
}
onClick={handleSelectNo}
size='sm'
variant='outlined'
>
<Text size={buttonTextSize}>No</Text>
</Button>
</div>
</div>
)}
</Modal.Body>
<Modal.Footer hideBorder>
<Button
className='border-2'
color={rating ? 'primary' : 'black'}
onClick={onRequestClose}
size='lg'
textSize={isMobile ? 'md' : 'sm'}
variant={rating ? 'contained' : 'outlined'}
>
{rating ? 'Done' : 'Skip'}
</Button>
</Modal.Footer>
</Modal>
);
};

export default RatingModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import RatingModal, { TRatingModalProps } from '../RatingModal';

let mockProps: TRatingModalProps = {
isBuyOrder: true,
isModalOpen: true,
isRecommendedPreviously: null,
onRequestClose: jest.fn(),
ratingValue: 0,
};

const disabledClassName = 'p2p-v2-rating-modal__button--disabled';

jest.mock('@deriv-com/ui', () => ({
...jest.requireActual('@deriv-com/ui'),
useDevice: () => ({ isMobile: false }),
}));

describe('<RatingModal />', () => {
it('should render just the star rating initially', () => {
render(<RatingModal {...mockProps} />);

expect(screen.getByText('How would you rate this transaction?')).toBeInTheDocument();
expect(screen.getByTestId('dt_p2p_v2_rating_modal_stars')).toBeInTheDocument();
expect(screen.queryByText('Would you recommend this buyer?')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Yes' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'No' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Skip' })).toBeInTheDocument();
});

it('should show the recommendation buttons if ratingValue is passed ', () => {
mockProps = { ...mockProps, ratingValue: 4 };
render(<RatingModal {...mockProps} />);

expect(screen.getByText('Would you recommend this buyer?')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Skip' })).not.toBeInTheDocument();
});

it('should enable the Yes button when clicked and disabled the No button if isRecommendedPreviously is 0', () => {
mockProps = { ...mockProps, isRecommendedPreviously: 0 };

render(<RatingModal {...mockProps} />);

const noButton = screen.getByRole('button', { name: 'No' });
expect(noButton).not.toHaveClass(disabledClassName);

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

expect(yesButton).not.toHaveClass(disabledClassName);
expect(noButton).toHaveClass(disabledClassName);
});

it('should enable the No button when clicked and disabled the Yes button if isRecommendedPreviously is 1', () => {
mockProps = { ...mockProps, isRecommendedPreviously: 1 };

render(<RatingModal {...mockProps} />);

const yesButton = screen.getByRole('button', { name: 'Yes' });
expect(yesButton).not.toHaveClass(disabledClassName);

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

expect(noButton).not.toHaveClass(disabledClassName);
expect(yesButton).toHaveClass(disabledClassName);
});
});
1 change: 1 addition & 0 deletions packages/p2p-v2/src/components/Modals/RatingModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as RatingModal } from './RatingModal';
1 change: 1 addition & 0 deletions packages/p2p-v2/src/components/Modals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './NicknameModal';
export * from './OrderDetailsConfirmModal';
export * from './PaymentMethods';
export * from './RadioGroupFilterModal';
export * from './RatingModal';
12 changes: 8 additions & 4 deletions packages/p2p-v2/src/components/StarRating/StarRating.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import { LabelPairedStarLgFillIcon, LabelPairedStarLgRegularIcon } from '@deriv/
import './StarRating.scss';

type TStarRatingProps = {
allowHalfIcon?: boolean;
allowHover?: boolean;
initialValue?: number;
isReadonly?: boolean;
onClick?: () => void;
onClick?: (rate: number) => void;
ratingValue: number;
starsScale?: number;
};

const StarRating = ({
allowHalfIcon = false,
allowHover = false,
initialValue = 0,
isReadonly = false,
onClick,
Expand All @@ -24,8 +28,8 @@ const StarRating = ({

return (
<Rating
allowHalfIcon
allowHover={false}
allowHalfIcon={allowHalfIcon}
allowHover={allowHover}
className='p2p-v2-star-rating'
emptyIcon={<LabelPairedStarLgRegularIcon fill='#FFAD3A' />}
fullIcon={<LabelPairedStarLgFillIcon fill='#FFAD3A' />}
Expand All @@ -35,7 +39,7 @@ const StarRating = ({
ratingValue={fractionalizedValue}
readonly={isReadonly}
size={12}
style={{ transform: `scale(${starsScale})` }}
style={{ transform: `scale(${starsScale})`, transformOrigin: 'left' }}
/>
);
};
Expand Down

0 comments on commit 5459c3f

Please sign in to comment.