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
9837acc2
Commit
9837acc2
authored
Sep 20, 2024
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add auth guard to address action items
parent
b98271b6
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
122 additions
and
91 deletions
+122
-91
useIsAccountActionAllowed.tsx
lib/hooks/useIsAccountActionAllowed.tsx
+0
-8
AddressFavoriteButton.tsx
ui/address/details/AddressFavoriteButton.tsx
+24
-24
AccountActionsMenu.tsx
ui/shared/AccountActionsMenu/AccountActionsMenu.tsx
+2
-4
PrivateTagMenuItem.tsx
ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx
+17
-14
PublicTagMenuItem.tsx
ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx
+14
-22
TokenInfoMenuItem.tsx
ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx
+18
-15
types.ts
ui/shared/AccountActionsMenu/types.ts
+0
-2
AuthGuard.tsx
ui/snippets/auth/AuthGuard.tsx
+39
-0
AuthModal.tsx
ui/snippets/auth/AuthModal.tsx
+8
-2
No files found.
lib/hooks/useIsAccountActionAllowed.tsx
deleted
100644 → 0
View file @
b98271b6
import
React
from
'
react
'
;
// TODO @tom2drum remove this hook
export
default
function
useIsAccountActionAllowed
()
{
return
React
.
useCallback
(()
=>
{
return
true
;
},
[]);
}
ui/address/details/AddressFavoriteButton.tsx
View file @
9837acc2
...
...
@@ -5,10 +5,10 @@ import React from 'react';
import
config
from
'
configs/app
'
;
import
{
getResourceKey
}
from
'
lib/api/useApiQuery
'
;
import
useIsAccountActionAllowed
from
'
lib/hooks/useIsAccountActionAllowed
'
;
import
usePreventFocusAfterModalClosing
from
'
lib/hooks/usePreventFocusAfterModalClosing
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
AuthGuard
from
'
ui/snippets/auth/AuthGuard
'
;
import
WatchlistAddModal
from
'
ui/watchlist/AddressModal/AddressModal
'
;
import
DeleteAddressModal
from
'
ui/watchlist/DeleteAddressModal
'
;
...
...
@@ -23,16 +23,12 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const
deleteModalProps
=
useDisclosure
();
const
queryClient
=
useQueryClient
();
const
router
=
useRouter
();
const
isAccountActionAllowed
=
useIsAccountActionAllowed
();
const
onFocusCapture
=
usePreventFocusAfterModalClosing
();
const
handleClick
=
React
.
useCallback
(()
=>
{
if
(
!
isAccountActionAllowed
())
{
return
;
}
const
handleAddToFavorite
=
React
.
useCallback
(()
=>
{
watchListId
?
deleteModalProps
.
onOpen
()
:
addModalProps
.
onOpen
();
!
watchListId
&&
mixpanel
.
logEvent
(
mixpanel
.
EventTypes
.
PAGE_WIDGET
,
{
Type
:
'
Add to watchlist
'
});
},
[
isAccountActionAllowed
,
watchListId
,
deleteModalProps
,
addModalProps
]);
},
[
watchListId
,
deleteModalProps
,
addModalProps
]);
const
handleAddOrDeleteSuccess
=
React
.
useCallback
(
async
()
=>
{
const
queryKey
=
getResourceKey
(
'
address
'
,
{
pathParams
:
{
hash
:
router
.
query
.
hash
?.
toString
()
}
});
...
...
@@ -50,7 +46,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const
formData
=
React
.
useMemo
(()
=>
{
if
(
typeof
watchListId
!==
'
number
'
)
{
return
;
return
{
address_hash
:
hash
}
;
}
return
{
...
...
@@ -65,21 +61,25 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
return
(
<>
<
Tooltip
label=
{
`${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }`
}
>
<
IconButton
isActive=
{
Boolean
(
watchListId
)
}
className=
{
className
}
aria
-
label=
"edit"
variant=
"outline"
size=
"sm"
pl=
"6px"
pr=
"6px"
flexShrink=
{
0
}
onClick=
{
handleClick
}
icon=
{
<
IconSvg
name=
{
watchListId
?
'
star_filled
'
:
'
star_outline
'
}
boxSize=
{
5
}
/>
}
onFocusCapture=
{
onFocusCapture
}
/>
</
Tooltip
>
<
AuthGuard
onAuthSuccess=
{
handleAddToFavorite
}
>
{
({
onClick
})
=>
(
<
Tooltip
label=
{
`${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }`
}
>
<
IconButton
isActive=
{
Boolean
(
watchListId
)
}
className=
{
className
}
aria
-
label=
"edit"
variant=
"outline"
size=
"sm"
pl=
"6px"
pr=
"6px"
flexShrink=
{
0
}
onClick=
{
onClick
}
icon=
{
<
IconSvg
name=
{
watchListId
?
'
star_filled
'
:
'
star_outline
'
}
boxSize=
{
5
}
/>
}
onFocusCapture=
{
onFocusCapture
}
/>
</
Tooltip
>
)
}
</
AuthGuard
>
<
WatchlistAddModal
{
...
addModalProps
}
isAdd
...
...
@@ -87,7 +87,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
onSuccess=
{
handleAddOrDeleteSuccess
}
data=
{
formData
}
/>
{
formData
&&
(
{
formData
.
id
&&
(
<
DeleteAddressModal
{
...
deleteModalProps
}
onClose=
{
handleDeleteModalClose
}
...
...
ui/shared/AccountActionsMenu/AccountActionsMenu.tsx
View file @
9837acc2
...
...
@@ -5,7 +5,6 @@ import React from 'react';
import
type
{
ItemProps
}
from
'
./types
'
;
import
config
from
'
configs/app
'
;
import
useIsAccountActionAllowed
from
'
lib/hooks/useIsAccountActionAllowed
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
getQueryParamString
from
'
lib/router/getQueryParamString
'
;
import
Menu
from
'
ui/shared/chakra/Menu
'
;
...
...
@@ -30,7 +29,6 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
const
isTokenPage
=
router
.
pathname
===
'
/token/[hash]
'
;
const
isTokenInstancePage
=
router
.
pathname
===
'
/token/[hash]/instance/[id]
'
;
const
isTxPage
=
router
.
pathname
===
'
/tx/[hash]
'
;
const
isAccountActionAllowed
=
useIsAccountActionAllowed
();
const
profileQuery
=
useProfileQuery
();
...
...
@@ -74,7 +72,7 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
if
(
items
.
length
===
1
)
{
return
(
<
Box
className=
{
className
}
>
{
items
[
0
].
render
({
type
:
'
button
'
,
hash
,
onBeforeClick
:
isAccountActionAllowed
})
}
{
items
[
0
].
render
({
type
:
'
button
'
,
hash
})
}
</
Box
>
);
}
...
...
@@ -95,7 +93,7 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
<
MenuList
minWidth=
"180px"
zIndex=
"popover"
>
{
items
.
map
(({
render
},
index
)
=>
(
<
React
.
Fragment
key=
{
index
}
>
{
render
({
type
:
'
menu_item
'
,
hash
,
onBeforeClick
:
isAccountActionAllowed
})
}
{
render
({
type
:
'
menu_item
'
,
hash
})
}
</
React
.
Fragment
>
))
}
</
MenuList
>
...
...
ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx
View file @
9837acc2
...
...
@@ -12,6 +12,7 @@ import getPageType from 'lib/mixpanel/getPageType';
import
AddressModal
from
'
ui/privateTags/AddressModal/AddressModal
'
;
import
TransactionModal
from
'
ui/privateTags/TransactionModal/TransactionModal
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
AuthGuard
from
'
ui/snippets/auth/AuthGuard
'
;
import
ButtonItem
from
'
../parts/ButtonItem
'
;
import
MenuItem
from
'
../parts/MenuItem
'
;
...
...
@@ -20,7 +21,7 @@ interface Props extends ItemProps {
entityType
?:
'
address
'
|
'
tx
'
;
}
const
PrivateTagMenuItem
=
({
className
,
hash
,
onBeforeClick
,
entityType
=
'
address
'
,
type
}:
Props
)
=>
{
const
PrivateTagMenuItem
=
({
className
,
hash
,
entityType
=
'
address
'
,
type
}:
Props
)
=>
{
const
modal
=
useDisclosure
();
const
queryClient
=
useQueryClient
();
const
router
=
useRouter
();
...
...
@@ -28,14 +29,6 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick, entityType = 'addr
const
queryKey
=
getResourceKey
(
entityType
===
'
tx
'
?
'
tx
'
:
'
address
'
,
{
pathParams
:
{
hash
}
});
const
queryData
=
queryClient
.
getQueryData
<
Address
|
Transaction
>
(
queryKey
);
const
handleClick
=
React
.
useCallback
(()
=>
{
if
(
!
onBeforeClick
())
{
return
;
}
modal
.
onOpen
();
},
[
modal
,
onBeforeClick
]);
const
handleAddPrivateTag
=
React
.
useCallback
(
async
()
=>
{
await
queryClient
.
refetchQueries
({
queryKey
});
modal
.
onClose
();
...
...
@@ -62,14 +55,24 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick, entityType = 'addr
const
element
=
(()
=>
{
switch
(
type
)
{
case
'
button
'
:
{
return
<
ButtonItem
label=
"Add private tag"
icon=
"privattags"
onClick=
{
handleClick
}
className=
{
className
}
/>;
return
(
<
AuthGuard
onAuthSuccess=
{
modal
.
onOpen
}
>
{
({
onClick
})
=>
(
<
ButtonItem
label=
"Add private tag"
icon=
"privattags"
onClick=
{
onClick
}
className=
{
className
}
/>
)
}
</
AuthGuard
>
);
}
case
'
menu_item
'
:
{
return
(
<
MenuItem
className=
{
className
}
onClick=
{
handleClick
}
>
<
IconSvg
name=
"privattags"
boxSize=
{
6
}
mr=
{
2
}
/>
<
span
>
Add private tag
</
span
>
</
MenuItem
>
<
AuthGuard
onAuthSuccess=
{
modal
.
onOpen
}
>
{
({
onClick
})
=>
(
<
MenuItem
className=
{
className
}
onClick=
{
onClick
}
>
<
IconSvg
name=
"privattags"
boxSize=
{
6
}
mr=
{
2
}
/>
<
span
>
Add private tag
</
span
>
</
MenuItem
>
)
}
</
AuthGuard
>
);
}
}
...
...
ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx
View file @
9837acc2
...
...
@@ -8,34 +8,26 @@ import IconSvg from 'ui/shared/IconSvg';
import
ButtonItem
from
'
../parts/ButtonItem
'
;
import
MenuItem
from
'
../parts/MenuItem
'
;
const
PublicTagMenuItem
=
({
className
,
hash
,
onBeforeClick
,
type
}:
ItemProps
)
=>
{
const
PublicTagMenuItem
=
({
className
,
hash
,
type
}:
ItemProps
)
=>
{
const
router
=
useRouter
();
const
handleClick
=
React
.
useCallback
(()
=>
{
if
(
!
onBeforeClick
())
{
return
;
}
router
.
push
({
pathname
:
'
/public-tags/submit
'
,
query
:
{
addresses
:
[
hash
]
}
});
},
[
hash
,
onBeforeClick
,
router
]);
},
[
hash
,
router
]);
const
element
=
(()
=>
{
switch
(
type
)
{
case
'
button
'
:
{
return
<
ButtonItem
label=
"Add public tag"
icon=
"publictags"
onClick=
{
handleClick
}
className=
{
className
}
/>;
}
case
'
menu_item
'
:
{
return
(
<
MenuItem
className=
{
className
}
onClick=
{
handleClick
}
>
<
IconSvg
name=
"publictags"
boxSize=
{
6
}
mr=
{
2
}
/>
<
span
>
Add public tag
</
span
>
</
MenuItem
>
);
}
switch
(
type
)
{
case
'
button
'
:
{
return
<
ButtonItem
label=
"Add public tag"
icon=
"publictags"
onClick=
{
handleClick
}
className=
{
className
}
/>;
}
})();
return
element
;
case
'
menu_item
'
:
{
return
(
<
MenuItem
className=
{
className
}
onClick=
{
handleClick
}
>
<
IconSvg
name=
"publictags"
boxSize=
{
6
}
mr=
{
2
}
/>
<
span
>
Add public tag
</
span
>
</
MenuItem
>
);
}
}
};
export
default
React
.
memo
(
PublicTagMenuItem
);
ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx
View file @
9837acc2
...
...
@@ -9,12 +9,13 @@ import useApiQuery from 'lib/api/useApiQuery';
import
{
PAGE_TYPE_DICT
}
from
'
lib/mixpanel/getPageType
'
;
import
AddressVerificationModal
from
'
ui/addressVerification/AddressVerificationModal
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
AuthGuard
from
'
ui/snippets/auth/AuthGuard
'
;
import
useIsAuth
from
'
ui/snippets/auth/useIsAuth
'
;
import
ButtonItem
from
'
../parts/ButtonItem
'
;
import
MenuItem
from
'
../parts/MenuItem
'
;
const
TokenInfoMenuItem
=
({
className
,
hash
,
onBeforeClick
,
type
}:
ItemProps
)
=>
{
const
TokenInfoMenuItem
=
({
className
,
hash
,
type
}:
ItemProps
)
=>
{
const
router
=
useRouter
();
const
modal
=
useDisclosure
();
const
isAuth
=
useIsAuth
();
...
...
@@ -38,14 +39,6 @@ const TokenInfoMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps)
},
});
const
handleAddAddressClick
=
React
.
useCallback
(()
=>
{
if
(
!
onBeforeClick
({
pathname
:
'
/account/verified-addresses
'
}))
{
return
;
}
modal
.
onOpen
();
},
[
modal
,
onBeforeClick
]);
const
handleAddApplicationClick
=
React
.
useCallback
(
async
()
=>
{
router
.
push
({
pathname
:
'
/account/verified-addresses
'
,
query
:
{
address
:
hash
}
});
},
[
hash
,
router
]);
...
...
@@ -72,18 +65,28 @@ const TokenInfoMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps)
return
hasApplication
||
tokenInfoQuery
.
data
?.
tokenAddress
?
'
Update token info
'
:
'
Add token info
'
;
})();
const
on
Click
=
isVerifiedAddress
?
handleAddApplicationClick
:
handleAddAddressClick
;
const
on
AuthSuccess
=
isVerifiedAddress
?
handleAddApplicationClick
:
modal
.
onOpen
;
switch
(
type
)
{
case
'
button
'
:
{
return
<
ButtonItem
label=
{
label
}
icon=
{
icon
}
onClick=
{
onClick
}
className=
{
className
}
/>;
return
(
<
AuthGuard
onAuthSuccess=
{
onAuthSuccess
}
>
{
({
onClick
})
=>
(
<
ButtonItem
label=
{
label
}
icon=
{
icon
}
onClick=
{
onClick
}
className=
{
className
}
/>
)
}
</
AuthGuard
>
);
}
case
'
menu_item
'
:
{
return
(
<
MenuItem
className=
{
className
}
onClick=
{
onClick
}
>
{
icon
}
<
chakra
.
span
ml=
{
2
}
>
{
label
}
</
chakra
.
span
>
</
MenuItem
>
<
AuthGuard
onAuthSuccess=
{
onAuthSuccess
}
>
{
({
onClick
})
=>
(
<
MenuItem
className=
{
className
}
onClick=
{
onClick
}
>
{
icon
}
<
chakra
.
span
ml=
{
2
}
>
{
label
}
</
chakra
.
span
>
</
MenuItem
>
)
}
</
AuthGuard
>
);
}
}
...
...
ui/shared/AccountActionsMenu/types.ts
View file @
9837acc2
export
type
ItemType
=
'
button
'
|
'
menu_item
'
;
import
type
{
Route
}
from
'
nextjs-routes
'
;
export
interface
ItemProps
{
className
?:
string
;
type
:
ItemType
;
hash
:
string
;
onBeforeClick
:
(
route
?:
Route
)
=>
boolean
;
}
ui/snippets/auth/AuthGuard.tsx
0 → 100644
View file @
9837acc2
import
{
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
AuthModal
from
'
./AuthModal
'
;
import
useIsAuth
from
'
./useIsAuth
'
;
interface
InjectedProps
{
onClick
:
()
=>
void
;
}
interface
Props
{
children
:
(
props
:
InjectedProps
)
=>
React
.
ReactNode
;
onAuthSuccess
:
()
=>
void
;
}
const
AuthGuard
=
({
children
,
onAuthSuccess
}:
Props
)
=>
{
const
authModal
=
useDisclosure
();
const
isAuth
=
useIsAuth
();
const
handleClick
=
React
.
useCallback
(()
=>
{
isAuth
?
onAuthSuccess
()
:
authModal
.
onOpen
();
},
[
authModal
,
isAuth
,
onAuthSuccess
]);
const
handleModalClose
=
React
.
useCallback
((
isSuccess
?:
boolean
)
=>
{
if
(
isSuccess
)
{
onAuthSuccess
();
}
authModal
.
onClose
();
},
[
authModal
,
onAuthSuccess
]);
return
(
<>
{
children
({
onClick
:
handleClick
})
}
{
authModal
.
isOpen
&&
<
AuthModal
onClose=
{
handleModalClose
}
initialScreen=
{
{
type
:
'
select_method
'
}
}
/>
}
</>
);
};
export
default
React
.
memo
(
AuthGuard
);
ui/snippets/auth/AuthModal.tsx
View file @
9837acc2
...
...
@@ -15,11 +15,12 @@ import useProfileQuery from './useProfileQuery';
interface
Props
{
initialScreen
:
Screen
;
onClose
:
()
=>
void
;
onClose
:
(
isSuccess
?:
boolean
)
=>
void
;
}
const
AuthModal
=
({
initialScreen
,
onClose
}:
Props
)
=>
{
const
[
steps
,
setSteps
]
=
React
.
useState
<
Array
<
Screen
>>
([
initialScreen
]);
const
[
isSuccess
,
setIsSuccess
]
=
React
.
useState
(
false
);
const
profileQuery
=
useProfileQuery
();
const
onNextStep
=
React
.
useCallback
((
screen
:
Screen
)
=>
{
...
...
@@ -35,6 +36,7 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
},
[
initialScreen
,
onClose
]);
const
onAuthSuccess
=
React
.
useCallback
(
async
(
screen
:
ScreenSuccess
)
=>
{
setIsSuccess
(
true
);
const
{
data
}
=
await
profileQuery
.
refetch
();
if
(
data
)
{
onNextStep
({
...
screen
,
profile
:
data
});
...
...
@@ -42,6 +44,10 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
// TODO @tom2drum handle error case
},
[
onNextStep
,
profileQuery
]);
const
onModalClose
=
React
.
useCallback
(()
=>
{
onClose
(
isSuccess
);
},
[
isSuccess
,
onClose
]);
const
header
=
(()
=>
{
const
currentStep
=
steps
[
steps
.
length
-
1
];
switch
(
currentStep
.
type
)
{
...
...
@@ -92,7 +98,7 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
})();
return
(
<
Modal
isOpen
onClose=
{
onClose
}
size=
{
{
base
:
'
full
'
,
lg
:
'
sm
'
}
}
>
<
Modal
isOpen
onClose=
{
on
Modal
Close
}
size=
{
{
base
:
'
full
'
,
lg
:
'
sm
'
}
}
>
<
ModalOverlay
/>
<
ModalContent
p=
{
6
}
maxW=
{
{
lg
:
'
400px
'
}
}
>
<
ModalHeader
fontWeight=
"500"
textStyle=
"h3"
mb=
{
2
}
display=
"flex"
alignItems=
"center"
columnGap=
{
2
}
>
...
...
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