Skip to content

Commit 4e38079

Browse files
committed
open position UI
1 parent 6406e4b commit 4e38079

File tree

5 files changed

+253
-3
lines changed

5 files changed

+253
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { OpenPosition } from 'app/features/account/rewards/lock-and-earn/open-position/screen'
2+
import Head from 'next/head'
3+
import { userProtectedGetSSP } from 'utils/userProtected'
4+
import type { NextPageWithLayout } from 'next-app/pages/_app'
5+
import { HomeLayout } from 'app/features/home/layout.web'
6+
import { ButtonOption, TopNav } from 'app/components/TopNav'
7+
8+
export const Page: NextPageWithLayout = () => {
9+
return (
10+
<>
11+
<Head>
12+
<title>Send | Open a Position</title>
13+
</Head>
14+
<OpenPosition />
15+
</>
16+
)
17+
}
18+
19+
export const getServerSideProps = userProtectedGetSSP()
20+
Page.getLayout = (children) => (
21+
<HomeLayout TopNav={<TopNav header="Lock and Earn" button={ButtonOption.PROFILE} />}>
22+
{children}
23+
</HomeLayout>
24+
)
25+
26+
export default Page

packages/app/components/FormFields/CoinField.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ import { useBalance, type UseBalanceReturnType } from 'wagmi'
3030
import { IconCoin } from '../icons/IconCoin'
3131
export const CoinField = ({
3232
native = false,
33+
supportedCoins = [...coins],
3334
...props
34-
}: Pick<SelectProps, 'size' | 'native' | 'defaultValue'>) => {
35+
}: Pick<SelectProps, 'size' | 'native' | 'defaultValue'> & {
36+
supportedCoins?: coin[]
37+
}) => {
3538
const [isOpen, setIsOpen] = useState(false)
3639

3740
const {
@@ -121,7 +124,7 @@ export const CoinField = ({
121124
<XStack als="flex-start" w={320} $sm={{ w: '100%' }} boc={'transparent'} f={1}>
122125
<Select.Group disabled={disabled} space="$0">
123126
{/* <Select.Label>{label}</Select.Label> */}
124-
{coins.map((coin, i) => {
127+
{supportedCoins.map((coin, i) => {
125128
return (
126129
<CoinFieldItem
127130
active={coin.token === field.value}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { Button, Card, H1, Paragraph, XStack, YStack, SubmitButton, Stack } from '@my/ui'
2+
import { z } from 'zod'
3+
import { SchemaForm, formFields } from 'app/utils/SchemaForm'
4+
import { FormProvider, useForm } from 'react-hook-form'
5+
import type { PropsWithChildren } from 'react'
6+
import { coins, coinsDict, sendCoin } from 'app/data/coins'
7+
import { IconCoin } from 'app/components/icons/IconCoin'
8+
import { useSendAccountBalances } from 'app/utils/useSendAccountBalances'
9+
import { formatUnits } from 'viem'
10+
import formatAmount from 'app/utils/formatAmount'
11+
12+
const removeDuplicateInString = (text: string, substring: string) => {
13+
const [first, ...after] = text.split(substring)
14+
return first + (after.length ? `${substring}${after.join('')}` : '')
15+
}
16+
17+
export function OpenPosition() {
18+
return (
19+
<YStack pt={'$size.3.5'} $gtLg={{ pt: 0 }} f={1} $gtMd={{ ml: '$4' }}>
20+
<YStack pb={'$size.3.5'} gap="$size.0.9">
21+
<H1 size={'$9'} fontWeight={'900'} color="$color12" tt={'uppercase'}>
22+
Open a new position
23+
</H1>
24+
<Paragraph color={'$color10'} size={'$5'} maw={650}>
25+
Liquidity Providers get to share 1.5% of all swaps (added to your rewards automatically),
26+
and 5b in $SEND rewards over 12 months.
27+
</Paragraph>
28+
</YStack>
29+
30+
<YStack $gtMd={{ fd: 'row' }} gap={'$size.1.5'}>
31+
<OpenPositionForm />
32+
</YStack>
33+
</YStack>
34+
)
35+
}
36+
37+
const OpenPositionForm = () => {
38+
const form = useForm<z.infer<typeof OpenPositionSchema>>()
39+
const { balances } = useSendAccountBalances()
40+
41+
const formToken = form.watch('token')
42+
const formTokenAmount = form.watch('tokenAmount')
43+
const formSendAmount = form.watch('sendAmount')
44+
45+
const [tokenSymbol, tokenDecimals] = [
46+
coinsDict[formToken]?.symbol,
47+
coinsDict[formToken]?.decimals,
48+
]
49+
50+
const sendBalance = (
51+
balances?.SEND ? formatUnits(balances.SEND, sendCoin.decimals) : 0n
52+
).toString()
53+
const tokenBalance = formatUnits(balances?.[tokenSymbol] ?? 0n, tokenDecimals)
54+
55+
const canSubmit =
56+
formTokenAmount &&
57+
Number(formTokenAmount) > 0 &&
58+
Number(formTokenAmount) <= Number(tokenBalance) &&
59+
formSendAmount &&
60+
Number(formSendAmount) > 0 &&
61+
Number(formSendAmount) <= Number(sendBalance)
62+
63+
return (
64+
<FormProvider {...form}>
65+
<SchemaForm
66+
form={form}
67+
schema={OpenPositionSchema}
68+
onSubmit={() => {}}
69+
props={{
70+
tokenAmount: {
71+
...sharedStyles,
72+
onChangeText: (text) => {
73+
const formattedText = removeDuplicateInString(text, '.').replace(/[^0-9.]/g, '') //remove duplicate "." then filter out any letters
74+
form.setValue('tokenAmount', formattedText)
75+
},
76+
},
77+
sendAmount: {
78+
...sharedStyles,
79+
inputMode: 'numeric',
80+
onChangeText: (text) => {
81+
const formattedText = text.replace(/[^0-9]/g, '') // only accept numbers
82+
form.setValue('sendAmount', formattedText)
83+
},
84+
},
85+
token: {
86+
supportedCoins: coins.filter(({ symbol }) => symbol !== 'SEND'),
87+
},
88+
}}
89+
defaultValues={{
90+
token: 'eth',
91+
}}
92+
formProps={{
93+
testID: 'OpenPositionForm',
94+
$gtSm: { maxWidth: '100%' },
95+
jc: 'flex-start',
96+
als: 'flex-start',
97+
f: 1,
98+
height: '100%',
99+
}}
100+
renderAfter={({ submit }) => (
101+
<XStack mt={'$size.3.5'}>
102+
<SubmitButton
103+
variant={!canSubmit ? 'outlined' : undefined}
104+
theme={canSubmit ? 'green' : 'dim'}
105+
borderRadius={'$4'}
106+
disabled={!canSubmit}
107+
// @TODO: submit
108+
onPress={() => console.log('press')}
109+
>
110+
<Button.Text
111+
tt={'uppercase'}
112+
color={canSubmit ? '$color1' : '$color12'}
113+
fontWeight={500}
114+
ff="$mono"
115+
>
116+
{canSubmit ? 'Provide Liquidity' : 'Enter USDC or SEND Amount'}
117+
</Button.Text>
118+
</SubmitButton>
119+
</XStack>
120+
)}
121+
>
122+
{({ token, tokenAmount, sendAmount }) => (
123+
<YStack $gtMd={{ fd: 'row' }} gap={'$size.1.5'}>
124+
<FormCard
125+
balance={tokenBalance}
126+
onMaxPress={() => form.setValue('tokenAmount', tokenBalance)}
127+
>
128+
<Stack w={'$size.3.5'}>
129+
{coinsDict[formToken] && (
130+
<IconCoin coin={coinsDict[formToken]} size={'$size.3.5'} />
131+
)}
132+
</Stack>
133+
{tokenAmount}
134+
{token}
135+
</FormCard>
136+
137+
<FormCard
138+
balance={sendBalance}
139+
onMaxPress={() => form.setValue('sendAmount', sendBalance)}
140+
>
141+
<Stack>
142+
<IconCoin coin={sendCoin} size={'$size.3.5'} />
143+
</Stack>
144+
{sendAmount}
145+
<Paragraph fontWeight={600} fontSize={'$8'}>
146+
SEND
147+
</Paragraph>
148+
</FormCard>
149+
</YStack>
150+
)}
151+
</SchemaForm>
152+
</FormProvider>
153+
)
154+
}
155+
156+
const OpenPositionSchema = z.object({
157+
token: formFields.coin,
158+
tokenAmount: formFields.text,
159+
sendAmount: formFields.text,
160+
})
161+
162+
const FormCard = ({
163+
balance,
164+
onMaxPress,
165+
children,
166+
}: {
167+
balance: string
168+
onMaxPress: () => void
169+
} & PropsWithChildren) => {
170+
return (
171+
<Card p={'$size.3.5'} flex={1}>
172+
<XStack
173+
pb="$size.0.9"
174+
borderBottomColor={'$color10'}
175+
borderBottomWidth={1}
176+
justifyContent="space-between"
177+
alignItems="center"
178+
gap="$size.0.9"
179+
>
180+
{children}
181+
</XStack>
182+
183+
<XStack pt="$size.0.9" justifyContent="space-between">
184+
<XStack gap="$size.0.75">
185+
<Paragraph color="$color10" size={'$5'}>
186+
Balance:
187+
</Paragraph>
188+
<Paragraph size={'$5'} fontWeight={'600'}>
189+
{formatAmount(balance, 9, 2)}
190+
</Paragraph>
191+
</XStack>
192+
<Button unstyled onPress={onMaxPress}>
193+
<Button.Text size={'$5'} color="$color10" textDecorationLine="underline">
194+
MAX
195+
</Button.Text>
196+
</Button>
197+
</XStack>
198+
</Card>
199+
)
200+
}
201+
202+
const sharedStyles = {
203+
fieldsetProps: { flex: 1 },
204+
backgroundColor: '$color1',
205+
flex: 1,
206+
inputMode: 'decimal',
207+
fontSize: '$9',
208+
ff: '$mono',
209+
placeholder: '0',
210+
p: 0,
211+
lineHeight: '$1',
212+
borderColor: 'transparent',
213+
outlineColor: 'transparent',
214+
fontStyle: 'normal',
215+
'$theme-dark': {
216+
placeholderTextColor: '$color10',
217+
},
218+
$sm: {
219+
w: '100%',
220+
},
221+
} as const

packages/app/features/account/rewards/lock-and-earn/screen.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ const OpenPosition = () => {
429429
>
430430
<YStack jc={'space-between'} h={'100%'}>
431431
<LinkableButton
432-
href="/"
432+
href="/account/rewards/lock-and-earn/open-position"
433433
theme={'green'}
434434
variant="outlined"
435435
$theme-light={{ borderColor: '$color12' }}

0 commit comments

Comments
 (0)