-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds a ENS address selector to the Profile page #367
Merged
Merged
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
b3769c1
add base ens name selector in Profile
ramirotw f7e62d0
fix styles
alongoni 54b2683
fix codegen schema error
ramirotw e902cb6
Merge remote-tracking branch 'origin/develop' into ramirotw/issue-168…
ramirotw 54dc4a7
handle error for xdai
ramirotw e1b5e33
shorten address in dropdown
ramirotw 3361d68
Merge remote-tracking branch 'origin/develop' into ramirotw/issue-168…
ramirotw 117545f
only show the address selector on mainnet
ramirotw 828b2e1
Fix code style issues with Prettier
lint-action c8a3118
Merge remote-tracking branch 'origin/develop' into ramirotw/issue-168…
ramirotw 0bc1579
handle account change
ramirotw 97dce07
Merge remote-tracking branch 'origin/ramirotw/issue-1682-ens-affiliat…
ramirotw 3837a52
parse ens name from qs
ramirotw 49a1455
Merge remote-tracking branch 'origin/develop' into ramirotw/issue-168…
ramirotw db2b7bb
bump graphql-request
ramirotw 8057369
show full address when there are no ens names
ramirotw 13607b4
disable dropdown if there are no ens names
ramirotw 5c0a805
add rinkeby subgraph
ramirotw 7d529a9
remove dropdown when there's no ens names
ramirotw a804a4c
shorten always referral address
ramirotw 571f64d
prevent returning an invalid state due to a loading variable
ramirotw 1a95e1d
Update src/custom/hooks/useParseReferralQueryParam.ts
ramirotw 93aa746
fix wrong utils path
ramirotw 83092f2
merge useEffects hooks
ramirotw bcca83d
Merge remote-tracking branch 'origin/develop' into ramirotw/issue-168…
ramirotw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,227 @@ | ||
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 (selectedAddress) { | ||
return | ||
} | ||
|
||
dispatch(updateAddress(address)) | ||
}, [selectedAddress, address, dispatch]) | ||
|
||
useEffect(() => { | ||
if (!selectedAddress) { | ||
return | ||
} | ||
|
||
// if the user switches accounts, reset the selected address | ||
if (isAddress(selectedAddress) && selectedAddress !== address) { | ||
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]) | ||
ramirotw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced this change to force the CI script to run the
postinstall
npm script which runs theyarn graphql:generate
script which generates the graphql TS types insrc/state/data/generated.ts
that is needed to import theEnsNamesQuery
type.