Skip to content

Commit 7c1a2bc

Browse files
authored
Feat: earn v2 (#1848)
EarnV2 Released.
1 parent 91e3224 commit 7c1a2bc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2445
-275
lines changed

src/app/earn/components/chart-tabs.tsx renamed to src/app/earn-old/components/chart-tabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Dispatch, SetStateAction } from 'react'
22

3-
import { ChartTab } from '@/app/earn/types'
3+
import { ChartTab } from '@/app/earn-old/types'
44
import { useAnalytics } from '@/lib/hooks/use-analytics'
55
import { cn } from '@/lib/utils/tailwind'
66

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ChevronDownIcon } from '@heroicons/react/20/solid'
2+
import Image from 'next/image'
3+
4+
type TokenSelectorProps = {
5+
selectedToken: { image: string; symbol: string }
6+
onClick: () => void
7+
}
8+
9+
export function TokenSelector({ selectedToken, onClick }: TokenSelectorProps) {
10+
const { image, symbol } = selectedToken
11+
return (
12+
<div
13+
className='flex cursor-pointer flex-row items-center'
14+
onClick={onClick}
15+
>
16+
<Image
17+
alt={`${symbol} logo`}
18+
src={image}
19+
width={24}
20+
height={24}
21+
priority
22+
/>
23+
<span className='text-ic-black dark:text-ic-white ml-2 text-base font-bold'>
24+
{symbol}
25+
</span>
26+
<ChevronDownIcon className='dark:text-ic-white text-ic-black size-5' />
27+
</div>
28+
)
29+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Disclosure } from '@headlessui/react'
2+
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'
3+
4+
import { GasFees } from '@/components/gas-fees'
5+
import { StyledSkeleton } from '@/components/skeleton'
6+
7+
import { useFormattedEarnData } from '../../../use-formatted-data'
8+
9+
type SummaryQuoteProps = {
10+
label: string
11+
value: string
12+
valueUsd: string
13+
}
14+
15+
function SummaryQuote(props: SummaryQuoteProps) {
16+
return (
17+
<div className='text-ic-gray-600 dark:text-ic-gray-300 flex flex-row items-center justify-between text-xs'>
18+
<div className='font-medium'>{props.label}</div>
19+
<div className='flex flex-row gap-1'>
20+
<div className='text-ic-black dark:text-ic-white font-bold'>
21+
{props.value}
22+
</div>
23+
<div className='font-normal'>{props.valueUsd}</div>
24+
</div>
25+
</div>
26+
)
27+
}
28+
29+
export function Summary() {
30+
const {
31+
gasFeesEth,
32+
gasFeesUsd,
33+
inputAmount,
34+
inputAmoutUsd,
35+
isFetchingQuote,
36+
ouputAmount,
37+
outputAmountUsd,
38+
orderFee,
39+
orderFeePercent,
40+
priceImpactPercent,
41+
priceImpactUsd,
42+
shouldShowSummaryDetails,
43+
} = useFormattedEarnData()
44+
45+
if (!shouldShowSummaryDetails && !isFetchingQuote) return null
46+
47+
return (
48+
<Disclosure
49+
as='div'
50+
className='border-ic-gray-100 rounded-lg border dark:border-[#3A6060]'
51+
>
52+
{({ open }) => (
53+
<div className='p-4'>
54+
<dt>
55+
<Disclosure.Button className='text-ic-gray-600 dark:text-ic-gray-300 flex w-full items-center justify-between text-left'>
56+
<span className='text-xs font-medium'>
57+
{open && 'Summary'}
58+
{!open && isFetchingQuote && <StyledSkeleton width={120} />}
59+
{!open &&
60+
!isFetchingQuote &&
61+
shouldShowSummaryDetails &&
62+
`Receive ${ouputAmount}`}
63+
</span>
64+
<div className='flex flex-row items-center gap-1'>
65+
{!open && !isFetchingQuote ? (
66+
<GasFees
67+
valueUsd={gasFeesUsd}
68+
styles={{
69+
valueUsdTextColor:
70+
'text-ic-gray-600 dark:text-ic-gray-300',
71+
}}
72+
/>
73+
) : null}
74+
{!open && isFetchingQuote && <StyledSkeleton width={70} />}
75+
<span className='flex items-center'>
76+
{open ? (
77+
<ChevronUpIcon className='h-6 w-6' aria-hidden='true' />
78+
) : (
79+
<ChevronDownIcon className='h-6 w-6' aria-hidden='true' />
80+
)}
81+
</span>
82+
</div>
83+
</Disclosure.Button>
84+
</dt>
85+
<Disclosure.Panel as='dd' className='mt-2 flex flex-col gap-2'>
86+
{shouldShowSummaryDetails && (
87+
<>
88+
<SummaryQuote
89+
label='Pay'
90+
value={inputAmount}
91+
valueUsd={`(${inputAmoutUsd})`}
92+
/>
93+
<SummaryQuote
94+
label='Receive'
95+
value={ouputAmount}
96+
valueUsd={`(${outputAmountUsd})`}
97+
/>
98+
<SummaryQuote
99+
label='Swap Execution'
100+
value={priceImpactUsd}
101+
valueUsd={priceImpactPercent}
102+
/>
103+
<SummaryQuote
104+
label='Order Fee'
105+
value={orderFee}
106+
valueUsd={`(${orderFeePercent}%)`}
107+
/>
108+
<div className='text-ic-gray-600 dark:text-ic-gray-300 flex flex-row items-center justify-between text-xs'>
109+
<div className='font-normal'>Network Fee</div>
110+
<div>
111+
<GasFees valueUsd={gasFeesUsd} value={gasFeesEth} />
112+
</div>
113+
</div>
114+
</>
115+
)}
116+
</Disclosure.Panel>
117+
</div>
118+
)}
119+
</Disclosure>
120+
)
121+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
'use client'
2+
3+
import { useAtom } from 'jotai'
4+
import { useCallback, useEffect } from 'react'
5+
6+
import { Summary } from '@/app/earn-old/components/earn-widget/components/summary'
7+
import { supportedNetworks } from '@/app/earn-old/constants'
8+
import { useEarnContext } from '@/app/earn-old/provider'
9+
import { useQueryParams } from '@/app/earn-old/use-query-params'
10+
import { tradeMachineAtom } from '@/app/store/trade-machine'
11+
import { Receive } from '@/components/receive'
12+
import { BuySellSelector } from '@/components/selectors/buy-sell-selector'
13+
import { Settings } from '@/components/settings'
14+
import { SmartTradeButton } from '@/components/smart-trade-button'
15+
import { SelectTokenModal } from '@/components/swap/components/select-token-modal'
16+
import { TradeInputSelector } from '@/components/swap/components/trade-input-selector'
17+
import { TransactionReviewModal } from '@/components/swap/components/transaction-review'
18+
import { WarningType } from '@/components/swap/components/warning'
19+
import { TradeButtonState } from '@/components/swap/hooks/use-trade-button-state'
20+
import { TokenDisplay } from '@/components/token-display'
21+
import { useDisclosure } from '@/lib/hooks/use-disclosure'
22+
import { useGasData } from '@/lib/hooks/use-gas-data'
23+
import { useSupportedNetworks } from '@/lib/hooks/use-network'
24+
import { useWallet } from '@/lib/hooks/use-wallet'
25+
import { useSlippage } from '@/lib/providers/slippage'
26+
import { formatWei } from '@/lib/utils'
27+
import { getMaxBalance } from '@/lib/utils/max-balance'
28+
29+
import { useFormattedEarnData } from '../../use-formatted-data'
30+
31+
import './styles.css'
32+
33+
const hiddenLeverageWarnings = [WarningType.flashbots]
34+
35+
export function EarnWidget() {
36+
const gasData = useGasData()
37+
const isSupportedNetwork = useSupportedNetworks(supportedNetworks)
38+
const { queryParams } = useQueryParams()
39+
const { address } = useWallet()
40+
const {
41+
indexToken,
42+
inputToken,
43+
inputTokenAmount,
44+
inputTokens,
45+
inputValue,
46+
isMinting,
47+
outputTokens,
48+
onChangeInputTokenAmount,
49+
onSelectInputToken,
50+
onSelectOutputToken,
51+
outputToken,
52+
reset,
53+
toggleIsMinting,
54+
} = useEarnContext()
55+
56+
const {
57+
contract,
58+
hasInsufficientFunds,
59+
inputAmoutUsd,
60+
inputBalance,
61+
inputBalanceFormatted,
62+
isFetchingQuote,
63+
ouputAmount,
64+
resetData,
65+
} = useFormattedEarnData()
66+
67+
const {
68+
isOpen: isSelectInputTokenOpen,
69+
onOpen: onOpenSelectInputToken,
70+
onClose: onCloseSelectInputToken,
71+
} = useDisclosure()
72+
const {
73+
isOpen: isSelectOutputTokenOpen,
74+
onOpen: onOpenSelectOutputToken,
75+
onClose: onCloseSelectOutputToken,
76+
} = useDisclosure()
77+
78+
const [tradeState, sendTradeEvent] = useAtom(tradeMachineAtom)
79+
80+
const {
81+
auto: autoSlippage,
82+
isAuto: isAutoSlippage,
83+
set: setSlippage,
84+
setSlippageForToken,
85+
slippage,
86+
} = useSlippage()
87+
88+
const onClickBalance = useCallback(() => {
89+
if (!inputBalance) return
90+
const maxBalance = getMaxBalance(inputToken, inputBalance, gasData)
91+
onChangeInputTokenAmount(formatWei(maxBalance, inputToken.decimals))
92+
}, [gasData, inputBalance, inputToken, onChangeInputTokenAmount])
93+
94+
useEffect(() => {
95+
setSlippageForToken(isMinting ? outputToken.symbol : inputToken.symbol)
96+
}, [inputToken, isMinting, outputToken, setSlippageForToken])
97+
98+
useEffect(() => {
99+
if (tradeState.matches('reset')) {
100+
reset()
101+
resetData()
102+
sendTradeEvent({ type: 'RESET_DONE' })
103+
}
104+
}, [tradeState, reset, resetData, sendTradeEvent])
105+
106+
return (
107+
<div className='earn-widget flex h-fit flex-col gap-3 rounded-lg px-4 py-6 lg:ml-auto'>
108+
<div className='flex justify-between'>
109+
<TokenDisplay mini token={indexToken} />
110+
<Settings
111+
isAuto={isAutoSlippage}
112+
isDarkMode={false}
113+
slippage={slippage}
114+
onChangeSlippage={setSlippage}
115+
onClickAuto={autoSlippage}
116+
/>
117+
</div>
118+
<BuySellSelector isMinting={isMinting} onClick={toggleIsMinting} />
119+
<TradeInputSelector
120+
config={{ isReadOnly: false }}
121+
balance={inputBalanceFormatted}
122+
caption='You pay'
123+
formattedFiat={inputAmoutUsd}
124+
selectedToken={inputToken}
125+
selectedTokenAmount={inputValue}
126+
onChangeInput={(_, amount) => onChangeInputTokenAmount(amount)}
127+
onClickBalance={onClickBalance}
128+
onSelectToken={onOpenSelectInputToken}
129+
/>
130+
<Receive
131+
isLoading={isFetchingQuote}
132+
outputAmount={ouputAmount}
133+
selectedOutputToken={outputToken}
134+
onSelectToken={onOpenSelectOutputToken}
135+
/>
136+
<Summary />
137+
<SmartTradeButton
138+
contract={contract ?? ''}
139+
hasFetchingError={false}
140+
hasInsufficientFunds={hasInsufficientFunds}
141+
hiddenWarnings={hiddenLeverageWarnings}
142+
inputTokenAmount={inputTokenAmount}
143+
inputToken={inputToken}
144+
inputValue={inputValue}
145+
isFetchingQuote={isFetchingQuote}
146+
isSupportedNetwork={isSupportedNetwork}
147+
queryNetwork={queryParams.queryNetwork}
148+
outputToken={outputToken}
149+
buttonLabelOverrides={{
150+
[TradeButtonState.default]: 'Review Transaction',
151+
}}
152+
onOpenTransactionReview={() => sendTradeEvent({ type: 'REVIEW' })}
153+
onRefetchQuote={() => {}}
154+
/>
155+
<SelectTokenModal
156+
isOpen={isSelectInputTokenOpen}
157+
onClose={onCloseSelectInputToken}
158+
onSelectedToken={(tokenSymbol, chainId) => {
159+
onSelectInputToken(tokenSymbol, chainId)
160+
onCloseSelectInputToken()
161+
}}
162+
address={address}
163+
tokens={inputTokens}
164+
showNetworks={!isMinting}
165+
/>
166+
<SelectTokenModal
167+
isOpen={isSelectOutputTokenOpen}
168+
onClose={onCloseSelectOutputToken}
169+
onSelectedToken={(tokenSymbol, chainId) => {
170+
onSelectOutputToken(tokenSymbol, chainId)
171+
onCloseSelectOutputToken()
172+
}}
173+
address={address}
174+
tokens={outputTokens}
175+
showNetworks={isMinting}
176+
/>
177+
<TransactionReviewModal
178+
onClose={() => {
179+
reset()
180+
resetData()
181+
sendTradeEvent({ type: 'CLOSE' })
182+
}}
183+
/>
184+
</div>
185+
)
186+
}
File renamed without changes.
File renamed without changes.

src/app/earn/components/quick-stats.tsx renamed to src/app/earn-old/components/quick-stats.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { TokenSelector } from '@/app/earn/components/earn-widget/components/base-token-selector'
2-
import { StatMetric } from '@/app/earn/components/stat-metric'
1+
import { TokenSelector } from '@/app/earn-old/components/earn-widget/components/base-token-selector'
2+
import { StatMetric } from '@/app/earn-old/components/stat-metric'
33
import { formatPercentage, formatTvl } from '@/app/products/utils/formatters'
44
import { SelectTokenModal } from '@/components/swap/components/select-token-modal'
55
import { useDisclosure } from '@/lib/hooks/use-disclosure'
File renamed without changes.

0 commit comments

Comments
 (0)