Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Feature/1708 affiliate link owner info show #2026

Closed
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions src/custom/pages/Profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Txt } from 'assets/styles/styled'
import {
FlexCol,
FlexWrap,
Wrapper,
Container,
Expand All @@ -12,6 +11,7 @@ import {
ChildWrapper,
Loader,
ExtLink,
AffiliateTitle,
ProfileWrapper,
ProfileGridWrap,
} from 'pages/Profile/styled'
Expand All @@ -26,10 +26,12 @@ import { getExplorerAddressLink } from 'utils/explorer'
import useTimeAgo from 'hooks/useTimeAgo'
import { MouseoverTooltipContent } from 'components/Tooltip'
import NotificationBanner from 'components/NotificationBanner'
import { Title } from 'components/Page'
import { SupportedChainId as ChainId } from 'constants/chains'
import AffiliateStatusCheck from 'components/AffiliateStatusCheck'
import { useHasOrders } from 'api/gnosisProtocol/hooks'
import { Title } from 'components/Page'
import { useReferredByAddress } from 'state/affiliate/hooks'
import { shortenAddress } from 'utils'
import { useTokenBalance } from 'state/wallet/hooks'
import { V_COW } from 'constants/tokens'
import VCOWDropdown from './VCOWDropdown'
Expand All @@ -38,6 +40,7 @@ import { IS_CLAIMING_ENABLED } from 'pages/Claim/const'

export default function Profile() {
const referralLink = useReferralLink()
const refAddress = useReferredByAddress()
const { account, chainId } = useActiveWeb3React()
const { profileData, isLoading, error } = useFetchProfile()
const lastUpdated = useTimeAgo(profileData?.lastUpdated)
Expand Down Expand Up @@ -75,7 +78,20 @@ export default function Profile() {
<Wrapper>
<GridWrap>
<CardHead>
<Title>Affiliate Program</Title>
<StyledContainer>
<AffiliateTitle>Affiliate Program</AffiliateTitle>
{account && refAddress.value && (
<div>
<Txt fs={14}>
Referred by:&nbsp;<strong>{refAddress.value && shortenAddress(refAddress.value)}</strong>
</Txt>
<span style={{ display: 'inline-block', verticalAlign: 'middle', marginLeft: 8 }}>
<Copy toCopy={refAddress.value} />
</span>
</div>
)}
</StyledContainer>

{account && (
<Loader isLoading={isLoading}>
<StyledContainer>
Expand All @@ -99,7 +115,7 @@ export default function Profile() {
</Txt>
{hasOrders && (
<ExtLink href={getExplorerAddressLink(chainId || 1, account)}>
<Txt secondary>View all orders ↗</Txt>
<Txt fs={14}>View all orders ↗</Txt>
</ExtLink>
)}
</StyledContainer>
Expand Down Expand Up @@ -135,36 +151,38 @@ export default function Profile() {
<HelpCircle size={14} />
</MouseoverTooltipContent>
</ItemTitle>
<FlexWrap className="item">
<FlexCol>
<FlexWrap xAlign={'center'} className="item">
<FlexWrap col yAlign={'center'}>
<span role="img" aria-label="farmer">
🧑‍🌾
</span>
<Loader isLoading={isLoading}>
<strong>{formatInt(profileData?.totalTrades)}</strong>
<Txt fs={21}>
<strong>{formatInt(profileData?.totalTrades)}</strong>
</Txt>
</Loader>
<Loader isLoading={isLoading}>
<span>
<Txt secondary>
Total trades
{isTradesTooltipVisible && (
<MouseoverTooltipContent content="You may see more trades here than what you see in the activity list. To understand why, check out the FAQ.">
<HelpCircle size={14} />
</MouseoverTooltipContent>
)}
</span>
</Txt>
</Loader>
</FlexCol>
<FlexCol>
</FlexWrap>
<FlexWrap col yAlign={'center'}>
<span role="img" aria-label="moneybag">
💰
</span>
<Loader isLoading={isLoading}>
<strong>{formatDecimal(profileData?.tradeVolumeUsd)}</strong>
</Loader>
<Loader isLoading={isLoading}>
<span>Total traded volume</span>
<Txt secondary>Total traded volume</Txt>
</Loader>
</FlexCol>
</FlexWrap>
</FlexWrap>
</ChildWrapper>
<ChildWrapper>
Expand All @@ -175,28 +193,28 @@ export default function Profile() {
</MouseoverTooltipContent>
</ItemTitle>
<FlexWrap className="item">
<FlexCol>
<FlexWrap yAlign={'center'} col>
<span role="img" aria-label="handshake">
🤝
</span>
<Loader isLoading={isLoading}>
<strong>{formatInt(profileData?.totalReferrals)}</strong>
</Loader>
<Loader isLoading={isLoading}>
<span>Total referrals</span>
<Txt secondary>Total referrals</Txt>
</Loader>
</FlexCol>
<FlexCol>
</FlexWrap>
<FlexWrap yAlign={'center'} col>
<span role="img" aria-label="wingedmoney">
💸
</span>
<Loader isLoading={isLoading}>
<strong>{formatDecimal(profileData?.referralVolumeUsd)}</strong>
</Loader>
<Loader isLoading={isLoading}>
<span>Referrals volume</span>
<Txt secondary>Referrals volume</Txt>
</Loader>
</FlexCol>
</FlexWrap>
</FlexWrap>
</ChildWrapper>
</GridWrap>
Expand Down
45 changes: 30 additions & 15 deletions src/custom/pages/Profile/styled.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled, { css } from 'styled-components/macro'
import Page, { GdocsListStyle } from 'components/Page'
import Page, { GdocsListStyle, Title } from 'components/Page'
import * as CSS from 'csstype'
import { transparentize } from 'polished'
import { ExternalLink } from 'theme'
Expand Down Expand Up @@ -34,12 +34,18 @@ export const Wrapper = styled(Page)`
`

export const ExtLink = styled(ExternalLink)`
color: ${({ theme }) => theme.text1};
font-weight: 300;
&:hover,
&:focus {
color: ${({ theme }) => theme.text1};
}
`

export const AffiliateTitle = styled(Title)`
margin-top: 0;
`

export const ChildWrapper = styled.div`
display: flex;
flex-direction: column;
Expand All @@ -58,6 +64,7 @@ export const ChildWrapper = styled.div`
`}
> .item {
width: 100%;
flex-direction: row;
}
`

Expand All @@ -82,7 +89,12 @@ export const GridWrap = styled.div<Partial<CSS.Properties & { horizontal?: boole
export const CardHead = styled.div`
display: flex;
flex-grow: 1;
flex-wrap: wrap;
flex-direction: column;
${({ theme }) => theme.mediaWidth.upToSmall`
flex-direction: column;
flex-wrap: nowrap;
`}
`

export const StyledTime = styled.p`
Expand All @@ -102,27 +114,30 @@ export const ItemTitle = styled.h3`
`}
`

export const FlexWrap = styled.div`
export const FlexRow = styled.div`
display: inline-flex;
align-items: center;
justify-content: flex-end;
`

export const FlexWrap = styled.div<Partial<CSS.Properties & { xAlign?: string; yAlign?: string; col?: boolean }>>`
display: flex;
flex-grow: 1;
align-items: center;
flex-direction: row;
justify-content: center;
> div {
width: auto;
}
button {
max-width: 180px;
}
flex-direction: ${(props) => (props.col ? 'column' : 'row')};
flex-wrap: ${(props) => (props.col ? 'nowrap' : 'wrap')};
${(props) => props.xAlign && `justify-content: ${props.xAlign};`}
${(props) => props.yAlign && `align-items: ${props.yAlign};`}
${({ theme }) => theme.mediaWidth.upToSmall`
flex-wrap: wrap;
> div {
width: 50%;
}
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
button {
max-width: 100%;
}
`}
button {
max-width: 180px;
}
`

export const StyledContainer = styled.div`
Expand Down
28 changes: 27 additions & 1 deletion src/custom/state/affiliate/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useCallback } from 'react'
import { useCallback, useState, useEffect } from 'react'
import { useActiveWeb3React } from 'hooks/web3'
import { useSelector } from 'react-redux'
import { AppState } from 'state'
import { useAppDispatch } from 'state/hooks'
import { getTrades, getOrder } from 'api/gnosisProtocol/api'
import { updateReferralAddress } from 'state/affiliate/actions'
import { decodeAppData } from 'utils/metadata'
import { APP_DATA_HASH } from 'constants/index'

export function useAppDataHash() {
Expand Down Expand Up @@ -35,3 +38,26 @@ export function useIsNotificationClosed(id?: string): boolean | null {
return id ? state.affiliate.isNotificationClosed?.[id] ?? false : null
})
}

export function useReferredByAddress() {
const [referredAddress, setReferredAddress] = useState<string>('')
const { account, chainId } = useActiveWeb3React()

useEffect(() => {
const fetchReferredAddress = async () => {
if (!chainId || !account) return
try {
const trades = await getTrades({ chainId, owner: account, limit: 1 })
const order = await getOrder(chainId, trades[0]?.orderUid)

const appDataDecoded = order?.appData && (await decodeAppData(order?.appData.toString()))
setReferredAddress(appDataDecoded.metadata.referrer.address)
} catch {
setReferredAddress('')
}
}
fetchReferredAddress()
}, [account, chainId])

return { value: referredAddress }
}
47 changes: 47 additions & 0 deletions src/custom/utils/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,50 @@ export async function uploadMetadataDocToIpfs(appDataDoc: AppDataDoc): Promise<s
const { digest } = multihashes.decode(new CID(IpfsHash).multihash)
return `0x${Buffer.from(digest).toString('hex')}`
}

const fromHexString = (hexString: string) => {
const stringMatch = hexString.match(/.{1,2}/g)
if (!stringMatch) return
return new Uint8Array(stringMatch.map((byte) => parseInt(byte, 16)))
}

function buildCidInstance(hash: string) {
const cidVersion = 0x1 //.toString(16) //cidv1
const codec = 0x70 //.toString(16) //dag-pb
const type = 0x12 //.toString(16) //sha2-256
const length = 32 //.toString(16) //256 bits
const _hash = hash.replace(/(^0x)/, '')

const hexHash = fromHexString(_hash)

if (!hexHash) return

const uint8array = Uint8Array.from([cidVersion, codec, type, length, ...hexHash])

return new CID(uint8array)
}

async function loadIpfsFromCid(cid: string) {
const response = await fetch(`https://gnosis.mypinata.cloud/ipfs/${cid}`)

return await response.json()
}

export async function decodeAppData(hash: string) {
const cid = buildCidInstance(hash)
if (!cid) return
let cidV0
try {
cidV0 = cid.toV0().toString()
console.log(`CIDv0 for hash '${hash}': '${cidV0}'`)
} catch (e) {
console.error(`Not able to extract CIDv0 from hash '${hash}'`, e)
return
}

try {
return await loadIpfsFromCid(cidV0)
} catch (e) {
console.error(`Failed to fetch data from IPFS for CIDv0 ${cidV0} (hash ${hash})`, e)
}
}