-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a ENS address selector to the Profile page (#367)
* add base ens name selector in Profile * fix styles * fix codegen schema error * handle error for xdai * shorten address in dropdown * only show the address selector on mainnet * Fix code style issues with Prettier * handle account change * parse ens name from qs * bump graphql-request * show full address when there are no ens names * disable dropdown if there are no ens names * add rinkeby subgraph * remove dropdown when there's no ens names * shorten always referral address * prevent returning an invalid state due to a loading variable * Update src/custom/hooks/useParseReferralQueryParam.ts Co-authored-by: Leandro Boscariol <[email protected]> * fix wrong utils path * merge useEffects hooks Co-authored-by: Agustín Longoni <[email protected]> Co-authored-by: Lint Action <[email protected]> Co-authored-by: Leandro Boscariol <[email protected]>
- Loading branch information
1 parent
08e7b07
commit d983da0
Showing
11 changed files
with
332 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { useCallback, useEffect, useRef, useState } from 'react' | ||
import styled, { css } from 'styled-components/macro' | ||
import { Check, ChevronDown } from 'react-feather' | ||
import { useOnClickOutside } from 'hooks/useOnClickOutside' | ||
import { useActiveWeb3React } from 'hooks/web3' | ||
import { ensNames } from './ens' | ||
import { useAddress } from 'state/affiliate/hooks' | ||
import { updateAddress } from 'state/affiliate/actions' | ||
import { useAppDispatch } from 'state/hooks' | ||
import { isAddress, shortenAddress } from 'utils' | ||
|
||
type AddressSelectorProps = { | ||
address: string | ||
} | ||
|
||
export default function AddressSelector(props: AddressSelectorProps) { | ||
const { address } = props | ||
const dispatch = useAppDispatch() | ||
const selectedAddress = useAddress() | ||
const { chainId, library } = useActiveWeb3React() | ||
const [open, setOpen] = useState(false) | ||
const [items, setItems] = useState<string[]>([address]) | ||
const toggle = useCallback(() => setOpen((open) => !open), []) | ||
const node = useRef<HTMLDivElement>(null) | ||
useOnClickOutside(node, open ? toggle : undefined) | ||
|
||
const tryShortenAddress = useCallback((item?: string) => { | ||
if (!item) { | ||
return item | ||
} | ||
|
||
try { | ||
return shortenAddress(item) | ||
} catch (error) { | ||
return item | ||
} | ||
}, []) | ||
|
||
const handleSelectItem = useCallback( | ||
(item: string) => { | ||
dispatch(updateAddress(item)) | ||
toggle() | ||
}, | ||
[dispatch, toggle] | ||
) | ||
|
||
useEffect(() => { | ||
if (!chainId) { | ||
return | ||
} | ||
|
||
ensNames(chainId, address).then((response) => { | ||
if ('error' in response) { | ||
console.info(response.error) | ||
setItems([address]) | ||
return | ||
} | ||
setItems([...response, address]) | ||
}) | ||
}, [address, chainId]) | ||
|
||
useEffect(() => { | ||
// if the user switches accounts, reset the selected address | ||
const switchedAccounts = isAddress(selectedAddress) && selectedAddress !== address | ||
if (switchedAccounts || !selectedAddress) { | ||
dispatch(updateAddress(address)) | ||
return | ||
} | ||
|
||
// the selected address is a ens name, verify that resolves to the correct address | ||
const verify = async () => { | ||
const resolvedAddress = await library?.resolveName(selectedAddress) | ||
if (resolvedAddress !== address) { | ||
dispatch(updateAddress(address)) | ||
} | ||
} | ||
|
||
verify() | ||
}, [selectedAddress, address, dispatch, library]) | ||
|
||
return ( | ||
<> | ||
{items.length === 1 ? ( | ||
<strong>{tryShortenAddress(address)}</strong> | ||
) : ( | ||
<Wrapper ref={node}> | ||
<AddressInfo onClick={toggle}> | ||
<span style={{ marginRight: '2px' }}>{tryShortenAddress(selectedAddress)}</span> | ||
<ChevronDown size={16} style={{ marginTop: '2px' }} strokeWidth={2.5} /> | ||
</AddressInfo> | ||
{open && ( | ||
<MenuFlyout> | ||
{items.map((item) => ( | ||
<ButtonMenuItem key={item} $selected={item === ''} onClick={() => handleSelectItem(item)}> | ||
<GreenCheck size={16} strokeWidth={2.5} $visible={item === selectedAddress} />{' '} | ||
{tryShortenAddress(item)} | ||
</ButtonMenuItem> | ||
))} | ||
</MenuFlyout> | ||
)} | ||
</Wrapper> | ||
)} | ||
</> | ||
) | ||
} | ||
|
||
const Wrapper = styled.div` | ||
position: relative; | ||
display: inline; | ||
margin-right: 0.4rem; | ||
${({ theme }) => theme.mediaWidth.upToMedium` | ||
justify-self: end; | ||
`}; | ||
${({ theme }) => theme.mediaWidth.upToSmall` | ||
margin: 0 0.5rem 0 0; | ||
width: initial; | ||
text-overflow: ellipsis; | ||
flex-shrink: 1; | ||
`}; | ||
` | ||
|
||
const MenuFlyout = styled.span` | ||
background-color: ${({ theme }) => theme.bg4}; | ||
border: 1px solid ${({ theme }) => theme.bg0}; | ||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), | ||
0px 24px 32px rgba(0, 0, 0, 0.01); | ||
border-radius: 12px; | ||
padding: 0.3rem; | ||
display: flex; | ||
flex-direction: column; | ||
font-size: 1rem; | ||
position: absolute; | ||
left: 0; | ||
top: 1.75rem; | ||
z-index: 100; | ||
min-width: 350px; | ||
${({ theme }) => theme.mediaWidth.upToMedium`; | ||
min-width: 145px | ||
`}; | ||
> { | ||
padding: 12px; | ||
} | ||
` | ||
const MenuItem = css` | ||
align-items: center; | ||
background-color: transparent; | ||
border-radius: 12px; | ||
color: ${({ theme }) => theme.text2}; | ||
cursor: pointer; | ||
display: flex; | ||
flex: 1; | ||
flex-direction: row; | ||
font-size: 16px; | ||
font-weight: 400; | ||
justify-content: start; | ||
:hover { | ||
text-decoration: none; | ||
} | ||
` | ||
|
||
export const AddressInfo = styled.button` | ||
align-items: center; | ||
background-color: ${({ theme }) => theme.bg4}; | ||
border-radius: 12px; | ||
border: 1px solid ${({ theme }) => theme.bg0}; | ||
color: ${({ theme }) => theme.text1}; | ||
display: inline-flex; | ||
flex-direction: row; | ||
font-weight: 700; | ||
font-size: 12px; | ||
height: 100%; | ||
margin: 0 0.4rem; | ||
padding: 0.2rem 0.4rem; | ||
:hover, | ||
:focus { | ||
cursor: pointer; | ||
outline: none; | ||
border: 1px solid ${({ theme }) => theme.bg3}; | ||
} | ||
` | ||
const ButtonMenuItem = styled.button<{ $selected?: boolean }>` | ||
${MenuItem} | ||
cursor: ${({ $selected }) => ($selected ? 'initial' : 'pointer')}; | ||
border: none; | ||
box-shadow: none; | ||
color: ${({ theme, $selected }) => ($selected ? theme.text2 : theme.text1)}; | ||
background-color: ${({ theme, $selected }) => $selected && theme.primary1}; | ||
outline: none; | ||
font-weight: ${({ $selected }) => ($selected ? '700' : '500')}; | ||
font-size: 12px; | ||
text-transform: lowercase; | ||
padding: 6px 10px 6px 5px; | ||
${({ $selected }) => $selected && `margin: 3px 0;`} | ||
> ${AddressInfo} { | ||
margin: 0 auto 0 8px; | ||
} | ||
&:hover { | ||
color: ${({ theme, $selected }) => !$selected && theme.text1}; | ||
background: ${({ theme, $selected }) => !$selected && theme.bg4}; | ||
} | ||
transition: background 0.13s ease-in-out; | ||
` | ||
|
||
const GreenCheck = styled(Check)<{ $visible: boolean }>` | ||
margin-right: 5px; | ||
color: ${({ theme }) => theme.success}; | ||
visibility: ${({ $visible }) => ($visible ? 'visible' : 'hidden')}; | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { SupportedChainId } from 'constants/chains' | ||
import { ClientError, gql, GraphQLClient } from 'graphql-request' | ||
import { EnsNamesQuery } from 'state/data/generated' | ||
|
||
const CHAIN_SUBGRAPH_URL: Record<number, string> = { | ||
[SupportedChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/ensdomains/ens', | ||
[SupportedChainId.RINKEBY]: 'https://api.thegraph.com/subgraphs/name/ensdomains/ensrinkeby', | ||
} | ||
|
||
const DOMAINS_BY_ADDRESS_QUERY = gql` | ||
query ensNames($resolvedAddress: String!) { | ||
domains(where: { resolvedAddress_contains: $resolvedAddress }, orderBy: name) { | ||
name | ||
} | ||
} | ||
` | ||
|
||
export async function ensNames( | ||
chainId: SupportedChainId, | ||
address: string | ||
): Promise< | ||
| { | ||
error: { name: string; message: string; stack: string | undefined } | ||
} | ||
| string[] | ||
> { | ||
try { | ||
const subgraphUrl = chainId ? CHAIN_SUBGRAPH_URL[chainId] : undefined | ||
|
||
if (!subgraphUrl) { | ||
return { | ||
error: { | ||
name: 'UnsupportedChainId', | ||
message: `Subgraph queries against ChainId ${chainId} are not supported.`, | ||
stack: '', | ||
}, | ||
} | ||
} | ||
|
||
const data = await new GraphQLClient(subgraphUrl).request<EnsNamesQuery>(DOMAINS_BY_ADDRESS_QUERY, { | ||
resolvedAddress: address.toLocaleLowerCase(), | ||
}) | ||
|
||
return data.domains | ||
.map((domain) => domain.name) | ||
.filter((domainName): domainName is string => domainName !== null && domainName !== undefined) | ||
} catch (error) { | ||
if (error instanceof ClientError) { | ||
const { name, message, stack } = error | ||
return { error: { name, message, stack } } | ||
} | ||
throw error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.