Skip to content

Commit 4f6d38c

Browse files
authored
[FEQ] P2P Nada/feq 1390/counterparties (deriv-com#13120)
* fix: implemented counterparties * feat: added table, block user modal * fix: remove unused fields * fix: added import for memo * fix: conflicts fix * fix: test id in textfield * feat: added filter modal in responsive view * fix: change import * fix: mobile view full page, reverted changes in home page * fix: style fixes * fix: scroll issue in responsive fix * fix: replaced avatar with useravatar in advertisername * fix: sonar issues fixed * fix: pr review comments, moved table to separate component * fix: move constant to separate file
1 parent 0221e8f commit 4f6d38c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1679
-41
lines changed

packages/api/src/hooks/p2p/entity/advertiser/p2p-advertiser/useAdvertiserList.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ const useAdvertiserList = (
99
payload?: NonNullable<Parameters<typeof useInfiniteQuery<'p2p_advertiser_list'>>[1]>['payload']
1010
) => {
1111
const { isSuccess } = useAuthorize();
12+
if (!payload?.is_blocked) {
13+
delete payload?.is_blocked;
14+
}
15+
if (!payload?.advertiser_name) {
16+
delete payload?.advertiser_name;
17+
}
1218
const { data, fetchNextPage, ...rest } = useInfiniteQuery('p2p_advertiser_list', {
1319
payload: { ...payload, offset: payload?.offset, limit: payload?.limit },
1420
options: {
1521
getNextPageParam: (lastPage, pages) => {
16-
if (!lastPage?.p2p_advertiser_list?.list) return;
22+
if (!lastPage?.p2p_advertiser_list?.list?.length) return;
1723

1824
return pages.length;
1925
},
2026
enabled: isSuccess,
27+
refetchOnWindowFocus: false,
2128
},
2229
});
2330

packages/api/src/hooks/p2p/entity/counterparty/p2p-advertiser-relations/useAdvertiserRelations.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ const useAdvertiserRelations = () => {
99
const invalidate = useInvalidateQuery();
1010
const { data, ...rest } = useQuery('p2p_advertiser_relations', { options: { enabled: isSuccess } });
1111
const { mutate, ...mutate_rest } = useMutation('p2p_advertiser_relations', {
12-
onSuccess: () => invalidate('p2p_advertiser_relations'),
12+
onSuccess: () => {
13+
invalidate('p2p_advertiser_relations');
14+
invalidate('p2p_advertiser_list');
15+
},
1316
});
1417

1518
const advertiser_relations = data?.p2p_advertiser_relations;

packages/p2p-v2/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"@deriv/integration": "^1.0.0",
1818
"@deriv/react-joyride": "^2.6.2",
1919
"@sendbird/chat": "^4.9.7",
20+
"@tanstack/react-table": "^8.10.3",
2021
"clsx": "^2.0.0",
22+
"downshift": "^8.2.2",
2123
"i18next": "^22.4.6",
2224
"moment": "^2.29.2",
2325
"react": "^17.0.2",

packages/p2p-v2/src/components/AdvertiserName/AdvertiserName.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Avatar } from '../Avatar';
2+
import { UserAvatar } from '../UserAvatar';
33
import { useAdvertiserStats, useDevice } from '../../hooks';
44
import AdvertiserNameStats from './AdvertiserNameStats';
55
import AdvertiserNameBadges from './AdvertiserNameBadges';
@@ -15,7 +15,7 @@ const AdvertiserName = () => {
1515

1616
return (
1717
<div className='p2p-v2-advertiser-name'>
18-
<Avatar name={advertiserStats.name || ''} />
18+
<UserAvatar nickname={advertiserStats.name!} size={isDesktop ? 64 : 42} textSize='lg' />
1919
<div className='p2p-v2-advertiser-name__details'>
2020
<Text size='md' weight='bold'>
2121
{advertiserStats.name}{' '}

packages/p2p-v2/src/components/Avatar/Avatar.scss

-19
This file was deleted.

packages/p2p-v2/src/components/Avatar/Avatar.tsx

-14
This file was deleted.

packages/p2p-v2/src/components/Avatar/index.ts

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
.p2p-v2-dropdown {
2+
width: 100%;
3+
position: relative;
4+
cursor: pointer;
5+
6+
&--disabled {
7+
pointer-events: none;
8+
9+
& label {
10+
color: var(--system-light-5-active-background, #999);
11+
}
12+
}
13+
14+
&__button {
15+
all: unset;
16+
right: 1.6rem;
17+
transform: rotate(0);
18+
transform-origin: 50% 45%;
19+
transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
20+
21+
&--active {
22+
transform: rotate(180deg);
23+
}
24+
}
25+
26+
&__content {
27+
width: 100%;
28+
background: var(--system-light-8-primary-background, #fff);
29+
display: flex;
30+
align-items: center;
31+
32+
.p2p-v2-textfield__field {
33+
cursor: pointer;
34+
}
35+
}
36+
37+
&__field {
38+
position: absolute;
39+
inset: 0;
40+
min-width: 0; /* this is required to reset input's default width */
41+
padding-left: 2rem;
42+
display: flex;
43+
flex-grow: 1;
44+
font-family: inherit;
45+
outline: 0;
46+
font-size: 1.4rem;
47+
background-color: transparent;
48+
color: var(--system-light-2-general-text, #333);
49+
transition: border-color 0.2s;
50+
cursor: unset;
51+
user-select: none;
52+
&::selection {
53+
background-color: transparent;
54+
}
55+
56+
&::placeholder {
57+
color: transparent;
58+
}
59+
}
60+
61+
&__field:placeholder-shown ~ &__label {
62+
font-size: 1.4rem;
63+
cursor: text;
64+
top: 30%;
65+
padding: 0;
66+
}
67+
68+
&__field:placeholder-shown ~ &__label--with-icon {
69+
left: 4.4rem;
70+
}
71+
72+
label,
73+
&__field:focus ~ &__label {
74+
position: absolute;
75+
display: block;
76+
transition: 0.2s;
77+
font-size: 1rem;
78+
color: var(--system-light-3-less-prominent-text, #999);
79+
background: var(--system-light-8-primary-background, #fff);
80+
padding-inline: 0.4rem;
81+
left: 1.6rem;
82+
}
83+
84+
&__field:focus ~ &__label {
85+
color: var(--brand-blue, #85acb0);
86+
}
87+
88+
&__items {
89+
position: absolute;
90+
top: 4.8rem;
91+
width: 100%;
92+
display: flex;
93+
flex-direction: column;
94+
align-items: flex-start;
95+
z-index: 2;
96+
border-radius: 0.4rem;
97+
background: var(--system-light-8-primary-background, #fff);
98+
box-shadow: 0 3.2rem 6.4rem 0 rgba(14, 14, 14, 0.14);
99+
overflow-y: auto;
100+
101+
& > :first-child {
102+
border-radius: 0.4rem 0.4rem 0 0;
103+
}
104+
105+
& > :last-child {
106+
border-radius: 0 0 0.4rem 0.4rem;
107+
}
108+
109+
&--sm {
110+
max-height: 22rem;
111+
}
112+
113+
&--md {
114+
max-height: 42rem;
115+
}
116+
117+
&--lg {
118+
max-height: 66rem;
119+
}
120+
}
121+
122+
&__icon {
123+
position: absolute;
124+
left: 1.6rem;
125+
width: 1.6rem;
126+
height: 1.6rem;
127+
}
128+
129+
&__item {
130+
padding: 1rem 1.6rem;
131+
width: 100%;
132+
z-index: 2;
133+
134+
&:hover:not(&--active) {
135+
cursor: pointer;
136+
background: var(--system-light-6-hover-background, #e6e9e9);
137+
}
138+
139+
&--active {
140+
background: var(--system-light-5-active-background, #d6dadb);
141+
}
142+
}
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import React, { useCallback, useEffect, useState } from 'react';
2+
import clsx from 'clsx';
3+
import { useCombobox } from 'downshift';
4+
import { Text } from '@deriv-com/ui/dist/components/Text';
5+
import ArrowDown from '../../public/ic-chevron-down.svg';
6+
import reactNodeToString from '../../utils/react-node-to-string';
7+
import TextField, { TextFieldProps } from '../TextField/TextField';
8+
import './Dropdown.scss';
9+
10+
type TGenericSizes = '2xl' | '2xs' | '3xl' | '3xs' | '4xl' | '5xl' | '6xl' | 'lg' | 'md' | 'sm' | 'xl' | 'xs';
11+
12+
type TProps = {
13+
disabled?: boolean;
14+
errorMessage?: TextFieldProps['errorMessage'];
15+
icon?: React.ReactNode;
16+
isRequired?: boolean;
17+
label?: TextFieldProps['label'];
18+
list: {
19+
text?: React.ReactNode;
20+
value?: string;
21+
}[];
22+
listHeight?: Extract<TGenericSizes, 'lg' | 'md' | 'sm'>;
23+
name: TextFieldProps['name'];
24+
onChange?: (inputValue: string) => void;
25+
onSelect: (value: string) => void;
26+
value?: TextFieldProps['value'];
27+
variant?: 'comboBox' | 'prompt';
28+
};
29+
30+
const Dropdown: React.FC<TProps> = ({
31+
disabled = false,
32+
errorMessage = '',
33+
icon = false,
34+
isRequired = false,
35+
label = '',
36+
list,
37+
listHeight = 'md',
38+
name,
39+
onChange = () => {
40+
// do nothing
41+
},
42+
onSelect,
43+
value,
44+
variant = 'prompt',
45+
}) => {
46+
const [items, setItems] = useState(list);
47+
const [hasSelected, setHasSelected] = useState(false);
48+
const [shouldFilterList, setShouldFilterList] = useState(false);
49+
const clearFilter = useCallback(() => {
50+
setShouldFilterList(false);
51+
setItems(list);
52+
}, [list]);
53+
const { closeMenu, getInputProps, getItemProps, getMenuProps, getToggleButtonProps, isOpen, openMenu } =
54+
useCombobox({
55+
defaultSelectedItem: items.find(item => item.value === value) ?? null,
56+
items,
57+
itemToString(item) {
58+
return item ? reactNodeToString(item.text) : '';
59+
},
60+
onInputValueChange({ inputValue }) {
61+
onChange?.(inputValue ?? '');
62+
if (shouldFilterList) {
63+
setItems(
64+
list.filter(item =>
65+
reactNodeToString(item.text)
66+
.toLowerCase()
67+
.includes(inputValue?.toLowerCase() ?? '')
68+
)
69+
);
70+
}
71+
},
72+
onIsOpenChange({ isOpen }) {
73+
if (!isOpen) {
74+
clearFilter();
75+
}
76+
},
77+
onSelectedItemChange({ selectedItem }) {
78+
onSelect(selectedItem?.value ?? '');
79+
closeMenu();
80+
},
81+
});
82+
83+
const handleInputClick = useCallback(() => {
84+
variant === 'comboBox' && setShouldFilterList(true);
85+
86+
if (isOpen) {
87+
closeMenu();
88+
} else {
89+
openMenu();
90+
}
91+
}, [closeMenu, isOpen, openMenu, variant]);
92+
93+
useEffect(() => {
94+
setItems(list);
95+
}, [list]);
96+
97+
return (
98+
<div
99+
className={clsx('p2p-v2-dropdown', {
100+
'p2p-v2-dropdown--disabled': disabled,
101+
})}
102+
{...getToggleButtonProps()}
103+
>
104+
<div className='p2p-v2-dropdown__content'>
105+
<TextField
106+
disabled={disabled}
107+
errorMessage={errorMessage}
108+
isInvalid={hasSelected && !value && isRequired}
109+
label={label}
110+
name={name}
111+
onClickCapture={handleInputClick}
112+
onFocus={() => setHasSelected(true)}
113+
onKeyUp={() => setShouldFilterList(true)}
114+
placeholder={reactNodeToString(label)}
115+
readOnly={variant !== 'comboBox'}
116+
renderLeftIcon={icon ? () => icon : undefined}
117+
renderRightIcon={() => (
118+
<button
119+
className={clsx('p2p-v2-dropdown__button', {
120+
'p2p-v2-dropdown__button--active': isOpen,
121+
})}
122+
>
123+
<ArrowDown />
124+
</button>
125+
)}
126+
type='text'
127+
value={value}
128+
{...getInputProps()}
129+
/>
130+
</div>
131+
<ul className={`p2p-v2-dropdown__items p2p-v2-dropdown__items--${listHeight}`} {...getMenuProps()}>
132+
{isOpen &&
133+
items.map((item, index) => (
134+
<li
135+
className={clsx('p2p-v2-dropdown__item', {
136+
'p2p-v2-dropdown__item--active': value === item.value,
137+
})}
138+
key={item.value}
139+
onClick={clearFilter}
140+
onKeyDown={clearFilter}
141+
{...getItemProps({ index, item })}
142+
>
143+
<Text size='sm' weight={value === item.value ? 'bold' : 'normal'}>
144+
{item.text}
145+
</Text>
146+
</li>
147+
))}
148+
</ul>
149+
</div>
150+
);
151+
};
152+
153+
export default Dropdown;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as Dropdown } from './Dropdown';

0 commit comments

Comments
 (0)