Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
2d99aba9
Commit
2d99aba9
authored
Feb 07, 2025
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
watchlist page refactoring
parent
bb7d3592
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
158 additions
and
209 deletions
+158
-209
eslint.config.mjs
eslint.config.mjs
+1
-1
watchlist.tsx
pages/account/watchlist.tsx
+2
-2
Modal.ts
theme/components/Modal.ts
+1
-1
PinInput.ts
theme/components/PinInput.ts
+1
-1
semanticTokens.ts
theme/foundations/semanticTokens.ts
+1
-1
TokenSelectMenu.tsx
ui/address/tokenSelect/TokenSelectMenu.tsx
+1
-1
AddressVerificationStepAddress.tsx
...ressVerification/steps/AddressVerificationStepAddress.tsx
+1
-1
AddressVerificationStepSignature.tsx
...ssVerification/steps/AddressVerificationStepSignature.tsx
+2
-2
ApiKeyForm.tsx
ui/apiKey/ApiKeyModal/ApiKeyForm.tsx
+2
-2
CustomAbiForm.tsx
ui/customAbi/CustomAbiModal/CustomAbiForm.tsx
+3
-3
Watchlist.tsx
ui/pages/Watchlist.tsx
+38
-41
TableItemActionButtons.tsx
ui/shared/TableItemActionButtons.tsx
+0
-2
FormFieldCheckbox.tsx
ui/shared/forms/fields/FormFieldCheckbox.tsx
+14
-22
AddressForm.tsx
ui/watchlist/AddressModal/AddressForm.tsx
+13
-17
AddressModal.tsx
ui/watchlist/AddressModal/AddressModal.tsx
+5
-5
DeleteAddressModal.tsx
ui/watchlist/DeleteAddressModal.tsx
+5
-5
WatchListAddressItem.tsx
ui/watchlist/WatchlistTable/WatchListAddressItem.tsx
+10
-10
WatchListItem.tsx
ui/watchlist/WatchlistTable/WatchListItem.tsx
+20
-32
WatchListTableItem.tsx
ui/watchlist/WatchlistTable/WatchListTableItem.tsx
+25
-41
WatchlistTable.tsx
ui/watchlist/WatchlistTable/WatchlistTable.tsx
+13
-19
No files found.
eslint.config.mjs
View file @
2d99aba9
...
...
@@ -32,7 +32,7 @@ const RESTRICTED_MODULES = {
{
name
:
'
@chakra-ui/react
'
,
importNames
:
[
'
Menu
'
,
'
useToast
'
,
'
useDisclosure
'
,
'
useClipboard
'
,
'
Tooltip
'
,
'
Skeleton
'
,
'
IconButton
'
,
'
Button
'
,
'
Link
'
,
'
Tag
'
,
'
Menu
'
,
'
useToast
'
,
'
useDisclosure
'
,
'
useClipboard
'
,
'
Tooltip
'
,
'
Skeleton
'
,
'
IconButton
'
,
'
Button
'
,
'
Link
'
,
'
Tag
'
,
'
Switch
'
,
'
Image
'
,
'
Popover
'
,
'
PopoverTrigger
'
,
'
PopoverContent
'
,
'
PopoverBody
'
,
'
PopoverFooter
'
,
'
DrawerRoot
'
,
'
DrawerBody
'
,
'
DrawerContent
'
,
'
DrawerOverlay
'
,
'
DrawerBackdrop
'
,
'
DrawerTrigger
'
,
'
Drawer
'
,
'
Alert
'
,
'
AlertIcon
'
,
'
AlertTitle
'
,
'
AlertDescription
'
,
...
...
pages/account/watchlist.tsx
View file @
2d99aba9
...
...
@@ -4,12 +4,12 @@ import React from 'react';
import
PageNextJs
from
'
nextjs/PageNextJs
'
;
//
const WatchList = dynamic(() => import('ui/pages/Watchlist'), { ssr: false });
const
WatchList
=
dynamic
(()
=>
import
(
'
ui/pages/Watchlist
'
),
{
ssr
:
false
});
const
Page
:
NextPage
=
()
=>
{
return
(
<
PageNextJs
pathname=
"/account/watchlist"
>
{
/* <WatchList/> */
}
<
WatchList
/>
</
PageNextJs
>
);
};
...
...
theme/components/Modal.ts
View file @
2d99aba9
...
...
@@ -14,7 +14,7 @@ const baseStyleDialog = defineStyle(() => {
return
{
padding
:
8
,
borderRadius
:
'
lg
'
,
bg
:
'
dialog
_
bg
'
,
bg
:
'
dialog
.
bg
'
,
margin
:
'
auto
'
,
};
});
...
...
theme/components/PinInput.ts
View file @
2d99aba9
...
...
@@ -4,7 +4,7 @@ import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const
baseStyle
=
defineStyle
({
textAlign
:
'
center
'
,
bgColor
:
'
dialog
_
bg
'
,
bgColor
:
'
dialog
.
bg
'
,
});
const
sizes
=
{
...
...
theme/foundations/semanticTokens.ts
View file @
2d99aba9
...
...
@@ -31,7 +31,7 @@ const semanticTokens = {
'
default
'
:
'
red.500
'
,
_dark
:
'
red.500
'
,
},
dialog
_
bg
:
{
dialog
.
bg
:
{
'
default
'
:
'
white
'
,
_dark
:
'
gray.900
'
,
},
...
...
ui/address/tokenSelect/TokenSelectMenu.tsx
View file @
2d99aba9
...
...
@@ -39,7 +39,7 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onI
placeholder=
"Search by token name"
ml=
"1px"
onChange=
{
onInputChange
}
bgColor=
"dialog
_
bg"
bgColor=
"dialog
.
bg"
/>
</
InputGroup
>
<
Flex
flexDir=
"column"
rowGap=
{
6
}
>
...
...
ui/addressVerification/steps/AddressVerificationStepAddress.tsx
View file @
2d99aba9
...
...
@@ -106,7 +106,7 @@ const AddressVerificationStepAddress = ({ defaultAddress, onContinue }: Props) =
<
FormFieldAddress
<
Fields
>
name="address"
isRequired
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
placeholder="Smart contract address (0x...)"
mt=
{
8
}
/
>
...
...
ui/addressVerification/steps/AddressVerificationStepSignature.tsx
View file @
2d99aba9
...
...
@@ -221,7 +221,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
asComponent="Textarea"
isReadOnly
maxH=
{
{
base
:
'
140px
'
,
lg
:
'
80px
'
}
}
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
/
>
</
div
>
{
!
noWeb3Provider
&&
(
...
...
@@ -236,7 +236,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
placeholder="Signature hash"
isRequired
rules=
{
{
pattern
:
SIGNATURE_REGEXP
}
}
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
/
>
)
}
</
Flex
>
...
...
ui/apiKey/ApiKeyModal/ApiKeyForm.tsx
View file @
2d99aba9
...
...
@@ -100,7 +100,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
name="token"
placeholder="Auto-generated API key token"
isReadOnly
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
mb=
{
5
}
/
>
)
}
...
...
@@ -111,7 +111,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
rules=
{
{
maxLength
:
NAME_MAX_LENGTH
,
}
}
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
mb=
{
8
}
/
>
<
Box
marginTop=
{
8
}
>
...
...
ui/customAbi/CustomAbiModal/CustomAbiForm.tsx
View file @
2d99aba9
...
...
@@ -111,7 +111,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
name="contract_address_hash"
placeholder="Smart contract address (0x...)"
isRequired
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
isReadOnly=
{
Boolean
(
data
&&
'
contract_address_hash
'
in
data
)
}
mb=
{
5
}
/
>
...
...
@@ -122,7 +122,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
rules=
{
{
maxLength
:
NAME_MAX_LENGTH
,
}
}
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
mb=
{
5
}
/
>
<
FormFieldText
<
Inputs
>
...
...
@@ -130,7 +130,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
placeholder="Custom ABI [
{
...
}
] (JSON format)"
isRequired
asComponent="Textarea"
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
size="lg"
minH="300px"
mb=
{
8
}
...
...
ui/pages/Watchlist.tsx
View file @
2d99aba9
import
{
Box
,
Button
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
{
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
...
...
@@ -7,9 +7,11 @@ import type { WatchlistAddress, WatchlistResponse } from 'types/api/account';
import
{
resourceKey
}
from
'
lib/api/resources
'
;
import
{
getResourceKey
}
from
'
lib/api/useApiQuery
'
;
import
{
WATCH_LIST_ITEM_WITH_TOKEN_INFO
}
from
'
stubs/account
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
useDisclosure
}
from
'
toolkit/hooks/useDisclosure
'
;
import
AccountPageDescription
from
'
ui/shared/AccountPageDescription
'
;
import
ActionBar
,
{
ACTION_BAR_HEIGHT_DESKTOP
}
from
'
ui/shared/ActionBar
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
DataListDisplay
from
'
ui/shared/DataListDisplay
'
;
import
PageTitle
from
'
ui/shared/Page/PageTitle
'
;
import
Pagination
from
'
ui/shared/pagination/Pagination
'
;
...
...
@@ -44,9 +46,9 @@ const WatchList: React.FC = () => {
addressModalProps
.
onOpen
();
},
[
addressModalProps
]);
const
onAddressModal
Close
=
useCallback
((
)
=>
{
setAddressModalData
(
undefined
);
addressModalProps
.
on
Close
(
);
const
onAddressModal
OpenChange
=
useCallback
(({
open
}:
{
open
:
boolean
}
)
=>
{
!
open
&&
setAddressModalData
(
undefined
);
addressModalProps
.
on
OpenChange
({
open
}
);
},
[
addressModalProps
]);
const
onAddOrEditSuccess
=
useCallback
(
async
()
=>
{
...
...
@@ -60,9 +62,9 @@ const WatchList: React.FC = () => {
deleteModalProps
.
onOpen
();
},
[
deleteModalProps
]);
const
onDeleteModal
Close
=
useCallback
((
)
=>
{
setDeleteModalData
(
undefined
);
deleteModalProps
.
on
Close
(
);
const
onDeleteModal
OpenChange
=
useCallback
(({
open
}:
{
open
:
boolean
}
)
=>
{
!
open
&&
setDeleteModalData
(
undefined
);
deleteModalProps
.
on
OpenChange
({
open
}
);
},
[
deleteModalProps
]);
const
onDeleteSuccess
=
useCallback
(
async
()
=>
{
...
...
@@ -86,44 +88,39 @@ const WatchList: React.FC = () => {
</
ActionBar
>
)
:
null
;
const
list
=
(
<>
<
Box
display=
{
{
base
:
'
block
'
,
lg
:
'
none
'
}
}
>
{
data
?.
items
.
map
((
item
,
index
)
=>
(
<
WatchListItem
key=
{
item
.
address_hash
+
(
isPlaceholderData
?
index
:
''
)
}
item=
{
item
}
isLoading=
{
isPlaceholderData
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
hasEmail=
{
Boolean
(
profileQuery
.
data
?.
email
)
}
/>
))
}
</
Box
>
<
Box
display=
{
{
base
:
'
none
'
,
lg
:
'
block
'
}
}
>
<
WatchlistTable
data=
{
data
?.
items
}
isLoading=
{
isPlaceholderData
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
top=
{
pagination
.
isVisible
?
ACTION_BAR_HEIGHT_DESKTOP
:
0
}
hasEmail=
{
Boolean
(
profileQuery
.
data
?.
email
)
}
/>
</
Box
>
</>
);
return
(
<>
{
description
}
<
DataListDisplay
isError=
{
isError
}
items
=
{
data
?.
items
}
items
Num=
{
data
?.
items
.
length
}
emptyText=
""
content=
{
list
}
actionBar=
{
actionBar
}
/>
<
Skeleton
mt=
{
8
}
isLoaded=
{
!
isPlaceholderData
}
display=
"inline-block"
>
>
<
Box
display=
{
{
base
:
'
block
'
,
lg
:
'
none
'
}
}
>
{
data
?.
items
.
map
((
item
,
index
)
=>
(
<
WatchListItem
key=
{
item
.
address_hash
+
(
isPlaceholderData
?
index
:
''
)
}
item=
{
item
}
isLoading=
{
isPlaceholderData
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
hasEmail=
{
Boolean
(
profileQuery
.
data
?.
email
)
}
/>
))
}
</
Box
>
<
Box
display=
{
{
base
:
'
none
'
,
lg
:
'
block
'
}
}
>
<
WatchlistTable
data=
{
data
?.
items
}
isLoading=
{
isPlaceholderData
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
top=
{
pagination
.
isVisible
?
ACTION_BAR_HEIGHT_DESKTOP
:
0
}
hasEmail=
{
Boolean
(
profileQuery
.
data
?.
email
)
}
/>
</
Box
>
</
DataListDisplay
>
<
Skeleton
mt=
{
8
}
loading=
{
isPlaceholderData
}
display=
"inline-block"
>
<
Button
size=
"lg"
onClick=
{
addressModalProps
.
onOpen
}
...
...
@@ -133,7 +130,7 @@ const WatchList: React.FC = () => {
</
Skeleton
>
<
AddressModal
{
...
addressModalProps
}
on
Close=
{
onAddressModalClos
e
}
on
OpenChange=
{
onAddressModalOpenChang
e
}
onSuccess=
{
onAddOrEditSuccess
}
data=
{
addressModalData
}
isAdd=
{
!
addressModalData
}
...
...
@@ -141,7 +138,7 @@ const WatchList: React.FC = () => {
{
deleteModalData
&&
(
<
DeleteAddressModal
{
...
deleteModalProps
}
on
Close=
{
onDeleteModalClos
e
}
on
OpenChange=
{
onDeleteModalOpenChang
e
}
onSuccess=
{
onDeleteSuccess
}
data=
{
deleteModalData
}
/>
...
...
ui/shared/TableItemActionButtons.tsx
View file @
2d99aba9
...
...
@@ -21,7 +21,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props
<
IconButton
aria
-
label=
"edit"
variant=
"link"
boxSize=
{
5
}
onClick=
{
onEditClick
}
onFocusCapture=
{
onFocusCapture
}
loading=
{
isLoading
}
...
...
@@ -36,7 +35,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props
<
IconButton
aria
-
label=
"delete"
variant=
"link"
boxSize=
{
5
}
onClick=
{
onDeleteClick
}
onFocusCapture=
{
onFocusCapture
}
loading=
{
isLoading
}
...
...
ui/shared/forms/fields/FormFieldCheckbox.tsx
View file @
2d99aba9
import
type
{
ChakraProps
}
from
'
@chakra-ui/react
'
;
import
{
chakra
,
Checkbox
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
{
useController
,
useFormContext
,
type
FieldValues
,
type
Path
}
from
'
react-hook-form
'
;
import
type
{
FormFieldPropsBase
}
from
'
./types
'
;
import
type
{
CheckboxProps
}
from
'
toolkit/chakra/checkbox
'
;
import
{
Checkbox
}
from
'
toolkit/chakra/checkbox
'
;
interface
Props
<
FormFields
extends
FieldValues
,
Name
extends
Path
<
FormFields
>
=
Path
<
FormFields
>
,
>
extends
Omit
<
FormFieldPropsBase
<
FormFields
,
Name
>
,
'
size
'
|
'
bgColor
'
|
'
placeholder
'
>
{
>
extends
Pick
<
FormFieldPropsBase
<
FormFields
,
Name
>
,
'
rules
'
|
'
name
'
|
'
onChange
'
|
'
readOnly
'
>
,
Omit
<
CheckboxProps
,
'
name
'
|
'
onChange
'
>
{
label
:
string
;
}
...
...
@@ -20,8 +21,8 @@ const FormFieldCheckbox = <
label
,
rules
,
onChange
,
isR
eadOnly
,
className
,
r
eadOnly
,
...
rest
}
: Props
<
FormFields
,
Name
>
) =
>
{
const
{
control
}
=
useFormContext
<
FormFields
>
();
const
{
field
,
formState
}
=
useController
<
FormFields
,
typeof
name
>
({
...
...
@@ -32,32 +33,23 @@ const FormFieldCheckbox = <
const
isDisabled
=
formState
.
isSubmitting
;
const
handleChange
:
typeof
field
.
onChange
=
React
.
useCallback
((
...
args
)
=>
{
field
.
onChange
(
...
args
);
const
handleChange
:
typeof
field
.
onChange
=
React
.
useCallback
((
{
checked
}:
{
checked
:
boolean
}
)
=>
{
field
.
onChange
(
checked
);
onChange
?.();
},
[
field
,
onChange
]);
return
(
<
Checkbox
ref=
{
field
.
ref
}
isChecked=
{
field
.
value
}
className=
{
className
}
onChange=
{
handleChange
}
colorScheme=
"blue"
size=
"lg"
isDisabled=
{
isDisabled
}
isReadOnly=
{
isReadOnly
}
checked=
{
field
.
value
}
onCheckedChange=
{
handleChange
}
size=
"md"
disabled=
{
isDisabled
}
{
...
rest
}
>
{
label
}
</
Checkbox
>
);
}
;
const WrappedFormFieldCheckbox = chakra(FormFieldCheckbox);
export type WrappedComponent =
<
FormFields
extends
FieldValues
,
Name
extends
Path
<
FormFields
>
= Path
<
FormFields
>
,
>
(props: Props
<
FormFields
,
Name
>
&
ChakraProps) =
>
React.JSX.Element;
export default React.memo(WrappedFormFieldCheckbox) as WrappedComponent;
export default React.memo(FormFieldCheckbox) as typeof FormFieldCheckbox;
ui/watchlist/AddressModal/AddressForm.tsx
View file @
2d99aba9
import
{
Alert
,
Box
,
Button
,
Text
,
useDisclosure
,
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Text
}
from
'
@chakra-ui/react
'
;
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
type
{
SubmitHandler
}
from
'
react-hook-form
'
;
...
...
@@ -15,6 +9,9 @@ import type { WatchlistAddress, WatchlistErrors } from 'types/api/account';
import
type
{
ResourceErrorAccount
}
from
'
lib/api/resources
'
;
import
useApiFetch
from
'
lib/api/useApiFetch
'
;
import
getErrorMessage
from
'
lib/getErrorMessage
'
;
import
{
Alert
}
from
'
toolkit/chakra/alert
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
useDisclosure
}
from
'
toolkit/hooks/useDisclosure
'
;
import
FormFieldAddress
from
'
ui/shared/forms/fields/FormFieldAddress
'
;
import
FormFieldCheckbox
from
'
ui/shared/forms/fields/FormFieldCheckbox
'
;
import
FormFieldText
from
'
ui/shared/forms/fields/FormFieldText
'
;
...
...
@@ -138,25 +135,24 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
<
form
noValidate
onSubmit=
{
formApi
.
handleSubmit
(
onSubmit
)
}
>
<
FormFieldAddress
<
Inputs
>
name="address"
isR
equired
bgColor="dialog
_
bg"
r
equired
bgColor="dialog
.
bg"
mb=
{
5
}
/
>
<
FormFieldText
<
Inputs
>
name="tag"
placeholder="Private tag (max 35 characters)"
isR
equired
r
equired
rules=
{
{
maxLength
:
TAG_MAX_LENGTH
,
}
}
bgColor="dialog
_
bg"
bgColor="dialog
.
bg"
mb=
{
8
}
/
>
{
userWithoutEmail
?
(
<>
<
Alert
status=
"info"
colorScheme=
"gray"
display=
"flex"
flexDirection=
{
{
base
:
'
column
'
,
md
:
'
row
'
}
}
alignItems=
{
{
base
:
'
flex-start
'
,
lg
:
'
center
'
}
}
...
...
@@ -167,17 +163,17 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
To receive notifications you need to add an email to your profile.
<
Button
variant=
"outline"
size=
"sm"
onClick=
{
authModal
.
onOpen
}
>
Add email
</
Button
>
</
Alert
>
{
authModal
.
isO
pen
&&
<
AuthModal
initialScreen=
{
{
type
:
'
email
'
,
isAuth
:
true
}
}
onClose=
{
authModal
.
onClose
}
/>
}
{
authModal
.
o
pen
&&
<
AuthModal
initialScreen=
{
{
type
:
'
email
'
,
isAuth
:
true
}
}
onClose=
{
authModal
.
onClose
}
/>
}
</>
)
:
(
<>
<
Text
variant=
"
secondary"
fontSize=
"sm"
marginBottom=
{
5
}
>
<
Text
color=
"text.
secondary"
fontSize=
"sm"
marginBottom=
{
5
}
>
Please select what types of notifications you will receive
</
Text
>
<
Box
marginBottom=
{
8
}
>
<
AddressFormNotifications
/>
</
Box
>
<
Text
variant=
"
secondary"
fontSize=
"sm"
marginBottom=
{
{
base
:
'
10px
'
,
lg
:
5
}
}
>
Notification methods
</
Text
>
<
Text
color=
"text.
secondary"
fontSize=
"sm"
marginBottom=
{
{
base
:
'
10px
'
,
lg
:
5
}
}
>
Notification methods
</
Text
>
<
FormFieldCheckbox
<
Inputs
,
'
notification
'
>
name="notification"
label="Email notifications"
...
...
@@ -188,8 +184,8 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
<
Button
size=
"lg"
type=
"submit"
isL
oading=
{
pending
}
isD
isabled=
{
!
formApi
.
formState
.
isDirty
}
l
oading=
{
pending
}
d
isabled=
{
!
formApi
.
formState
.
isDirty
}
>
{
!
isAdd
?
'
Save changes
'
:
'
Add address
'
}
</
Button
>
...
...
ui/watchlist/AddressModal/AddressModal.tsx
View file @
2d99aba9
...
...
@@ -8,13 +8,13 @@ import AddressForm from './AddressForm';
type
Props
=
{
isAdd
:
boolean
;
isO
pen
:
boolean
;
on
Close
:
(
)
=>
void
;
o
pen
:
boolean
;
on
OpenChange
:
({
open
}:
{
open
:
boolean
}
)
=>
void
;
onSuccess
:
()
=>
Promise
<
void
>
;
data
?:
Partial
<
WatchlistAddress
>
;
};
const
AddressModal
:
React
.
FC
<
Props
>
=
({
isOpen
,
onClos
e
,
onSuccess
,
data
,
isAdd
})
=>
{
const
AddressModal
:
React
.
FC
<
Props
>
=
({
open
,
onOpenChang
e
,
onSuccess
,
data
,
isAdd
})
=>
{
const
title
=
!
isAdd
?
'
Edit watch list address
'
:
'
New address to watch list
'
;
const
text
=
isAdd
?
'
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
'
:
''
;
...
...
@@ -26,8 +26,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, isAdd
return
(
<
FormModal
<
WatchlistAddress
>
isOpen=
{
isO
pen
}
on
Close=
{
onClos
e
}
open=
{
o
pen
}
on
OpenChange=
{
onOpenChang
e
}
title=
{
title
}
text=
{
text
}
renderForm=
{
renderForm
}
...
...
ui/watchlist/DeleteAddressModal.tsx
View file @
2d99aba9
...
...
@@ -8,13 +8,13 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import
DeleteModal
from
'
ui/shared/DeleteModal
'
;
type
Props
=
{
isO
pen
:
boolean
;
on
Close
:
(
)
=>
void
;
o
pen
:
boolean
;
on
OpenChange
:
({
open
}:
{
open
:
boolean
}
)
=>
void
;
onSuccess
:
()
=>
Promise
<
void
>
;
data
:
Pick
<
WatchlistAddress
,
'
address_hash
'
|
'
id
'
>
;
};
const
DeleteAddressModal
:
React
.
FC
<
Props
>
=
({
isOpen
,
onClos
e
,
onSuccess
,
data
})
=>
{
const
DeleteAddressModal
:
React
.
FC
<
Props
>
=
({
open
,
onOpenChang
e
,
onSuccess
,
data
})
=>
{
const
isMobile
=
useIsMobile
();
const
apiFetch
=
useApiFetch
();
...
...
@@ -36,8 +36,8 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data
return
(
<
DeleteModal
isOpen=
{
isO
pen
}
on
Close=
{
onClos
e
}
open=
{
o
pen
}
on
OpenChange=
{
onOpenChang
e
}
title=
"Remove address from watch list"
renderContent=
{
renderModalContent
}
mutationFn=
{
mutationFn
}
...
...
ui/watchlist/WatchlistTable/WatchListAddressItem.tsx
View file @
2d99aba9
...
...
@@ -8,7 +8,7 @@ import config from 'configs/app';
import
getCurrencyValue
from
'
lib/getCurrencyValue
'
;
import
{
nbsp
}
from
'
lib/html-entities
'
;
import
{
currencyUnits
}
from
'
lib/units
'
;
import
Skeleton
from
'
ui/shared/chakra/S
keleton
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/s
keleton
'
;
import
CurrencyValue
from
'
ui/shared/CurrencyValue
'
;
import
AddressEntity
from
'
ui/shared/entities/address/AddressEntity
'
;
import
*
as
TokenEntity
from
'
ui/shared/entities/token/TokenEntity
'
;
...
...
@@ -26,7 +26,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
const
{
usdBn
:
usdNative
}
=
getCurrencyValue
({
value
:
item
.
address_balance
,
accuracy
:
2
,
accuracyUsd
:
2
,
exchangeRate
:
item
.
exchange_rate
});
return
(
<
VStack
spacing
=
{
3
}
align=
"stretch"
fontWeight=
{
500
}
>
<
VStack
gap
=
{
3
}
align=
"stretch"
fontWeight=
{
500
}
>
<
AddressEntity
address=
{
item
.
address
}
isLoading=
{
isLoading
}
...
...
@@ -38,7 +38,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
token=
{
nativeTokenData
}
isLoading=
{
isLoading
}
/>
<
Skeleton
isLoaded=
{
!
isLoading
}
whiteSpace=
"pre"
display=
"inline-flex"
>
<
Skeleton
loading=
{
isLoading
}
whiteSpace=
"pre"
display=
"inline-flex"
>
<
span
>
{
currencyUnits
.
ether
}
balance:
</
span
>
<
CurrencyValue
value=
{
item
.
address_balance
}
...
...
@@ -49,19 +49,19 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
/>
</
Skeleton
>
</
Flex
>
{
item
.
tokens_count
&&
(
<
HStack
spacing
=
{
2
}
fontSize=
"sm"
pl=
{
7
}
>
{
Boolean
(
item
.
tokens_count
)
&&
(
<
HStack
gap
=
{
2
}
fontSize=
"sm"
pl=
{
7
}
>
<
IconSvg
name=
"tokens"
boxSize=
{
5
}
isLoading=
{
isLoading
}
borderRadius=
"sm"
/>
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-flex"
>
<
Skeleton
loading=
{
isLoading
}
display=
"inline-flex"
>
<
span
>
{
`Tokens:${ nbsp }`
+
item
.
tokens_count
+
(
item
.
tokens_overflow
?
'
+
'
:
''
)
}
</
span
>
<
Text
variant=
"secondary"
fontWeight=
{
400
}
>
{
`${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })`
}
</
Text
>
<
Text
color=
"text.secondary"
>
{
`${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })`
}
</
Text
>
</
Skeleton
>
</
HStack
>
)
}
{
item
.
tokens_fiat_value
&&
(
<
HStack
spacing
=
{
2
}
fontSize=
"sm"
pl=
{
7
}
>
{
Boolean
(
item
.
tokens_fiat_value
)
&&
(
<
HStack
gap
=
{
2
}
fontSize=
"sm"
pl=
{
7
}
>
<
IconSvg
boxSize=
{
5
}
name=
"wallet"
isLoading=
{
isLoading
}
/>
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-flex"
>
<
Skeleton
loading=
{
isLoading
}
display=
"inline-flex"
>
<
Text
>
{
`Net worth:${ nbsp }`
}
{
`${ item.tokens_overflow ? '>' : '' }
...
...
ui/watchlist/WatchlistTable/WatchListItem.tsx
View file @
2d99aba9
import
{
Box
,
Switch
,
Text
,
HStack
,
Flex
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Text
,
HStack
,
Flex
}
from
'
@chakra-ui/react
'
;
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
import
type
{
WatchlistAddress
}
from
'
types/api/account
'
;
import
useApiFetch
from
'
lib/api/useApiFetch
'
;
import
useToast
from
'
lib/hooks/useToast
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
Tag
from
'
ui/shared/chakra/Tag
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
Switch
}
from
'
toolkit/chakra/switch
'
;
import
{
Tag
}
from
'
toolkit/chakra/tag
'
;
import
{
toaster
}
from
'
toolkit/chakra/toaster
'
;
import
ListItemMobile
from
'
ui/shared/ListItemMobile/ListItemMobile
'
;
import
TableItemActionButtons
from
'
ui/shared/TableItemActionButtons
'
;
...
...
@@ -32,34 +33,21 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
return
onDeleteClick
(
item
);
},
[
item
,
onDeleteClick
]);
const
errorToast
=
useToast
();
const
apiFetch
=
useApiFetch
();
const
showErrorToast
=
useCallback
(()
=>
{
errorToast
({
position
:
'
top-right
'
,
toaster
.
error
({
title
:
'
Error
'
,
description
:
'
There has been an error processing your request
'
,
colorScheme
:
'
red
'
,
status
:
'
error
'
,
variant
:
'
subtle
'
,
isClosable
:
true
,
icon
:
null
,
});
},
[
errorToast
]);
},
[
]);
const
notificationToast
=
useToast
();
const
showNotificationToast
=
useCallback
((
isOn
:
boolean
)
=>
{
notificationToast
({
position
:
'
top-right
'
,
description
:
!
isOn
?
'
Email notification is ON
'
:
'
Email notification is OFF
'
,
colorScheme
:
'
green
'
,
status
:
'
success
'
,
variant
:
'
subtle
'
,
toaster
.
success
({
title
:
'
Success
'
,
isClosable
:
true
,
icon
:
null
,
description
:
!
isOn
?
'
Email notification is ON
'
:
'
Email notification is OFF
'
,
});
},
[
notificationToast
]);
},
[
]);
const
{
mutate
}
=
useMutation
({
mutationFn
:
()
=>
{
...
...
@@ -90,22 +78,22 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
<
ListItemMobile
>
<
Box
maxW=
"100%"
>
<
WatchListAddressItem
item=
{
item
}
isLoading=
{
isLoading
}
/>
<
HStack
spacing
=
{
3
}
mt=
{
6
}
>
<
Text
fontSiz
e=
"sm"
fontWeight=
{
500
}
>
Private tag
</
Text
>
<
Tag
isLoading=
{
isLoading
}
isT
runcated
>
{
item
.
name
}
</
Tag
>
<
HStack
gap
=
{
3
}
mt=
{
6
}
>
<
Text
textStyl
e=
"sm"
fontWeight=
{
500
}
>
Private tag
</
Text
>
<
Tag
loading=
{
isLoading
}
t
runcated
>
{
item
.
name
}
</
Tag
>
</
HStack
>
</
Box
>
<
Flex
alignItems=
"center"
justifyContent=
"space-between"
mt=
{
6
}
w=
"100%"
>
<
HStack
spacing
=
{
3
}
>
<
Text
fontSiz
e=
"sm"
fontWeight=
{
500
}
>
Email notification
</
Text
>
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-block"
>
<
HStack
gap
=
{
3
}
>
<
Text
textStyl
e=
"sm"
fontWeight=
{
500
}
>
Email notification
</
Text
>
<
Skeleton
loading=
{
isLoading
}
display=
"inline-block"
>
<
Switch
colorScheme=
"blue"
size=
"md"
isC
hecked=
{
notificationEnabled
}
onChange=
{
onSwitch
}
c
hecked=
{
notificationEnabled
}
onCh
eckedCh
ange=
{
onSwitch
}
aria
-
label=
"Email notification"
isD
isabled=
{
!
hasEmail
||
switchDisabled
}
d
isabled=
{
!
hasEmail
||
switchDisabled
}
/>
</
Skeleton
>
</
HStack
>
...
...
ui/watchlist/WatchlistTable/WatchListTableItem.tsx
View file @
2d99aba9
import
{
Tr
,
Td
,
Switch
,
}
from
'
@chakra-ui/react
'
;
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
import
type
{
WatchlistAddress
}
from
'
types/api/account
'
;
import
useApiFetch
from
'
lib/api/useApiFetch
'
;
import
useToast
from
'
lib/hooks/useToast
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
Tag
from
'
ui/shared/chakra/Tag
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
Switch
}
from
'
toolkit/chakra/switch
'
;
import
{
TableCell
,
TableRow
}
from
'
toolkit/chakra/table
'
;
import
{
Tag
}
from
'
toolkit/chakra/tag
'
;
import
{
toaster
}
from
'
toolkit/chakra/toaster
'
;
import
TableItemActionButtons
from
'
ui/shared/TableItemActionButtons
'
;
import
WatchListAddressItem
from
'
./WatchListAddressItem
'
;
...
...
@@ -35,34 +32,21 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
return
onDeleteClick
(
item
);
},
[
item
,
onDeleteClick
]);
const
errorToast
=
useToast
();
const
apiFetch
=
useApiFetch
();
const
showErrorToast
=
useCallback
(()
=>
{
errorToast
({
position
:
'
top-right
'
,
toaster
.
error
({
title
:
'
Error
'
,
description
:
'
There has been an error processing your request
'
,
colorScheme
:
'
red
'
,
status
:
'
error
'
,
variant
:
'
subtle
'
,
isClosable
:
true
,
icon
:
null
,
});
},
[
errorToast
]);
},
[
]);
const
notificationToast
=
useToast
();
const
showNotificationToast
=
useCallback
((
isOn
:
boolean
)
=>
{
notificationToast
({
position
:
'
top-right
'
,
description
:
!
isOn
?
'
Email notification is ON
'
:
'
Email notification is OFF
'
,
colorScheme
:
'
green
'
,
status
:
'
success
'
,
variant
:
'
subtle
'
,
toaster
.
success
({
title
:
'
Success
'
,
isClosable
:
true
,
icon
:
null
,
description
:
!
isOn
?
'
Email notification is ON
'
:
'
Email notification is OFF
'
,
});
},
[
notificationToast
]);
},
[
]);
const
{
mutate
}
=
useMutation
({
mutationFn
:
()
=>
{
...
...
@@ -90,27 +74,27 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
},
[
mutate
]);
return
(
<
T
r
alignItems=
"top"
key=
{
item
.
address_hash
}
>
<
T
d
><
WatchListAddressItem
item=
{
item
}
isLoading=
{
isLoading
}
/></
Td
>
<
T
d
>
<
Tag
isLoading=
{
isLoading
}
isT
runcated
>
{
item
.
name
}
</
Tag
>
</
T
d
>
<
T
d
>
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-block"
>
<
T
ableRow
alignItems=
"top"
key=
{
item
.
address_hash
}
>
<
T
ableCell
><
WatchListAddressItem
item=
{
item
}
isLoading=
{
isLoading
}
/></
TableCell
>
<
T
ableCell
>
<
Tag
loading=
{
isLoading
}
t
runcated
>
{
item
.
name
}
</
Tag
>
</
T
ableCell
>
<
T
ableCell
>
<
Skeleton
loading=
{
isLoading
}
display=
"inline-block"
>
<
Switch
colorScheme=
"blue"
size=
"md"
isC
hecked=
{
notificationEnabled
}
onChange=
{
onSwitch
}
isD
isabled=
{
!
hasEmail
||
switchDisabled
}
c
hecked=
{
notificationEnabled
}
onCh
eckedCh
ange=
{
onSwitch
}
d
isabled=
{
!
hasEmail
||
switchDisabled
}
aria
-
label=
"Email notification"
/>
</
Skeleton
>
</
T
d
>
<
T
d
>
</
T
ableCell
>
<
T
ableCell
>
<
TableItemActionButtons
onDeleteClick=
{
onItemDeleteClick
}
onEditClick=
{
onItemEditClick
}
isLoading=
{
isLoading
}
/>
</
T
d
>
</
T
r
>
</
T
ableCell
>
</
T
ableRow
>
);
};
...
...
ui/watchlist/WatchlistTable/WatchlistTable.tsx
View file @
2d99aba9
import
{
Table
,
Tbody
,
Tr
,
Th
,
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
WatchlistAddress
}
from
'
types/api/account
'
;
import
TheadSticky
from
'
ui/shared/TheadSticky
'
;
import
{
TableBody
,
TableColumnHeader
,
TableHeaderSticky
,
TableRoot
,
TableRow
}
from
'
toolkit/chakra/table
'
;
import
WatchlistTableItem
from
'
./WatchListTableItem
'
;
...
...
@@ -23,16 +17,16 @@ interface Props {
const
WatchlistTable
=
({
data
,
isLoading
,
onDeleteClick
,
onEditClick
,
top
,
hasEmail
}:
Props
)
=>
{
return
(
<
Table
minWidth=
"600px"
>
<
T
head
Sticky
top=
{
top
}
>
<
T
r
>
<
T
h
width=
"70%"
>
Address
</
Th
>
<
T
h
width=
"30%"
>
Private tag
</
Th
>
<
T
h
width=
"160px"
>
Email notification
</
Th
>
<
T
h
width=
"108px"
></
Th
>
</
T
r
>
</
T
head
Sticky
>
<
T
b
ody
>
<
Table
Root
minWidth=
"600px"
>
<
T
ableHeader
Sticky
top=
{
top
}
>
<
T
ableRow
>
<
T
ableColumnHeader
width=
"70%"
>
Address
</
TableColumnHeader
>
<
T
ableColumnHeader
width=
"30%"
>
Private tag
</
TableColumnHeader
>
<
T
ableColumnHeader
width=
"160px"
>
Email notification
</
TableColumnHeader
>
<
T
ableColumnHeader
width=
"108px"
></
TableColumnHeader
>
</
T
ableRow
>
</
T
ableHeader
Sticky
>
<
T
ableB
ody
>
{
data
?.
map
((
item
,
index
)
=>
(
<
WatchlistTableItem
key=
{
item
.
address_hash
+
(
isLoading
?
index
:
''
)
}
...
...
@@ -43,8 +37,8 @@ const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasE
hasEmail=
{
hasEmail
}
/>
))
}
</
T
b
ody
>
</
Table
>
</
T
ableB
ody
>
</
Table
Root
>
);
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment