-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of stake form #93
Changes from 7 commits
7269487
2fdfae3
e4ccfbe
4c67f66
062cadf
1b1b9ab
4b845b9
a1bdfc7
303f3b2
c697ec8
9984076
4399b2c
8c8d2a1
acda15a
9eba84c
a0c4b71
443c881
d49a39c
99bcdfe
a28a07f
8f397e2
720b567
47c0ca1
96e87fc
cf1eabb
c6b6c7b
39cfbbc
597a890
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React from "react" | ||
import { | ||
ModalBody, | ||
Tabs, | ||
TabList, | ||
Tab, | ||
TabPanels, | ||
TabPanel, | ||
} from "@chakra-ui/react" | ||
import { ModalStep } from "../../../contexts" | ||
import StakeForm from "../Staking/StakeForm" | ||
|
||
const TABS = ["stake", "unstake"] as const | ||
|
||
type FormType = (typeof TABS)[number] | ||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
type ActionFormProps = { defaultForm: FormType } & ModalStep | ||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function ActionForm({ defaultForm, goNext }: ActionFormProps) { | ||
return ( | ||
<ModalBody> | ||
<Tabs | ||
w="100%" | ||
variant="underline" | ||
defaultIndex={TABS.indexOf(defaultForm)} | ||
> | ||
<TabList> | ||
{TABS.map((tab) => ( | ||
<Tab key={tab} w="50%"> | ||
{tab} | ||
</Tab> | ||
))} | ||
</TabList> | ||
<TabPanels> | ||
<TabPanel> | ||
<StakeForm goNext={goNext} /> | ||
</TabPanel> | ||
<TabPanel>{/* TODO: Add form for unstake */}</TabPanel> | ||
</TabPanels> | ||
</Tabs> | ||
</ModalBody> | ||
) | ||
} | ||
|
||
export default ActionForm |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,54 @@ | ||
import React from "react" | ||
import { Button, ModalBody } from "@chakra-ui/react" | ||
import React, { useCallback } from "react" | ||
import { FormikErrors, withFormik } from "formik" | ||
import { ModalStep } from "../../../contexts" | ||
import { TextMd } from "../../shared/Typography" | ||
import FormBase, { FormBaseProps, FormValues } from "../../shared/FormBase" | ||
import { useTransactionContext, useWalletContext } from "../../../hooks" | ||
import { getErrorsObj, validateTokenAmount } from "../../../utils" | ||
|
||
const CUSTOM_DATA = { | ||
buttonText: "Stake", | ||
btcAmountText: "Amount to be staked", | ||
estimatedAmountText: "Approximately staked tokens", | ||
} | ||
|
||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const StakeFormik = withFormik< | ||
{ onSubmitForm: (values: FormValues) => void } & FormBaseProps, | ||
FormValues | ||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
>({ | ||
mapPropsToValues: () => ({ | ||
amount: "", | ||
}), | ||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validate: async ({ amount }, { tokenBalance }) => { | ||
const errors: FormikErrors<FormValues> = {} | ||
|
||
errors.amount = validateTokenAmount(amount, tokenBalance) | ||
|
||
return getErrorsObj(errors) | ||
}, | ||
handleSubmit: (values, { props }) => { | ||
props.onSubmitForm(values) | ||
}, | ||
})(FormBase) | ||
|
||
r-czajkowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function StakeForm({ goNext }: ModalStep) { | ||
const { btcAccount } = useWalletContext() | ||
const { setAmount } = useTransactionContext() | ||
|
||
const handleSubmitForm = useCallback( | ||
(values: FormValues) => { | ||
setAmount(values.amount) | ||
goNext() | ||
}, | ||
[goNext, setAmount], | ||
) | ||
|
||
export default function StakeModal({ goNext }: ModalStep) { | ||
return ( | ||
<ModalBody> | ||
<TextMd>Stake modal</TextMd> | ||
<Button width="100%" onClick={goNext}> | ||
Stake | ||
</Button> | ||
</ModalBody> | ||
<StakeFormik | ||
customData={CUSTOM_DATA} | ||
tokenBalance={btcAccount?.balance.toString() ?? "0"} | ||
onSubmitForm={handleSubmitForm} | ||
/> | ||
) | ||
} | ||
|
||
export default StakeForm |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,17 @@ | ||
import React from "react" | ||
import { useModalFlowContext } from "../../../hooks" | ||
import StakeForm from "./StakeForm" | ||
import Overview from "./Overview" | ||
import ModalBase from "../../shared/ModalBase" | ||
import ActionForm from "../ActionForm" | ||
|
||
function StakingSteps() { | ||
const { activeStep, goNext } = useModalFlowContext() | ||
|
||
switch (activeStep) { | ||
case 1: | ||
return <Overview goNext={goNext} /> | ||
return <ActionForm defaultForm="stake" goNext={goNext} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to figure out how to display the correct steps based on the form type. In this case, if a user changes tab to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great catch! Let's do it as you say in a separate PR. We will have to think about it so I will also create a task to not lose it. #127 |
||
case 2: | ||
return <StakeForm goNext={goNext} /> | ||
return <Overview goNext={goNext} /> | ||
default: | ||
return null | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { chakra } from "@chakra-ui/react" | ||
import { Form as FormikForm } from "formik" | ||
|
||
export const Form = chakra(FormikForm) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from "react" | ||
import { useField } from "formik" | ||
import TokenBalanceInput, { TokenBalanceInputProps } from "../TokenBalanceInput" | ||
|
||
export type FormTokenBalanceInputProps = { | ||
name: string | ||
} & Omit<TokenBalanceInputProps, "setAmount"> | ||
export function FormTokenBalanceInput({ | ||
name, | ||
...restProps | ||
}: FormTokenBalanceInputProps) { | ||
const [field, meta, helpers] = useField(name) | ||
|
||
return ( | ||
<TokenBalanceInput | ||
{...restProps} | ||
{...field} | ||
amount={meta.value} | ||
setAmount={helpers.setValue} | ||
hasError={Boolean(meta.touched && meta.error)} | ||
errorMsgText={meta.error} | ||
/> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./Form" | ||
export * from "./FormTokenBalanceInput" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React from "react" | ||
import { Box, Flex, VStack } from "@chakra-ui/react" | ||
import { useField } from "formik" | ||
import { CurrencyBalanceWithConversion } from "../CurrencyBalanceWithConversion" | ||
import { TextMd } from "../Typography" | ||
|
||
// TODO: Use data from the SDK | ||
function getTransactionDetails(value: string): | ||
| { | ||
btcAmount: string | ||
protocolFee: string | ||
stakedAmount: string | ||
} | ||
| undefined { | ||
const btcAmount = value ?? 0n | ||
const btcAmountInBI = BigInt(btcAmount) | ||
|
||
if (btcAmountInBI <= 0n) return undefined | ||
|
||
const protocolFee = btcAmountInBI / 10000n | ||
const stakedAmount = btcAmountInBI - protocolFee | ||
|
||
return { | ||
btcAmount, | ||
protocolFee: protocolFee.toString(), | ||
stakedAmount: stakedAmount.toString(), | ||
} | ||
} | ||
|
||
function TransactionDetailsItem({ | ||
text, | ||
btcAmount, | ||
usdAmount, | ||
}: { | ||
text: string | ||
btcAmount?: string | number | ||
usdAmount: string | ||
}) { | ||
return ( | ||
<Flex justifyContent="space-between" w="100%"> | ||
<TextMd fontWeight="semibold" color="grey.700"> | ||
{text} | ||
</TextMd> | ||
<Box textAlign="end"> | ||
<CurrencyBalanceWithConversion | ||
from={{ | ||
currencyType: "bitcoin", | ||
amount: btcAmount, | ||
}} | ||
to={{ | ||
currencyType: "usd", | ||
amount: usdAmount, | ||
shouldBeFormatted: false, | ||
size: "sm", | ||
fontWeight: "medium", | ||
color: "grey.500", | ||
}} | ||
/> | ||
</Box> | ||
</Flex> | ||
) | ||
} | ||
|
||
function TransactionDetails({ | ||
fieldName, | ||
btcAmountText, | ||
estimatedAmountText, | ||
}: { | ||
fieldName: string | ||
btcAmountText: string | ||
estimatedAmountText: string | ||
}) { | ||
const [, { value }] = useField(fieldName) | ||
|
||
const details = getTransactionDetails(value) | ||
|
||
return ( | ||
<VStack gap={3} mt={10}> | ||
<TransactionDetailsItem | ||
text={btcAmountText} | ||
btcAmount={details?.btcAmount} | ||
usdAmount="45.725,91" | ||
/> | ||
<TransactionDetailsItem | ||
// TODO: Use protocol fee from the SDK | ||
text="Protocol fee (0.01%)" | ||
btcAmount={details?.protocolFee} | ||
usdAmount="0.024,91" | ||
/> | ||
<TransactionDetailsItem | ||
text={estimatedAmountText} | ||
btcAmount={details?.stakedAmount} | ||
usdAmount="44.762,21" | ||
/> | ||
</VStack> | ||
) | ||
} | ||
|
||
export default TransactionDetails |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React from "react" | ||
import { Button } from "@chakra-ui/react" | ||
import { FormikProps } from "formik" | ||
import { Form, FormTokenBalanceInput } from "../Form" | ||
import { CurrencyType } from "../../../types" | ||
import TransactionDetails from "./TransactionDetails" | ||
|
||
export type FormValues = { | ||
amount: string | ||
} | ||
|
||
export type FormBaseProps = { | ||
customData: { | ||
buttonText: string | ||
btcAmountText: string | ||
estimatedAmountText: string | ||
} | ||
tokenBalance: string | ||
tokenBalanceInputPlaceholder?: string | ||
currencyType?: CurrencyType | ||
fieldName?: string | ||
children?: React.ReactNode | ||
} | ||
|
||
function FormBase({ | ||
customData, | ||
tokenBalance, | ||
tokenBalanceInputPlaceholder = "BTC", | ||
currencyType = "bitcoin", | ||
fieldName = "amount", | ||
children, | ||
...formikProps | ||
}: FormBaseProps & FormikProps<FormValues>) { | ||
return ( | ||
<Form onSubmit={formikProps.handleSubmit}> | ||
<FormTokenBalanceInput | ||
name={fieldName} | ||
tokenBalance={tokenBalance} | ||
placeholder={tokenBalanceInputPlaceholder} | ||
currencyType={currencyType} | ||
/> | ||
<TransactionDetails | ||
fieldName={fieldName} | ||
btcAmountText={customData.btcAmountText} | ||
estimatedAmountText={customData.estimatedAmountText} | ||
/> | ||
{children} | ||
<Button type="submit" size="lg" width="100%" mt={4}> | ||
{customData.buttonText} | ||
</Button> | ||
</Form> | ||
) | ||
} | ||
|
||
export default FormBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the name of this component because at first sight, it may indicate we can render any form but actually we are rendering stake/unstake form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's true this name isn't perfect. But I don't have a better idea. You are right, but on the other hand, we are just displaying the form there. The type of action restricts us on the types of form. If you just have any better idea let me know.