Skip to content

Commit

Permalink
Validator profile creation (#4428)
Browse files Browse the repository at this point in the history
* Compoenents Created

* RowInline created

* create profile modal complete

* profile modification modal complete

* change BuyMembershipModal to multiTransaction modal

* add Bonding Validator Account Transaction,fix the machine

* fix the bug in machine

* fix the bug

* complete the UI,but did not implement transaction for binding validator account

* lint:fix

* add bondValidatorAcc tx

* update storybook

* fix mistake

* lint fix

* fix storybook

* add storybook for update validator membership

* Revert "fix storybook"

This reverts commit 489d7f6.

* add some happy and failure case

* Metadata to BYTES for UpdateProfile Tx

* buyValidatorMembership flow draft

* update machine, modal flow

* fix machine

* address merge conflicts

* lint --fix

* update MembershipForm, UpdateMembershipForm

* update interaction test

* fix machine self transition condition, correct PlusIcon import

* fix signer, update test

* revert changes on UpdateMembership

* fix

* update app.stories.tsx with validator provider mocking

* Revert "update app.stories.tsx with validator provider mocking"

This reverts commit d3182d2.

* update storybook comment

* update buymembership machine

* update buyMembership machine

* fix

* Fix the duplicated transaction signing

* Improve binding tests

* Change the select validator account function

* add validatorProvider

* check validator account

* Count the membership's root/controller account into the validator membership

* fix

* Skip validator query until it's needed

---------

Co-authored-by: Theophile Sandoz <theophile.sandoz@gmail.com>
  • Loading branch information
eshark9312 and thesan authored Dec 28, 2023
1 parent c31f909 commit 23e07ff
Show file tree
Hide file tree
Showing 18 changed files with 1,091 additions and 122 deletions.
13 changes: 8 additions & 5 deletions packages/ui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { TransactionStatusProvider } from '../src/common/providers/transactionSt
import { MockProvidersDecorator, MockRouterDecorator } from '../src/mocks/providers'
import { i18next } from '../src/services/i18n'
import { KeyringContext } from '../src/common/providers/keyring/context'
import { ValidatorContextProvider } from '../src/validators/providers/provider'
import { Keyring } from '@polkadot/ui-keyring'

configure({ testIdAttribute: 'id' })
Expand Down Expand Up @@ -56,11 +57,13 @@ const ModalDecorator: Decorator = (Story) => (
<TransactionStatusProvider>
<ModalContextProvider>
<OnBoardingProvider>
<Story />
<GlobalModals />
<NotificationsHolder>
<TransactionStatus />
</NotificationsHolder>
<ValidatorContextProvider>
<Story />
<GlobalModals />
<NotificationsHolder>
<TransactionStatus />
</NotificationsHolder>
</ValidatorContextProvider>
</OnBoardingProvider>
</ModalContextProvider>
</TransactionStatusProvider>
Expand Down
545 changes: 540 additions & 5 deletions packages/ui/src/app/App.stories.tsx

Large diffs are not rendered by default.

35 changes: 19 additions & 16 deletions packages/ui/src/app/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OnBoardingProvider } from '@/common/providers/onboarding/provider'
import { ResponsiveProvider } from '@/common/providers/responsive/provider'
import { TransactionStatusProvider } from '@/common/providers/transactionStatus/provider'
import { MembershipContextProvider } from '@/memberships/providers/membership/provider'
import { ValidatorContextProvider } from '@/validators/providers/provider'

import { BackendProvider } from './providers/backend/provider'
import { GlobalStyle } from './providers/GlobalStyle'
Expand All @@ -31,22 +32,24 @@ export const Providers = ({ children }: Props) => (
<TransactionStatusProvider>
<BalancesContextProvider>
<MembershipContextProvider>
<HashRouter>
<RouteActions>
<ModalContextProvider>
<BackendProvider>
<OnBoardingProvider>
<ImageReportProvider>
<ResponsiveProvider>
<GlobalStyle />
{children}
</ResponsiveProvider>
</ImageReportProvider>
</OnBoardingProvider>
</BackendProvider>
</ModalContextProvider>
</RouteActions>
</HashRouter>
<ValidatorContextProvider>
<HashRouter>
<RouteActions>
<ModalContextProvider>
<BackendProvider>
<OnBoardingProvider>
<ImageReportProvider>
<ResponsiveProvider>
<GlobalStyle />
{children}
</ResponsiveProvider>
</ImageReportProvider>
</OnBoardingProvider>
</BackendProvider>
</ModalContextProvider>
</RouteActions>
</HashRouter>
</ValidatorContextProvider>
</MembershipContextProvider>
</BalancesContextProvider>
</TransactionStatusProvider>
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/common/components/Modal/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ export const Row = styled.div`
height: auto;
`

export const RowInline = styled.div<{ gap?: number; top?: number }>`
display: flex;
flex-direction: row;
width: 100%;
height: auto;
align-items: center;
gap: ${({ gap }) => gap ?? 16}px;
margin-top: ${({ top }) => top ?? 0}px;
`

export const AccountRow = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/common/components/forms/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Colors } from '../../constants'
import { TooltipContainer } from '../Tooltip'

interface LabelProps {
noMargin?: boolean
isRequired?: boolean
className?: string
}
Expand All @@ -13,7 +14,7 @@ export const Label = styled.label<LabelProps>`
align-items: center;
align-content: center;
width: fit-content;
margin-bottom: 4px;
margin-bottom: ${({ noMargin }) => (noMargin ? '0px' : '4px')};
font-size: 14px;
line-height: 20px;
font-weight: 700;
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/common/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export * from './ApplicationIcon'
export * from './CouncilMemberIcon'
export * from './VerifiedMemberIcon'
export * from './MenuIcon'
export * from './PlusIcon'
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { ISubmittableResult } from '@polkadot/types/types'
import React from 'react'
import { ActorRef } from 'xstate'

import { Account } from '@/accounts/types'
import { TextMedium } from '@/common/components/typography'
import { SignTransactionModal } from '@/common/modals/SignTransactionModal/SignTransactionModal'

interface SignProps {
transaction: SubmittableExtrinsic<'rxjs', ISubmittableResult> | undefined
signer: Account
service: ActorRef<any>
}

export const AddStakingAccCandidateModal = ({ transaction, signer, service }: SignProps) => (
<SignTransactionModal
buttonText="Sign and Bond"
transaction={transaction}
signer={signer.address}
skipQueryNode
service={service}
useMultiTransaction={{
steps: [
{ title: 'Create Membership' },
{ title: 'Bind Validator Account' },
{ title: 'Confirm Validator Account' },
],
active: 1,
}}
>
<TextMedium>You are intending to bond your validator account with your membership.</TextMedium>
</SignTransactionModal>
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
import { BalanceOf } from '@polkadot/types/interfaces/runtime'
import React, { useEffect, useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import styled from 'styled-components'
import * as Yup from 'yup'

import { SelectAccount } from '@/accounts/components/SelectAccount'
import { SelectAccount, SelectedAccount } from '@/accounts/components/SelectAccount'
import { useMyAccounts } from '@/accounts/hooks/useMyAccounts'
import { accountOrNamed } from '@/accounts/model/accountOrNamed'
import { encodeAddress } from '@/accounts/model/encodeAddress'
import { Account } from '@/accounts/types'
import { TermsRoutes } from '@/app/constants/routes'
import { ButtonGhost, ButtonPrimary } from '@/common/components/buttons'
Expand All @@ -20,27 +22,30 @@ import {
LabelLink,
ToggleCheckbox,
} from '@/common/components/forms'
import { Arrow } from '@/common/components/icons'
import { Arrow, CrossIcon, PlusIcon } from '@/common/components/icons'
import { AlertSymbol } from '@/common/components/icons/symbols'
import { Loading } from '@/common/components/Loading'
import {
ModalFooter,
ModalFooterGroup,
ModalHeader,
Row,
RowInline,
ScrolledModal,
ScrolledModalBody,
ScrolledModalContainer,
TransactionInfoContainer,
} from '@/common/components/Modal'
import { Tooltip, TooltipDefault } from '@/common/components/Tooltip'
import { TransactionInfo } from '@/common/components/TransactionInfo'
import { TextMedium } from '@/common/components/typography'
import { TextMedium, TextSmall } from '@/common/components/typography'
import { definedValues } from '@/common/utils'
import { useYupValidationResolver } from '@/common/utils/validation'
import { AvatarInput } from '@/memberships/components/AvatarInput'
import { SocialMediaSelector } from '@/memberships/components/SocialMediaSelector/SocialMediaSelector'
import { useUploadAvatarAndSubmit } from '@/memberships/hooks/useUploadAvatarAndSubmit'
import { useGetMembersCountQuery } from '@/memberships/queries'
import { useValidators } from '@/validators/hooks/useValidators'

import { SelectMember } from '../../components/SelectMember'
import {
Expand Down Expand Up @@ -76,6 +81,7 @@ const CreateMemberSchema = Yup.object().shape({
),
hasTerms: Yup.boolean().required().oneOf([true]),
isReferred: Yup.boolean(),
isValidator: Yup.boolean(),
referrer: ReferrerSchema,
externalResources: ExternalResourcesSchema,
})
Expand All @@ -88,6 +94,9 @@ export interface MemberFormFields {
about: string
avatarUri: File | string | null
isReferred?: boolean
isValidator?: boolean
validatorAccountCandidate?: Account
validatorAccounts?: Account[]
referrer?: Member
hasTerms?: boolean
invitor?: Member
Expand All @@ -101,6 +110,7 @@ const formDefaultValues = {
about: '',
avatarUri: null,
isReferred: false,
isValidator: false,
referrer: undefined,
hasTerms: false,
externalResources: {},
Expand Down Expand Up @@ -135,7 +145,30 @@ export const BuyMembershipForm = ({
},
})

const [handle, isReferred, referrer, captchaToken] = form.watch(['handle', 'isReferred', 'referrer', 'captchaToken'])
const [handle, isReferred, isValidator, referrer, captchaToken, validatorAccountCandidate] = form.watch([
'handle',
'isReferred',
'isValidator',
'referrer',
'captchaToken',
'validatorAccountCandidate',
])

const { allValidators, allValidatorsWithCtrlAcc } = useValidators({ skip: isValidator ?? true })
const [validatorAccounts, setValidatorAccounts] = useState<Account[]>([])
const validatorAddresses = useMemo(() => {
if (!allValidatorsWithCtrlAcc || !allValidators) return
return (
[...allValidatorsWithCtrlAcc, ...allValidators.map(({ address }) => address)].filter(
(element) => !!element
) as string[]
).map(encodeAddress)
}, [allValidators, allValidatorsWithCtrlAcc])

const isValidValidatorAccount = useMemo(
() => validatorAccountCandidate && validatorAddresses?.includes(encodeAddress(validatorAccountCandidate.address)),
[allValidators, allValidatorsWithCtrlAcc, validatorAddresses, validatorAccountCandidate]
)

useEffect(() => {
if (handle) {
Expand All @@ -149,10 +182,21 @@ export const BuyMembershipForm = ({
}
}, [data?.membershipsConnection.totalCount])

const isFormValid = !isUploading && form.formState.isValid
const isFormValid = !isUploading && form.formState.isValid && (!isValidator || validatorAccounts?.length)
const isDisabled =
type === 'onBoarding' && process.env.REACT_APP_CAPTCHA_SITE_KEY ? !captchaToken || !isFormValid : !isFormValid

const addValidatorAccount = () => {
if (validatorAccountCandidate && isValidValidatorAccount) {
setValidatorAccounts([...new Set([...validatorAccounts, validatorAccountCandidate])])
form?.setValue('validatorAccountCandidate' as keyof MemberFormFields, undefined)
}
}

const removeValidatorAccount = (index: number) => {
setValidatorAccounts([...validatorAccounts.slice(0, index), ...validatorAccounts.slice(index + 1)])
}

return (
<>
<ScrolledModalBody>
Expand Down Expand Up @@ -234,6 +278,79 @@ export const BuyMembershipForm = ({

<SocialMediaSelector />

{type === 'general' && (
<>
<RowInline top={16}>
<Label>I am a validator: </Label>
<ToggleCheckbox trueLabel="Yes" falseLabel="No" name="isValidator" />
</RowInline>
{isValidator && (
<>
<SelectValidatorAccountWrapper>
<RowInline gap={4}>
<Label noMargin>Add validator controller account or validator stash account</Label>
<Tooltip tooltipText="This is the status which indicates the selected account is actually a validator account.">
<TooltipDefault />
</Tooltip>
<TextSmall dark>*</TextSmall>
</RowInline>
<TextMedium dark>
If your validator account is not in your signer wallet, paste the account address to the field
below:
</TextMedium>
<RowInline>
<InputComponent id="select-validatorAccount" inputSize="l">
<SelectAccount
id="select-validatorAccount"
name="validatorAccountCandidate"
filter={(account) => !!validatorAddresses?.includes(encodeAddress(account.address))}
/>
</InputComponent>
<ButtonPrimary
square
size="large"
onClick={addValidatorAccount}
disabled={!isValidValidatorAccount}
className="add-button"
>
<PlusIcon />
</ButtonPrimary>
</RowInline>
{validatorAccountCandidate && !isValidValidatorAccount && (
<RowInline gap={2}>
<TextSmall error>
<InputNotificationIcon>
<AlertSymbol />
</InputNotificationIcon>
</TextSmall>
<TextSmall error>
This account is neither a validator controller account nor a validator stash account.
</TextSmall>
</RowInline>
)}
</SelectValidatorAccountWrapper>

{validatorAccounts.map((account, index) => (
<Row>
<RowInline>
<SelectedAccount account={account as Account} key={'selected' + index} />
<ButtonGhost
square
size="large"
onClick={() => {
removeValidatorAccount(index)
}}
>
<CrossIcon />
</ButtonGhost>
</RowInline>
</Row>
))}
</>
)}
</>
)}

{process.env.REACT_APP_CAPTCHA_SITE_KEY && type === 'onBoarding' && (
<Row>
<HCaptcha
Expand Down Expand Up @@ -287,6 +404,10 @@ export const BuyMembershipForm = ({
<ButtonPrimary
size="medium"
onClick={() => {
validatorAccounts?.map((account, index) => {
form?.register(('validatorAccounts[' + index + ']') as keyof MemberFormFields)
form?.setValue(('validatorAccounts[' + index + ']') as keyof MemberFormFields, account)
})
const values = form.getValues()
uploadAvatarAndSubmit({ ...values, externalResources: { ...definedValues(values.externalResources) } })
}}
Expand All @@ -308,3 +429,25 @@ export const BuyMembershipFormModal = ({ onClose, onSubmit, membershipPrice }: B
</ScrolledModal>
)
}

const SelectValidatorAccountWrapper = styled.div`
margin-top: -4px;
display: flex;
flex-direction: column;
gap: 8px;
`

const InputNotificationIcon = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 12px;
height: 12px;
color: inherit;
padding-right: 2px;
.blackPart,
.primaryPart {
fill: currentColor;
}
`
Loading

0 comments on commit 23e07ff

Please sign in to comment.