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 all 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
104 changes: 86 additions & 18 deletions src/custom/api/gnosisProtocol/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { SupportedChainId as ChainId, SupportedChainId } from 'constants/chains'
import {
SupportedChainId as ChainId,
SupportedChainId,
BARN_API_ENDPOINTS_PER_NETWORK,
PROD_API_ENDPOINTS_PER_NETWORK,
} from 'constants/chains'
import { OrderKind, QuoteQuery } from '@gnosis.pm/gp-v2-contracts'
import { stringify } from 'qs'
import { getSigningSchemeApiValue, OrderCancellation, OrderCreation, SigningSchemeValue } from 'utils/signatures'
Expand Down Expand Up @@ -26,20 +31,10 @@ import { getAppDataHash } from 'constants/appDataHash'
import { GpPriceStrategy } from 'hooks/useGetGpPriceStrategy'

function getGnosisProtocolUrl(): Partial<Record<ChainId, string>> {
if (isLocal || isDev || isPr || isBarn) {
return {
[ChainId.MAINNET]: process.env.REACT_APP_API_URL_STAGING_MAINNET || 'https://barn.api.cow.fi/mainnet/api',
[ChainId.RINKEBY]: process.env.REACT_APP_API_URL_STAGING_RINKEBY || 'https://barn.api.cow.fi/rinkeby/api',
[ChainId.XDAI]: process.env.REACT_APP_API_URL_STAGING_XDAI || 'https://barn.api.cow.fi/xdai/api',
}
}
if (isLocal || isDev || isPr || isBarn) return BARN_API_ENDPOINTS_PER_NETWORK

// Production, staging, ens, ...
return {
[ChainId.MAINNET]: process.env.REACT_APP_API_URL_PROD_MAINNET || 'https://api.cow.fi/mainnet/api',
[ChainId.RINKEBY]: process.env.REACT_APP_API_URL_PROD_RINKEBY || 'https://api.cow.fi/rinkeby/api',
[ChainId.XDAI]: process.env.REACT_APP_API_URL_PROD_XDAI || 'https://api.cow.fi/xdai/api',
}
return PROD_API_ENDPOINTS_PER_NETWORK
}

function getProfileUrl(): Partial<Record<ChainId, string>> {
Expand Down Expand Up @@ -171,9 +166,15 @@ export function getOrderLink(chainId: ChainId, orderId: OrderID): string {
return baseUrl + `/orders/${orderId}`
}

function _fetch(chainId: ChainId, url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
const baseUrl = _getApiBaseUrl(chainId)
return fetch(baseUrl + url, {
function _fetch(
chainId: ChainId,
url: string,
method: 'GET' | 'POST' | 'DELETE',
baseUrl?: string,
data?: any
): Promise<Response> {
const baseUri = baseUrl ? baseUrl + '/v1' : _getApiBaseUrl(chainId)
return fetch(baseUri + url, {
headers: DEFAULT_HEADERS,
method,
body: data !== undefined ? JSON.stringify(data) : data,
Expand Down Expand Up @@ -203,8 +204,8 @@ function _post(chainId: ChainId, url: string, data: any): Promise<Response> {
return _fetch(chainId, url, 'POST', data)
}

function _get(chainId: ChainId, url: string): Promise<Response> {
return _fetch(chainId, url, 'GET')
function _get(chainId: ChainId, url: string, baseurl?: string): Promise<Response> {
return _fetch(chainId, url, 'GET', baseurl)
}

function _getProfile(chainId: ChainId, url: string): Promise<Response> {
Expand Down Expand Up @@ -382,6 +383,36 @@ export async function getOrder(chainId: ChainId, orderId: string): Promise<Order
}
}

export async function getOrderFromAllEnvs(chainId: ChainId, orderId: string): Promise<OrderMetaData | null> {
console.log(`[api:${API_NAME}] Get order from all envs for `, chainId, orderId)
try {
const results = await Promise.allSettled([
await _get(chainId, `/orders/${orderId}`, BARN_API_ENDPOINTS_PER_NETWORK[chainId]),
await _get(chainId, `/orders/${orderId}`, PROD_API_ENDPOINTS_PER_NETWORK[chainId]),
])

const response = results
.filter(({ status }) => status === 'fulfilled')
.filter((res) => (res as PromiseFulfilledResult<any>).value.ok)
.map((p) => (p as PromiseFulfilledResult<any>).value)

const [stgResponse, prodResponse] = response

if (!response.length) {
const [stgErrResponse, prodErrResponse] = results as PromiseFulfilledResult<any>[]
const errorResponse: ApiErrorObject = (await stgErrResponse.value.json()) || (await prodErrResponse.value.json())
throw new OperatorError(errorResponse)
} else {
return Promise.allSettled([stgResponse?.json(), prodResponse?.json()]).then(
(r: any) => r.find((t: any) => t.value)?.value as OrderMetaData
)
}
} catch (error) {
console.error('Error getting all order information:', error)
throw new OperatorError(UNHANDLED_ORDER_ERROR)
}
}

export async function getOrders(chainId: ChainId, owner: string, limit = 1000, offset = 0): Promise<OrderMetaData[]> {
console.log(`[api:${API_NAME}] Get orders for `, chainId, owner, limit, offset)

Expand Down Expand Up @@ -426,6 +457,41 @@ export async function getTrades(params: GetTradesParams): Promise<TradeMetaData[
}
}

export async function getTradesFromAllEnvs(params: GetTradesParams): Promise<TradeMetaData[]> {
const { chainId, owner, limit, offset } = params
const qsParams = stringify({ owner, limit, offset })
console.log('[util:operator] Get all trades for', chainId, owner, { limit, offset })
try {
const results = await Promise.allSettled([
await _get(chainId, `/trades?${qsParams}`, BARN_API_ENDPOINTS_PER_NETWORK[chainId]),
await _get(chainId, `/trades?${qsParams}`, PROD_API_ENDPOINTS_PER_NETWORK[chainId]),
])

const response = results
.filter(({ status }) => status === 'fulfilled')
.filter((res) => (res as PromiseFulfilledResult<any>).value.ok)
.map((p) => (p as PromiseFulfilledResult<any>).value)

const [stgResponse, prodResponse] = response

if (!response.length) {
const [stgErrResponse, prodErrResponse] = results as PromiseFulfilledResult<any>[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment for both fns: there seems to be a lot of anys. Don't we know the types returned?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a problem with Promise.allSettled and typing (https://stackoverflow.com/a/67533949) . I tried the solution suggested there but I was not able to make it work with our current eslint configuration so I have to type those cases with any

const errorResponse = (await stgErrResponse.value.json()) || (await prodErrResponse.value.json())
throw new Error(errorResponse.description)
} else {
return Promise.allSettled([stgResponse.json(), prodResponse.json()]).then((r) =>
r
.map((t: any) => t.value)
.flat()
.sort((a, b) => a.blockNumber - b.blockNumber)
)
}
} catch (error) {
console.error('Error getting all trades:', error)
throw new Error('Error getting all trades: ' + error)
}
}

export type ProfileData = {
totalTrades: number
totalReferrals: number
Expand Down Expand Up @@ -515,7 +581,9 @@ registerOnWindow({
operator: {
getQuote,
getTrades,
getTradesFromAllEnvs,
getOrder,
getOrderFromAllEnvs,
sendSignedOrder: sendOrder,
apiGet: _get,
apiPost: _post,
Expand Down
11 changes: 11 additions & 0 deletions src/custom/constants/chains/chainsMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,14 @@ export const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string }
// [SupportedChainId.ARBITRUM_KOVAN]: 'kArbitrum',
// [SupportedChainId.ARBITRUM_ONE]: 'Arbitrum One',
}

export const BARN_API_ENDPOINTS_PER_NETWORK: Record<SupportedChainId, string> = {
[SupportedChainId.MAINNET]: process.env.REACT_APP_API_URL_STAGING_MAINNET || 'https://barn.api.cow.fi/mainnet/api',
[SupportedChainId.RINKEBY]: process.env.REACT_APP_API_URL_STAGING_RINKEBY || 'https://barn.api.cow.fi/rinkeby/api',
[SupportedChainId.XDAI]: process.env.REACT_APP_API_URL_STAGING_XDAI || 'https://barn.api.cow.fi/xdai/api',
}
export const PROD_API_ENDPOINTS_PER_NETWORK: Record<SupportedChainId, string> = {
[SupportedChainId.MAINNET]: process.env.REACT_APP_API_URL_PROD_MAINNET || 'https://api.cow.fi/mainnet/api',
[SupportedChainId.RINKEBY]: process.env.REACT_APP_API_URL_PROD_RINKEBY || 'https://api.cow.fi/rinkeby/api',
[SupportedChainId.XDAI]: process.env.REACT_APP_API_URL_PROD_XDAI || 'https://api.cow.fi/xdai/api',
}
72 changes: 49 additions & 23 deletions src/custom/pages/Profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useContext } from 'react'
import { ThemeContext } from 'styled-components/macro'
import { Txt } from 'assets/styles/styled'

import {
FlexCol,
FlexWrap,
Wrapper,
Container,
Expand All @@ -12,6 +14,7 @@ import {
ChildWrapper,
Loader,
ExtLink,
AffiliateTitle,
ProfileWrapper,
ProfileGridWrap,
} from 'pages/Profile/styled'
Expand All @@ -26,10 +29,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,11 +43,13 @@ 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)
const isTradesTooltipVisible = account && chainId == 1 && !!profileData?.totalTrades
const hasOrders = useHasOrders(account)
const theme = useContext(ThemeContext)

const vCowBalance = useTokenBalance(account || undefined, chainId ? V_COW[chainId] : undefined)

Expand Down Expand Up @@ -75,31 +82,48 @@ 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>
<Txt>
<RefreshCcw size={16} />
<RefreshCcw color={theme.text1} size={14} />
&nbsp;&nbsp;
<Txt secondary>
<Txt fs={14}>
Last updated
<MouseoverTooltipContent content="Data is updated on the background periodically.">
<HelpCircle size={14} />
</MouseoverTooltipContent>
:&nbsp;
:
</Txt>
{!lastUpdated ? (
'-'
) : (
<MouseoverTooltipContent content={<TimeFormatted date={profileData?.lastUpdated} />}>
<strong>{lastUpdated}</strong>
<Txt fs={14}>
<strong>{lastUpdated}</strong>
</Txt>
</MouseoverTooltipContent>
)}
</Txt>
{hasOrders && (
<ExtLink href={getExplorerAddressLink(chainId || 1, account)}>
<Txt secondary>View all orders ↗</Txt>
<Txt style={{ fontWeight: 500 }} fs={14}>
View all orders ↗
</Txt>
</ExtLink>
)}
</StyledContainer>
Expand Down Expand Up @@ -135,36 +159,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 +201,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
Loading