Skip to content

Commit

Permalink
feat: analytic callback handlers (#270)
Browse files Browse the repository at this point in the history
* feat: add initial analytics handlers (#266)

* feat: handlers

* feat: onPermitSign

* feat: onSwapPriceUpdateAck

* feat: onExpandSwapDetails

* fix: trades in ack

* feat: more events

* test: update onAmountChange spy

* fix: one destruct

* fix: update fixture handlers
  • Loading branch information
zzmp authored Oct 11, 2022
1 parent dcad132 commit 5a542fc
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/components/Swap/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function InputWrapper({

const onClickMax = useCallback(() => {
if (!maxAmount) return
updateAmount(maxAmount)
updateAmount(maxAmount, /* origin= */ 'max')
input?.focus()
}, [input, maxAmount, updateAmount])

Expand Down
17 changes: 13 additions & 4 deletions src/components/Swap/Summary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import Row from 'components/Row'
import { PriceImpact } from 'hooks/usePriceImpact'
import { Slippage } from 'hooks/useSlippage'
import { AlertTriangle, BarChart, Info, Spinner } from 'icons'
import { useAtomValue } from 'jotai/utils'
import { useCallback, useMemo, useState } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { swapEventHandlersAtom } from 'state/swap'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
Expand Down Expand Up @@ -94,6 +96,7 @@ function ConfirmButton({
}) {
const [ackPriceImpact, setAckPriceImpact] = useState(false)

const { onSwapPriceUpdateAck, onSubmitSwapClick } = useAtomValue(swapEventHandlersAtom)
const [ackTrade, setAckTrade] = useState(trade)
const doesTradeDiffer = useMemo(
() => Boolean(trade && ackTrade && tradeMeaningfullyDiffers(trade, ackTrade)),
Expand All @@ -103,9 +106,10 @@ function ConfirmButton({
const [isPending, setIsPending] = useState(false)
const onClick = useCallback(async () => {
setIsPending(true)
onSubmitSwapClick?.(trade)
await onConfirm()
setIsPending(false)
}, [onConfirm])
}, [onConfirm, onSubmitSwapClick, trade])

const action = useMemo((): Action | undefined => {
if (isPending) {
Expand All @@ -114,7 +118,10 @@ function ConfirmButton({
return {
message: <Trans>Price updated</Trans>,
icon: BarChart,
onClick: () => setAckTrade(trade),
onClick: () => {
onSwapPriceUpdateAck?.(ackTrade, trade)
setAckTrade(trade)
},
children: <Trans>Accept</Trans>,
}
} else if (highPriceImpact && !ackPriceImpact) {
Expand All @@ -125,7 +132,7 @@ function ConfirmButton({
}
}
return
}, [ackPriceImpact, doesTradeDiffer, highPriceImpact, isPending, trade])
}, [ackPriceImpact, ackTrade, doesTradeDiffer, highPriceImpact, isPending, onSwapPriceUpdateAck, trade])

return (
<ActionButton
Expand Down Expand Up @@ -166,9 +173,11 @@ export function SummaryDialog({
const { inputAmount, outputAmount } = trade

const [open, setOpen] = useState(false)
const { onExpandSwapDetails } = useAtomValue(swapEventHandlersAtom)
const onExpand = useCallback(() => {
onExpandSwapDetails?.()
setOpen((open) => !open)
}, [])
}, [onExpandSwapDetails])

return (
<>
Expand Down
17 changes: 9 additions & 8 deletions src/cosmos/ControlledSwap.fixture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { tokens } from '@uniswap/default-token-list'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { Field, SupportedChainId, SwapWidget } from '@uniswap/widgets'
import Row from 'components/Row'
import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useValue } from 'react-cosmos/fixture'

import { DAI, nativeOnChain, USDC } from '../constants/tokens'
import EventFeed, { Event } from './EventFeed'
import EventFeed, { Event, HANDLERS } from './EventFeed'
import useOption from './useOption'
import useProvider, { INFURA_NETWORK_URLS } from './useProvider'

Expand Down Expand Up @@ -46,6 +46,12 @@ function Fixture() {

const connector = useProvider(SupportedChainId.MAINNET)

const eventHandlers = useMemo(
// eslint-disable-next-line react-hooks/rules-of-hooks
() => HANDLERS.reduce((handlers, name) => ({ ...handlers, [name]: useHandleEvent(name) }), {}),
[useHandleEvent]
)

return (
<Row flex align="start" justify="start" gap={0.5}>
<SwapWidget
Expand All @@ -64,12 +70,7 @@ function Fixture() {
jsonRpcUrlMap={INFURA_NETWORK_URLS}
provider={connector}
tokenList={tokens}
onConnectWalletClick={useHandleEvent('onConnectWalletClick')}
onReviewSwapClick={useHandleEvent('onReviewSwapClick')}
onTokenSelectorClick={useHandleEvent('onTokenSelectorClick')}
onTxSubmit={useHandleEvent('onTxSubmit')}
onTxSuccess={useHandleEvent('onTxSuccess')}
onTxFail={useHandleEvent('onTxFail')}
{...eventHandlers}
/>
<EventFeed events={events} onClear={() => setEvents([])} />
</Row>
Expand Down
21 changes: 20 additions & 1 deletion src/cosmos/EventFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defaultTheme } from '@uniswap/widgets'
import { defaultTheme, SwapEventHandlers, TransactionEventHandlers } from '@uniswap/widgets'
import Row from 'components/Row'
import styled from 'styled-components/macro'
import * as Type from 'theme/type'
Expand All @@ -25,6 +25,25 @@ const EventData = styled.pre`
margin: 0.5em 0 0;
`

export const HANDLERS: (keyof SwapEventHandlers | keyof TransactionEventHandlers)[] = [
'onAmountChange',
'onExpandSwapDetails',
'onInitialSwapQuote',
'onSwapApprove',
'onReviewSwapClick',
'onSettingsReset',
'onSlippageChange',
'onSubmitSwapClick',
'onSwapPriceUpdateAck',
'onSwitchTokens',
'onTokenChange',
'onTokenSelectorClick',
'onTransactionDeadlineChange',
'onTxFail',
'onTxSubmit',
'onTxSuccess',
]

export interface Event {
name: string
data: unknown
Expand Down
17 changes: 9 additions & 8 deletions src/cosmos/Swap.fixture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
} from '@uniswap/widgets'
import Row from 'components/Row'
import { CHAIN_NAMES_TO_IDS } from 'constants/chains'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useValue } from 'react-cosmos/fixture'

import { DAI, USDC_MAINNET } from '../constants/tokens'
import EventFeed, { Event } from './EventFeed'
import EventFeed, { Event, HANDLERS } from './EventFeed'
import useOption from './useOption'
import useProvider, { INFURA_NETWORK_URLS } from './useProvider'

Expand Down Expand Up @@ -88,6 +88,12 @@ function Fixture() {

const [routerUrl] = useValue('routerUrl', { defaultValue: 'https://api.uniswap.org/v1/' })

const eventHandlers = useMemo(
// eslint-disable-next-line react-hooks/rules-of-hooks
() => HANDLERS.reduce((handlers, name) => ({ ...handlers, [name]: useHandleEvent(name) }), {}),
[useHandleEvent]
)

const widget = (
<SwapWidget
convenienceFee={convenienceFee}
Expand All @@ -106,12 +112,7 @@ function Fixture() {
tokenList={tokenList}
width={width}
routerUrl={routerUrl}
onConnectWalletClick={useHandleEvent('onConnectWalletClick')}
onReviewSwapClick={useHandleEvent('onReviewSwapClick')}
onTokenSelectorClick={useHandleEvent('onTokenSelectorClick')}
onTxSubmit={useHandleEvent('onTxSubmit')}
onTxSuccess={useHandleEvent('onTxSuccess')}
onTxFail={useHandleEvent('onTxFail')}
{...eventHandlers}
/>
)

Expand Down
4 changes: 2 additions & 2 deletions src/hooks/swap/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('swap state', () => {
],
}
)
expect(spy).toHaveBeenCalledWith(Field.OUTPUT, '123')
expect(spy).toHaveBeenCalledWith(Field.OUTPUT, '123', undefined)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject({ ...INITIAL_SWAP, amount: '123', type: TradeType.EXACT_OUTPUT })
Expand All @@ -145,7 +145,7 @@ describe('swap state', () => {
],
}
)
expect(spy).toHaveBeenCalledWith(Field.OUTPUT, '123')
expect(spy).toHaveBeenCalledWith(Field.OUTPUT, '123', undefined)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject(INITIAL_SWAP)
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/swap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@ export function useIsAmountPopulated() {
return Boolean(useAtomValue(amountAtom))
}

export function useSwapAmount(field: Field): [string | undefined, (amount: string) => void] {
export function useSwapAmount(field: Field): [string | undefined, (amount: string, origin?: 'max') => void] {
const value = useAtomValue(amountAtom)
const isFieldIndependent = useIsSwapFieldIndependent(field)
const amount = isFieldIndependent ? value : undefined

const { onAmountChange } = useAtomValue(swapEventHandlersAtom)
const setSwap = useUpdateAtom(swapAtom)
const updateAmount = useCallback(
(update: string) => {
(update: string, origin?: 'max') => {
if (update === amount) return
onAmountChange?.(field, update)
onAmountChange?.(field, update, origin)
setSwap((swap) => {
swap.type = toTradeType(field)
swap.amount = update
Expand Down
13 changes: 9 additions & 4 deletions src/hooks/swap/useSwapApproval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { SWAP_ROUTER_ADDRESSES } from 'constants/addresses'
import { ErrorCode } from 'constants/eip1193'
import { useERC20PermitFromTrade, UseERC20PermitState } from 'hooks/useERC20Permit'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useAtomValue } from 'jotai/utils'
import { useCallback, useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { swapEventHandlersAtom } from 'state/swap'

import { ApprovalState, useApproval } from '../useApproval'
export { ApprovalState } from '../useApproval'
Expand Down Expand Up @@ -59,24 +61,27 @@ export const useApproveOrPermit = (
} = useERC20PermitFromTrade(trade, allowedSlippage, deadline)

// If permit is supported, trigger a signature, if not create approval transaction.
const { onSwapApprove } = useAtomValue(swapEventHandlersAtom)
const handleApproveOrPermit = useCallback(async () => {
try {
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
try {
return await gatherPermitSignature()
await gatherPermitSignature()
} catch (error) {
// Try to approve if gatherPermitSignature failed for any reason other than the user rejecting it.
if (error?.code !== ErrorCode.USER_REJECTED_REQUEST) {
return await getApproval()
await getApproval()
}
}
} else {
return await getApproval()
await getApproval()
}
} catch (e) {
// Swallow approval errors - user rejections do not need to be displayed.
return
}
}, [signatureState, gatherPermitSignature, getApproval])
onSwapApprove?.()
}, [onSwapApprove, signatureState, gatherPermitSignature, getApproval])

const approvalState = useMemo(() => {
if (approval === ApprovalState.PENDING) {
Expand Down
15 changes: 12 additions & 3 deletions src/hooks/swap/useSwapInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import useSwitchChain from 'hooks/useSwitchChain'
import { useUSDCValue } from 'hooks/useUSDCPrice'
import useConnectors from 'hooks/web3/useConnectors'
import { useAtomValue } from 'jotai/utils'
import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import { Field, swapAtom } from 'state/swap'
import { Field, swapAtom, swapEventHandlersAtom } from 'state/swap'
import { isExactInput } from 'utils/tradeType'
import tryParseCurrencyAmount from 'utils/tryParseCurrencyAmount'

Expand Down Expand Up @@ -144,13 +144,22 @@ const DEFAULT_SWAP_INFO: SwapInfo = {
const SwapInfoContext = createContext(DEFAULT_SWAP_INFO)

export function SwapInfoProvider({ children, routerUrl }: PropsWithChildren<{ routerUrl?: string }>) {
const swap = useAtomValue(swapAtom)
const swapInfo = useComputeSwapInfo(routerUrl)

const {
error,
trade,
[Field.INPUT]: { currency: currencyIn },
[Field.OUTPUT]: { currency: currencyOut },
} = swapInfo

const { onInitialSwapQuote } = useAtomValue(swapEventHandlersAtom)
useEffect(() => {
if (trade.state === TradeState.VALID && trade.trade) {
onInitialSwapQuote?.(trade.trade)
}
}, [onInitialSwapQuote, swap, trade])

const { connector } = useWeb3React()
const switchChain = useSwitchChain()
const chainIn = currencyIn?.chainId
Expand Down
5 changes: 5 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ export { EMPTY_TOKEN_LIST, UNISWAP_TOKEN_LIST } from 'hooks/useTokenList'
export type { JsonRpcConnectionMap } from 'hooks/web3/useJsonRpcUrlsMap'
export type {
OnAmountChange,
OnExpandSwapDetails,
OnInitialSwapQuote,
OnReviewSwapClick,
OnSettingsReset,
OnSlippageChange,
OnSubmitSwapClick,
OnSwapApprove,
OnSwapPriceUpdateAck,
OnSwitchTokens,
OnTokenChange,
OnTokenSelectorClick,
Expand Down
Loading

1 comment on commit 5a542fc

@vercel
Copy link

@vercel vercel bot commented on 5a542fc Oct 11, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

widgets – ./

widgets-git-main-uniswap.vercel.app
widgets-seven-tau.vercel.app
widgets-uniswap.vercel.app

Please sign in to comment.