Skip to content
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

Gating dApp #767

Merged
merged 22 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
78ccb1a
Add a feature flag `GATING_DAPP_ENABLED`
kkosiorowska Oct 16, 2024
246f78d
Move `HelperErrorText` to separate file
kkosiorowska Oct 16, 2024
1be9c17
Init `GateModal`
kkosiorowska Oct 16, 2024
4e8942b
Set `outline` as a default variant for input component
kkosiorowska Oct 16, 2024
e5fdf3e
Add password form to `GateModal`
kkosiorowska Oct 16, 2024
937feb9
Add a link to the Discord in `GateModal`
kkosiorowska Oct 21, 2024
1618ee8
Add a error message for password form
kkosiorowska Oct 21, 2024
9539b85
Make sure that input is controlled for password form
kkosiorowska Oct 21, 2024
39d4ba6
Open GateModal only for standalone dApp
kkosiorowska Oct 22, 2024
dd1a535
Add access code verification
kkosiorowska Oct 22, 2024
40c38df
Remove unneeded changes after rebase
kkosiorowska Oct 22, 2024
3fed9b4
Save encoded access code in `localStorage`
kkosiorowska Oct 22, 2024
5d76aeb
Separate logic into `useFormField` hook
kkosiorowska Oct 23, 2024
cc94dd7
Validate access code from the `localStorage`
kkosiorowska Oct 23, 2024
1da8272
Add `LoadingModal` to verify access code
kkosiorowska Oct 24, 2024
5e16b52
Merge branch 'main' of github.com:thesis/acre into gating-dapp-ui
kkosiorowska Oct 24, 2024
d0ced38
Handle errors for verify access code
kkosiorowska Oct 24, 2024
3edf103
Update styles for `LoadingModal`
kkosiorowska Oct 24, 2024
72e5bda
Return the boolean result instead of returning the data object
kkosiorowska Oct 24, 2024
26c28a8
Rename from `accessCode` to `encodedCode`
kkosiorowska Oct 24, 2024
60d9039
Add `TODO` comment
kkosiorowska Oct 24, 2024
9d9c120
Merge branch 'main' of github.com:thesis/acre into gating-dapp-ui
kkosiorowska Oct 24, 2024
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
1 change: 1 addition & 0 deletions dapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ VITE_FEATURE_FLAG_XVERSE_WALLET_ENABLED="false"
VITE_FEATURE_FLAG_BEEHIVE_COMPONENT_ENABLED="false"
VITE_FEATURE_FLAG_ACRE_POINTS_ENABLED="true"
VITE_FEATURE_FLAG_TVL_ENABLED="false"
VITE_FEATURE_GATING_DAPP_ENABLED="true"

49 changes: 49 additions & 0 deletions dapp/src/components/GateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useCallback } from "react"
import { Link, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react"
import { TextSm } from "#/components/shared/Typography"
import { EXTERNAL_HREF } from "#/constants"
import { BaseModalProps } from "#/types"
import { useAccessCode } from "#/hooks"
import withBaseModal from "./ModalRoot/withBaseModal"
import PasswordForm from "./shared/PasswordForm"
import { PasswordFormValues } from "./shared/PasswordForm/PasswordFormBase"

export function GateModalBase({ closeModal }: BaseModalProps) {
const { saveAccessCode } = useAccessCode()

const onSubmitForm = useCallback(
(values: PasswordFormValues) => {
const { password } = values
if (!password) return

closeModal()
saveAccessCode(password)
},
[closeModal, saveAccessCode],
)

return (
<>
<ModalHeader>Enter password</ModalHeader>
<ModalBody>
<PasswordForm submitButtonText="Connect" onSubmitForm={onSubmitForm} />
</ModalBody>
<ModalFooter pt={0}>
<TextSm>
Don’t have a password? Contact us on{" "}
<Link
fontWeight="bold"
textDecoration="underline"
href={EXTERNAL_HREF.DISCORD}
isExternal
>
Discord
</Link>
</TextSm>
</ModalFooter>
</>
)
}

const GateModal = withBaseModal(GateModalBase, { closeOnEsc: false })
export default GateModal
26 changes: 26 additions & 0 deletions dapp/src/components/LoadingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react"
import { ModalBody } from "@chakra-ui/react"
import { TextMd } from "#/components/shared/Typography"
import withBaseModal from "./ModalRoot/withBaseModal"
import Spinner from "./shared/Spinner"

export function LoadingModalBase() {
return (
<ModalBody>
<Spinner
size="2xl"
variant="filled"
borderTopColor="gold.100"
borderBottomColor="gold.100"
borderLeftColor="gold.100"
/>
<TextMd fontWeight="semibold">Loading...</TextMd>
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
</ModalBody>
)
}

const LoadingModal = withBaseModal(LoadingModalBase, {
variant: "unstyled",
closeOnEsc: false,
})
export default LoadingModal
4 changes: 4 additions & 0 deletions dapp/src/components/ModalRoot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import MezoBeehiveModal from "../MezoBeehiveModal"
import ConnectWalletModal from "../ConnectWalletModal"
import UnexpectedErrorModal from "../UnexpectedErrorModal"
import AcrePointsClaimModal from "../AcrePointsClaimModal"
import GateModal from "../GateModal"
import LoadingModal from "../LoadingModal"

const MODALS: Record<ModalType, ElementType> = {
STAKE: TransactionModal,
Expand All @@ -16,6 +18,8 @@ const MODALS: Record<ModalType, ElementType> = {
CONNECT_WALLET: ConnectWalletModal,
UNEXPECTED_ERROR: UnexpectedErrorModal,
ACRE_POINTS_CLAIM: AcrePointsClaimModal,
GATE: GateModal,
LOADING: LoadingModal,
} as const

export default function ModalRoot() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
import { H4, TextMd } from "#/components/shared/Typography"
import { numberToLocaleString } from "#/utils"
import { IconChevronDown } from "@tabler/icons-react"
import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { TOKEN_AMOUNT_FIELD_NAME } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { useFormField } from "#/hooks"
import { ONE_MONTH_IN_DAYS, ONE_WEEK_IN_DAYS } from "#/constants"

const ACRE_POINTS_DATA = {
Expand Down Expand Up @@ -44,7 +45,9 @@ function AcrePointsRewardEstimation(props: StackProps) {
),
]

const { value = 0n } = useTokenAmountField()
const { value = 0n } = useFormField<bigint | undefined>(
TOKEN_AMOUNT_FIELD_NAME,
)
const baseReward = Number(value)
const pointsRate = 10000

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import React from "react"
import { List } from "@chakra-ui/react"
import TransactionDetailsAmountItem from "#/components/shared/TransactionDetails/AmountItem"
import FeesDetailsAmountItem from "#/components/shared/FeesDetails/FeesItem"
import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { TOKEN_AMOUNT_FIELD_NAME } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { FeesTooltip } from "#/components/TransactionModal/FeesTooltip"
import { useMinDepositAmount, useTransactionDetails } from "#/hooks"
import {
useFormField,
useMinDepositAmount,
useTransactionDetails,
} from "#/hooks"
import { CurrencyType } from "#/types"
import { DESIRED_DECIMALS_FOR_FEE } from "#/constants"

function StakeDetails({ currency }: { currency: CurrencyType }) {
const { value = 0n } = useTokenAmountField()
const { value = 0n } = useFormField<bigint | undefined>(
TOKEN_AMOUNT_FIELD_NAME,
)
const minDepositAmount = useMinDepositAmount()
const amount = value >= minDepositAmount ? value : 0n
const details = useTransactionDetails(amount)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from "react"
import { Flex, List } from "@chakra-ui/react"
import TransactionDetailsAmountItem from "#/components/shared/TransactionDetails/AmountItem"
import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { useMinWithdrawAmount, useTransactionDetails } from "#/hooks"
import { TOKEN_AMOUNT_FIELD_NAME } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import {
useFormField,
useMinWithdrawAmount,
useTransactionDetails,
} from "#/hooks"
import { ACTION_FLOW_TYPES, CurrencyType } from "#/types"
import { DESIRED_DECIMALS_FOR_FEE, featureFlags } from "#/constants"
import FeesDetailsAmountItem from "#/components/shared/FeesDetails/FeesItem"
Expand All @@ -16,7 +20,9 @@ function UnstakeDetails({
balance: bigint
currency: CurrencyType
}) {
const { value = 0n } = useTokenAmountField()
const { value = 0n } = useFormField<bigint | undefined>(
TOKEN_AMOUNT_FIELD_NAME,
)
const minWithdrawAmount = useMinWithdrawAmount()
const amount = value >= minWithdrawAmount ? value : 0n
const details = useTransactionDetails(amount, ACTION_FLOW_TYPES.UNSTAKE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { CurrencyType } from "#/types"
import { MINIMUM_BALANCE } from "#/constants"
import { formatSatoshiAmount, getCurrencyByType } from "#/utils"
import { TextMd } from "#/components/shared/Typography"
import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { TOKEN_AMOUNT_FIELD_NAME } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { Alert, AlertTitle, AlertIcon } from "#/components/shared/Alert"
import { useFormField } from "#/hooks"

function WithdrawWarning({
balance,
Expand All @@ -14,7 +15,9 @@ function WithdrawWarning({
balance: bigint
currency: CurrencyType
}) {
const { value, isValid } = useTokenAmountField()
const { value, isValid } = useFormField<bigint | undefined>(
TOKEN_AMOUNT_FIELD_NAME,
)
const amount = value ?? 0n

const { symbol } = getCurrencyByType(currency)
Expand Down
39 changes: 39 additions & 0 deletions dapp/src/components/shared/Form/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react"
import { FormControl, FormLabel, Input, InputProps } from "@chakra-ui/react"
import { useFormField } from "#/hooks"
import HelperErrorText from "./HelperErrorText"

export type FormInputProps = {
name: string
label?: string
helperText?: string | JSX.Element
} & Omit<InputProps, "id" | "isInvalid" | "value" | "onChange">

export default function FormInput({
name,
label,
helperText,
...inputProps
}: FormInputProps) {
const { field, value, errorMsgText, hasError, onChange } =
useFormField<string>(name)

return (
<FormControl isInvalid={hasError} isDisabled={inputProps.isDisabled}>
{label && <FormLabel htmlFor={name}>{label}</FormLabel>}
<Input
{...inputProps}
{...field}
id={name}
isInvalid={hasError}
value={value}
onChange={(event) => onChange(event.target.value)}
/>
<HelperErrorText
helperText={helperText}
errorMsgText={errorMsgText}
hasError={hasError}
/>
</FormControl>
)
}
31 changes: 11 additions & 20 deletions dapp/src/components/shared/Form/FormTokenBalanceInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useCallback, useEffect } from "react"
import { useField } from "formik"
import { logPromiseFailure } from "#/utils"
import React, { useEffect } from "react"
import { useFormField } from "#/hooks"
import TokenBalanceInput, { TokenBalanceInputProps } from "../TokenBalanceInput"

export type FormTokenBalanceInputProps = {
Expand All @@ -13,32 +12,24 @@ export function FormTokenBalanceInput({
defaultValue,
...restProps
}: FormTokenBalanceInputProps) {
const [field, meta, helpers] = useField(name)

const setAmount = useCallback(
(value?: bigint) => {
if (!meta.touched) logPromiseFailure(helpers.setTouched(true))
if (meta.error) helpers.setError(undefined)

logPromiseFailure(helpers.setValue(value))
},
[helpers, meta.touched, meta.error],
)
const { field, value, errorMsgText, hasError, onChange } = useFormField<
bigint | undefined
>(name)

useEffect(() => {
if (defaultValue) {
setAmount(defaultValue)
onChange(defaultValue)
}
}, [defaultValue, setAmount])
}, [defaultValue, onChange])

return (
<TokenBalanceInput
{...restProps}
{...field}
amount={defaultValue ?? (meta.value as bigint)}
setAmount={setAmount}
hasError={Boolean(meta.error)}
errorMsgText={meta.error}
amount={defaultValue ?? value}
setAmount={onChange}
hasError={hasError}
errorMsgText={errorMsgText}
/>
)
}
40 changes: 40 additions & 0 deletions dapp/src/components/shared/Form/HelperErrorText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react"
import { FormErrorMessage, FormHelperText, Icon } from "@chakra-ui/react"
import { IconInfoCircle } from "@tabler/icons-react"
import { Alert, AlertIcon, AlertDescription } from "../Alert"

export type HelperErrorTextProps = {
errorMsgText?: string | JSX.Element
hasError?: boolean
helperText?: string | JSX.Element
}

export default function HelperErrorText({
helperText,
errorMsgText,
hasError,
}: HelperErrorTextProps) {
if (hasError) {
return (
<FormErrorMessage>
<Alert status="error">
<AlertIcon status="error" />
<AlertDescription>
{errorMsgText || "Please enter a valid value"}
</AlertDescription>
</Alert>
</FormErrorMessage>
)
}

if (helperText) {
return (
<FormHelperText>
<Icon as={IconInfoCircle} />
{helperText}
</FormHelperText>
)
}

return null
}
30 changes: 30 additions & 0 deletions dapp/src/components/shared/PasswordForm/PasswordFormBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react"
import { FormikProps } from "formik"
import { Form, FormSubmitButton } from "../Form"
import FormInput from "../Form/FormInput"

const PASSWORD_FIELD_NAME = "password"

export type PasswordFormValues = {
[PASSWORD_FIELD_NAME]?: string
}

export type PasswordFormBaseProps = {
formId?: string
label?: string
submitButtonText: string
}

export default function PasswordFormBase({
formId,
label,
submitButtonText,
...formikProps
}: PasswordFormBaseProps & FormikProps<PasswordFormValues>) {
return (
<Form id={formId} onSubmit={formikProps.handleSubmit} w="100%">
<FormInput name={PASSWORD_FIELD_NAME} label={label} type="password" />
<FormSubmitButton mt={10}>{submitButtonText}</FormSubmitButton>
</Form>
)
}
30 changes: 30 additions & 0 deletions dapp/src/components/shared/PasswordForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { FormikErrors, withFormik } from "formik"
import { BaseFormProps } from "#/types"
import { getErrorsObj, validatePassword } from "#/utils"
import PasswordFormBase, {
PasswordFormBaseProps,
PasswordFormValues,
} from "./PasswordFormBase"

type PasswordFormProps = PasswordFormBaseProps &
BaseFormProps<PasswordFormValues>

const PasswordForm = withFormik<PasswordFormProps, PasswordFormValues>({
mapPropsToValues: () => ({
password: "",
}),
validate: async ({ password }) => {
const errors: FormikErrors<PasswordFormValues> = {}

errors.password = await validatePassword(password)

return getErrorsObj(errors)
},
handleSubmit: (values, { props }) => {
props.onSubmitForm(values)
},
validateOnBlur: false,
validateOnChange: false,
})(PasswordFormBase)

export default PasswordForm
Loading
Loading