@rango-dev/widget-embedded 0.30.1-next.14 → 0.30.1-next.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rango-dev/widget-embedded",
3
- "version": "0.30.1-next.14",
3
+ "version": "0.30.1-next.16",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "source": "./src/index.ts",
@@ -26,11 +26,11 @@
26
26
  "@lingui/react": "4.2.1",
27
27
  "@rango-dev/logging-core": "^0.5.1-next.1",
28
28
  "@rango-dev/provider-all": "^0.35.2-next.4",
29
- "@rango-dev/queue-manager-core": "^0.26.1-next.0",
30
- "@rango-dev/queue-manager-rango-preset": "^0.35.2-next.2",
31
- "@rango-dev/queue-manager-react": "^0.26.1-next.0",
29
+ "@rango-dev/queue-manager-core": "^0.26.1-next.1",
30
+ "@rango-dev/queue-manager-rango-preset": "^0.35.2-next.3",
31
+ "@rango-dev/queue-manager-react": "^0.26.1-next.1",
32
32
  "@rango-dev/signer-solana": "^0.30.1-next.1",
33
- "@rango-dev/ui": "^0.36.1-next.5",
33
+ "@rango-dev/ui": "^0.36.1-next.7",
34
34
  "@rango-dev/wallets-react": "^0.21.2-next.3",
35
35
  "@rango-dev/wallets-shared": "^0.35.2-next.1",
36
36
  "bignumber.js": "^9.1.1",
@@ -53,4 +53,4 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  }
56
- }
56
+ }
@@ -0,0 +1,95 @@
1
+ import { css, darkTheme, IconButton, styled } from '@rango-dev/ui';
2
+
3
+ export const IconContainer = styled('div', {
4
+ position: 'relative',
5
+ '&::before': {
6
+ position: 'absolute',
7
+ right: '1px',
8
+ top: '-1px',
9
+ width: '$8',
10
+ height: '$8',
11
+ borderRadius: '100%',
12
+ backgroundColor: '$neutral300',
13
+ [`.${darkTheme} &`]: {
14
+ backgroundColor: '$neutral400',
15
+ },
16
+ padding: '$2',
17
+ },
18
+ variants: {
19
+ isSelect: {
20
+ true: {
21
+ '&::before': {
22
+ content: '',
23
+ },
24
+ },
25
+ },
26
+ },
27
+ });
28
+
29
+ export const FilterButton = styled(IconButton, {
30
+ width: '$36',
31
+ height: '$36',
32
+ position: 'relative',
33
+ padding: '0',
34
+ overflow: 'unset',
35
+ backgroundColor: '$neutral300',
36
+ [`.${darkTheme} &`]: {
37
+ backgroundColor: '$neutral400',
38
+ },
39
+ '&:hover': {
40
+ backgroundColor: '$secondary100',
41
+ [`.${darkTheme} &`]: {
42
+ backgroundColor: '$neutral',
43
+ },
44
+ [`& ${IconContainer}::before`]: {
45
+ backgroundColor: '$secondary100',
46
+ [`.${darkTheme} &`]: {
47
+ backgroundColor: '$neutral',
48
+ },
49
+ },
50
+ },
51
+ variants: {
52
+ isSelect: {
53
+ true: {
54
+ border: '1px solid $secondary',
55
+ },
56
+ },
57
+ },
58
+ });
59
+
60
+ export const Badge = styled('div', {
61
+ position: 'absolute',
62
+ width: '$6',
63
+ height: '$6',
64
+ display: 'flex',
65
+ justifyContent: 'center',
66
+ alignItems: 'center',
67
+ borderRadius: '3px',
68
+ top: '$0',
69
+ right: '1px',
70
+ backgroundColor: '$secondary500',
71
+ });
72
+
73
+ export const FilterContainer = styled('div', {
74
+ padding: '$15',
75
+ borderRadius: '$sm',
76
+ width: '248px',
77
+ backgroundColor: '$background',
78
+ zIndex: 10,
79
+ });
80
+
81
+ export const centeredFlexContainer = css({
82
+ display: 'flex',
83
+ justifyContent: 'space-between',
84
+ alignItems: 'center',
85
+ });
86
+
87
+ export const FilterList = styled('ul', {
88
+ margin: 0,
89
+ listStyle: 'none',
90
+ height: '100%',
91
+ padding: 0,
92
+ '.item-start-container': {
93
+ paddingRight: '0 !important',
94
+ },
95
+ });
@@ -0,0 +1,42 @@
1
+ import type { FilterSelectorPropTypes } from './FilterSelector.type';
2
+
3
+ import { FilterIcon, Popover } from '@rango-dev/ui';
4
+ import React from 'react';
5
+
6
+ import { getContainer } from '../../utils/common';
7
+
8
+ import { Badge, FilterButton, IconContainer } from './FilterSelector.style';
9
+ import { FilterSelectorContent } from './FilterSelectorContent';
10
+
11
+ export function FilterSelector(props: FilterSelectorPropTypes) {
12
+ const { onClickItem, onOpenChange, filterBy, list, open } = props;
13
+ return (
14
+ <div>
15
+ <Popover
16
+ open={open}
17
+ align="end"
18
+ onOpenChange={onOpenChange}
19
+ container={getContainer()}
20
+ content={
21
+ <FilterSelectorContent
22
+ list={list}
23
+ filterBy={filterBy}
24
+ onClickItem={(id) => {
25
+ onClickItem(id);
26
+ onOpenChange(false);
27
+ }}
28
+ />
29
+ }>
30
+ <FilterButton
31
+ variant="default"
32
+ isSelect={!!filterBy}
33
+ onClick={() => onOpenChange(!props.open)}>
34
+ <IconContainer isSelect={!!filterBy}>
35
+ <FilterIcon size={16} color="black" />
36
+ {!!filterBy && <Badge />}
37
+ </IconContainer>
38
+ </FilterButton>
39
+ </Popover>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,15 @@
1
+ export type PropTypes = {
2
+ filterBy: string;
3
+ onClickItem: (id: string) => void;
4
+ list: Array<FilterItem>;
5
+ };
6
+
7
+ export type FilterItem = {
8
+ id: string;
9
+ title: string;
10
+ };
11
+
12
+ export type FilterSelectorPropTypes = PropTypes & {
13
+ open: boolean;
14
+ onOpenChange: (open: boolean) => void;
15
+ };
@@ -0,0 +1,60 @@
1
+ import type { PropTypes } from './FilterSelector.type';
2
+
3
+ import { i18n } from '@lingui/core';
4
+ import {
5
+ Button,
6
+ Divider,
7
+ ListItemButton,
8
+ Radio,
9
+ RadioRoot,
10
+ Typography,
11
+ } from '@rango-dev/ui';
12
+ import React from 'react';
13
+
14
+ import {
15
+ centeredFlexContainer,
16
+ FilterContainer,
17
+ FilterList,
18
+ } from './FilterSelector.style';
19
+
20
+ export function FilterSelectorContent(props: PropTypes) {
21
+ const { filterBy: value, onClickItem } = props;
22
+ return (
23
+ <FilterContainer>
24
+ <div className={centeredFlexContainer()}>
25
+ <Typography size="small" variant="body">
26
+ {i18n.t('Status')}
27
+ </Typography>
28
+ <Button variant="ghost" size="xxsmall" onClick={() => onClickItem('')}>
29
+ {i18n.t('Reset')}
30
+ </Button>
31
+ </div>
32
+ <Divider size={10} />
33
+ <RadioRoot value={value}>
34
+ <FilterList>
35
+ {props.list.map((item, index) => {
36
+ return (
37
+ <ListItemButton
38
+ key={item.id}
39
+ style={{ height: '40px', width: '100%' }}
40
+ selected={false}
41
+ hasDivider={props.list.length - 1 != index}
42
+ id={item.id}
43
+ title={
44
+ <>
45
+ <Divider direction="horizontal" size={4} />
46
+ <Typography size="medium" variant="body">
47
+ {item.title}
48
+ </Typography>
49
+ </>
50
+ }
51
+ start={<Radio value={item.id} />}
52
+ onClick={onClickItem}
53
+ />
54
+ );
55
+ })}
56
+ </FilterList>
57
+ </RadioRoot>
58
+ </FilterContainer>
59
+ );
60
+ }
@@ -0,0 +1 @@
1
+ export { FilterSelector } from './FilterSelector';
@@ -13,6 +13,7 @@ export function SearchInput(props: PropTypes) {
13
13
  size,
14
14
  onChange,
15
15
  value,
16
+ style,
16
17
  setValue,
17
18
  ...inputAttributes
18
19
  } = props;
@@ -41,6 +42,7 @@ export function SearchInput(props: PropTypes) {
41
42
  padding: 10,
42
43
  borderRadius: 25,
43
44
  alignItems: 'center',
45
+ ...style,
44
46
  }}
45
47
  size={size}
46
48
  value={value}
@@ -1,15 +1,30 @@
1
- import type { PendingSwap, PendingSwapStep } from 'rango-types';
2
-
3
1
  import { i18n } from '@lingui/core';
4
2
  import { useManager } from '@rango-dev/queue-manager-react';
5
- import { Divider, NotFound, styled } from '@rango-dev/ui';
6
- import React, { useState } from 'react';
3
+ import {
4
+ Button,
5
+ darkTheme,
6
+ Divider,
7
+ MessageBox,
8
+ NotFound,
9
+ styled,
10
+ Typography,
11
+ } from '@rango-dev/ui';
12
+ import {
13
+ type PendingSwap,
14
+ type PendingSwapStep,
15
+ TransactionStatus,
16
+ } from 'rango-types';
17
+ import React, { useMemo, useState } from 'react';
7
18
  import { useNavigate } from 'react-router-dom';
8
19
 
20
+ import { WatermarkedModal } from '../components/common/WatermarkedModal';
21
+ import { FilterSelector } from '../components/FilterSelector';
22
+ import { SuffixContainer } from '../components/HeaderButtons/HeaderButtons.styles';
9
23
  import { HistoryGroupedList } from '../components/HistoryGroupedList';
10
24
  import { NotFoundContainer } from '../components/HistoryGroupedList/HistoryGroupedList.styles';
11
25
  import { Layout, PageContainer } from '../components/Layout';
12
26
  import { SearchInput } from '../components/SearchInput';
27
+ import { getContainer } from '../utils/common';
13
28
  import { groupSwapsByDate } from '../utils/date';
14
29
  import { containsText } from '../utils/numbers';
15
30
  import { getPendingSwaps } from '../utils/queue';
@@ -23,6 +38,33 @@ const HistoryGroupedListContainer = styled('div', {
23
38
  height: '100%',
24
39
  });
25
40
 
41
+ const SearchAndFilterBar = styled('div', {
42
+ display: 'flex',
43
+ justifyContent: 'center',
44
+ alignItems: 'center',
45
+ });
46
+
47
+ const Description = styled('div', {
48
+ '._typography': {
49
+ color: '$neutral700',
50
+ [`.${darkTheme}&`]: {
51
+ color: '$neutral900',
52
+ },
53
+ },
54
+ });
55
+
56
+ const transactionStatusFilters = [
57
+ {
58
+ id: TransactionStatus.SUCCESS,
59
+ title: i18n.t('Complete'),
60
+ },
61
+ {
62
+ id: TransactionStatus.RUNNING,
63
+ title: i18n.t('Running'),
64
+ },
65
+ { id: TransactionStatus.FAILED, title: i18n.t('Failed') },
66
+ ];
67
+
26
68
  const isStepContainsText = (steps: PendingSwapStep[], value: string) => {
27
69
  if (!steps?.length) {
28
70
  return false;
@@ -41,50 +83,116 @@ export function HistoryPage() {
41
83
  const { manager, state } = useManager();
42
84
  const list: PendingSwap[] = getPendingSwaps(manager).map(({ swap }) => swap);
43
85
  const [searchedFor, setSearchedFor] = useState<string>('');
86
+ const [openFilterSelector, setOpenFilterSelector] = useState(false);
44
87
  const loading = !state.loadedFromPersistor;
45
-
88
+ const [filterBy, setFilterBy] = useState('');
89
+ const [openClearModal, setOpenClearModal] = useState(false);
46
90
  const searchHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
47
91
  const value = event.target.value;
48
92
  setSearchedFor(value);
49
93
  };
50
94
 
51
- let filteredList = list;
52
- if (searchedFor) {
53
- filteredList = list.filter(
54
- (swap) =>
55
- containsText(swap.inputAmount, searchedFor) ||
56
- containsText(swap.status, searchedFor) ||
57
- isStepContainsText(swap.steps, searchedFor) ||
58
- containsText(swap.requestId, searchedFor)
59
- );
60
- }
95
+ const filteredList = useMemo(() => {
96
+ if (!searchedFor && !filterBy) {
97
+ return list;
98
+ }
99
+
100
+ return list.filter((swap) => {
101
+ const { inputAmount, status, steps, requestId } = swap;
102
+
103
+ const matchesSearch =
104
+ !searchedFor ||
105
+ containsText(inputAmount, searchedFor) ||
106
+ containsText(status, searchedFor) ||
107
+ isStepContainsText(steps, searchedFor) ||
108
+ containsText(requestId, searchedFor);
109
+
110
+ const matchesFilter = !filterBy || filterBy === status;
111
+
112
+ return matchesSearch && matchesFilter;
113
+ });
114
+ }, [list, searchedFor, filterBy]);
61
115
 
62
116
  const isEmpty = !filteredList?.length && !loading;
63
117
 
118
+ const onCloseModal = () => setOpenClearModal(false);
119
+ const onClear = async () => {
120
+ try {
121
+ await manager?.clearQueue();
122
+
123
+ setOpenClearModal(false);
124
+ } catch (e) {
125
+ console.log(e);
126
+ }
127
+ };
128
+ const isClearButtonDisabled = useMemo(() => {
129
+ return !list.some(
130
+ (swap) =>
131
+ swap.status === TransactionStatus.SUCCESS ||
132
+ swap.status === TransactionStatus.FAILED
133
+ );
134
+ }, [list]);
135
+
64
136
  return (
65
137
  <Layout
66
138
  header={{
67
139
  title: i18n.t('History'),
140
+ suffix: (
141
+ <SuffixContainer>
142
+ <Button
143
+ disabled={isClearButtonDisabled}
144
+ variant="ghost"
145
+ size="xsmall"
146
+ onClick={() => setOpenClearModal(true)}>
147
+ <Typography size="medium" variant="label" color="error">
148
+ {i18n.t('Clear all Txs')}
149
+ </Typography>
150
+ </Button>
151
+ </SuffixContainer>
152
+ ),
68
153
  }}>
69
154
  <PageContainer>
70
- <SearchInput
71
- setValue={setSearchedFor}
72
- fullWidth
73
- variant="contained"
74
- placeholder={i18n.t('Search Transaction')}
75
- autoFocus
76
- onChange={searchHandler}
77
- value={searchedFor}
78
- />
155
+ <SearchAndFilterBar>
156
+ <SearchInput
157
+ setValue={setSearchedFor}
158
+ fullWidth
159
+ variant="contained"
160
+ placeholder={i18n.t('Search Transaction')}
161
+ autoFocus
162
+ onChange={searchHandler}
163
+ style={{ height: 36 }}
164
+ value={searchedFor}
165
+ />
166
+ <Divider size={10} direction="horizontal" />
167
+
168
+ <FilterSelector
169
+ filterBy={filterBy}
170
+ open={openFilterSelector}
171
+ onOpenChange={(open) => setOpenFilterSelector(open)}
172
+ onClickItem={(id) => setFilterBy(id)}
173
+ list={transactionStatusFilters}
174
+ />
175
+ </SearchAndFilterBar>
176
+
79
177
  <Divider size="16" />
80
178
  <HistoryGroupedListContainer>
81
179
  {isEmpty && (
82
180
  <NotFoundContainer>
83
181
  <Divider size={32} />
84
182
  <NotFound
85
- title={i18n.t('No results found')}
183
+ title={
184
+ searchedFor
185
+ ? i18n.t('No results found')
186
+ : i18n.t('No transactions')
187
+ }
188
+ titleColor={!searchedFor ? '$info' : undefined}
189
+ hasIcon={!!searchedFor}
86
190
  description={
87
- searchedFor ? i18n.t('Try using different keywords') : ''
191
+ searchedFor
192
+ ? i18n.t('Try using different keywords')
193
+ : i18n.t(
194
+ 'Your transaction history is stored locally and will appear here after you start a swap'
195
+ )
88
196
  }
89
197
  />
90
198
  </NotFoundContainer>
@@ -99,6 +207,52 @@ export function HistoryPage() {
99
207
  )}
100
208
  </HistoryGroupedListContainer>
101
209
  </PageContainer>
210
+ <WatermarkedModal
211
+ open={openClearModal}
212
+ onClose={onCloseModal}
213
+ container={getContainer()}>
214
+ <Divider size={20} />
215
+ <MessageBox
216
+ type="warning"
217
+ title={i18n.t('Clear History')}
218
+ description={
219
+ <Description>
220
+ <Typography variant="body" size="medium">
221
+ {i18n.t(
222
+ 'This would clear all succeeded/failed transactions from widget. Do you wish to proceed?'
223
+ )}
224
+ </Typography>
225
+ <Divider size={'24'} />
226
+
227
+ <Typography variant="body" size="small">
228
+ {i18n.t(
229
+ 'Hint: This does not clear your transaction history on blockchain. Only hides them in this page.'
230
+ )}
231
+ </Typography>
232
+ </Description>
233
+ }
234
+ />
235
+ <Divider size={40} />
236
+
237
+ <Divider size={10} />
238
+ <Button
239
+ variant="contained"
240
+ type="primary"
241
+ size="large"
242
+ onClick={onClear}>
243
+ {i18n.t('Yes, Clear history')}
244
+ </Button>
245
+ <Divider size={10} />
246
+ <Button
247
+ variant="outlined"
248
+ type="primary"
249
+ size="large"
250
+ onClick={onCloseModal}>
251
+ <Typography variant="title" size="medium" color="primary">
252
+ {i18n.t('No, Cancel')}
253
+ </Typography>
254
+ </Button>
255
+ </WatermarkedModal>
102
256
  </Layout>
103
257
  );
104
258
  }
@@ -224,7 +224,7 @@ export const useQuoteStore = createSelectors(
224
224
  set((state) => ({
225
225
  inputAmount: amount,
226
226
  ...(!amount && {
227
- outputAmount: new BigNumber(0),
227
+ outputAmount: null,
228
228
  outputUsdValue: new BigNumber(0),
229
229
  selectedQuote: null,
230
230
  }),