-
Notifications
You must be signed in to change notification settings - Fork 70
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
Funding Request Proposal for Multiple Recipients #2365 #4453
Funding Request Proposal for Multiple Recipients #2365 #4453
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
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.
This looks great and works perfectly up to proposal creation: For some reason tx fails with
proposalsCodex.InvalidFundingRequestProposalBalance
Is the total amount submitted correctly?
Editor
Validation
Preview
@traumschule I forgot to add in the new parameters that support multiple funding requests. My bad!. |
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.
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.
@vrrayz thank you for this PR and sorry it took me so long to finally get to review it. You did a really nice work with this proposal that's really not an easy one !
However there is a bunch of changes I believe this feature needs. In addition to those change requests there is the main reason it took me so long to get to this PR: which is that this proposal should get at least one automated test. Please add a new "Specific Parameters Multiple Recipients Funding Request" test story right after this one: https://pioneer-2-storybook-joystream.vercel.app/?path=/story/pages-proposals-proposallist-current--specific-parameters-funding-request
.test(duplicateAccounts('Duplicate accounts are not allowed')) | ||
.test(isValidCSV('Not valid CSV format')) | ||
.test( | ||
maxFundingAmount( | ||
'Maximal amount allowed is ${max}', | ||
api?.consts.proposalsCodex.fundingRequestProposalMaxTotalAmount | ||
) | ||
) | ||
.test( | ||
maxAccounts( | ||
'Maximum allowed accounts is ${max}', | ||
api?.consts.proposalsCodex.fundingRequestProposalMaxAccounts.toNumber() | ||
) | ||
) |
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.
If I understand the design correctly, all these validations except for isValidCSV
should be shown in the PreviewAndValidateModal
for clarity. So the form validation should only check whether the csv format is correct.
.test(duplicateAccounts('Duplicate accounts are not allowed')) | |
.test(isValidCSV('Not valid CSV format')) | |
.test( | |
maxFundingAmount( | |
'Maximal amount allowed is ${max}', | |
api?.consts.proposalsCodex.fundingRequestProposalMaxTotalAmount | |
) | |
) | |
.test( | |
maxAccounts( | |
'Maximum allowed accounts is ${max}', | |
api?.consts.proposalsCodex.fundingRequestProposalMaxAccounts.toNumber() | |
) | |
) | |
.test(isValidCSV('Not valid CSV format')) |
is: false, | ||
then: (schema) => | ||
schema | ||
.test(moreThanMixed(0, '')) // todo: change funding request to allow upload request in file |
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.
This comment can be removed now:
.test(moreThanMixed(0, '')) // todo: change funding request to allow upload request in file | |
.test(moreThanMixed(0, '')) |
const decimals = new BN(10).pow(new BN(JOY_DECIMAL_PLACES)) | ||
return input?.split(';\n').map((item) => { | ||
const [address, amount] = item.split(',') | ||
const formattedAmount = new BN(amount).mul(decimals) |
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.
This doesn't work when the amount has a decimal part because bn.js
doesn't work with decimals see:
> new BN('1.5').muln(2).toNumber()
2
There is already the joy
helper function for this:
const formattedAmount = new BN(amount).mul(decimals) | |
const formattedAmount = new BN(joy(amount)) |
setErrorMessages((prev) => | ||
totalInvalidAccounts > 0 ? [...prev, 'Incorrect destination accounts detected'] : [...prev] | ||
) | ||
setErrorMessages((prev) => | ||
maxAllowedAccounts && previewAccounts?.length > maxAllowedAccounts | ||
? [...prev, 'Maximum allowed accounts exceeded'] | ||
: [...prev] | ||
) |
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.
Please avoid calling set state dispatchers when no data change is needed because it creates useless re-renders. Instead just call them conditionally:
setErrorMessages((prev) => | |
totalInvalidAccounts > 0 ? [...prev, 'Incorrect destination accounts detected'] : [...prev] | |
) | |
setErrorMessages((prev) => | |
maxAllowedAccounts && previewAccounts?.length > maxAllowedAccounts | |
? [...prev, 'Maximum allowed accounts exceeded'] | |
: [...prev] | |
) | |
if (totalInvalidAccounts > 0) { | |
setErrorMessages((prev) => [...prev, 'Incorrect destination accounts detected']) | |
} | |
if (maxAllowedAccounts && previewAccounts?.length > maxAllowedAccounts) { | |
setErrorMessages((prev) => [...prev, 'Maximum allowed accounts exceeded']) | |
} |
Also are you sure calling these 2 setErrorMessages
in the same useEffect
works ? meaning the prev
value gets updated between the 2 calls ? It probably does but I have a small doubt. Just in case you haven't tested that part already.
setIsPreviewModalShown, | ||
previewModalData, | ||
setValue, |
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.
You shouldn't need to pass these as props. I think you can access these with:
const { setValue, getValues } = useFormContext()
just like in the FundingRequest
form.
const verifyInput = useCallback((input: string) => { | ||
setValue('fundingRequest.hasPreviewedInput', false, { shouldValidate: true }) | ||
setValue('fundingRequest.accountsAndAmounts', input, { shouldValidate: true }) | ||
}, []) |
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.
This looks like a dangerous hack to me. Instead please watch the csv value (currently accountsAndAmounts
) and reset what should be reset (currently hasPreviewedInput
) in a useEffect
.
So with the current accountsAndAmounts
and hasPreviewedInput
it would be something like:
const accountsAndAmounts = watch('accountsAndAmounts')
useEffect(() => {
if (getFieldState('hasPreviewedInput')) {
setData('fundingRequest.hasPreviewedInput', false, { shouldValidate: true })
}
}, [accountsAndAmounts])
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.
Thank you, the tests are perfect !
There is just one last thing to change in term of UX:
When the csv field is empty or invalid please:
- hide the "Please preview and validate the inputs to proceed" message
- disable the "Preview and Validate" button
Also I just realized that a followup to this PR will be needed because ATM the preview page only shows 1 of the payments. I setup a temporary PR to visualize this case in storybook: https://pioneer-2-storybook-git-fork-thesan-feature-mu-ca321d-joystream.vercel.app/?path=/story/pages-proposals-proposalpreview--funding-request-multiple-recipients
So I'll retarget this PR to a new branch.
customModalSize?: any | ||
marginRight?: any |
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.
I just realized that the changes to this file are now unnecessary. Please run: git checkout dev -- packages/ui/src/common/components/Modal/Modal.tsx
to reverse these changes.
@@ -24,6 +24,7 @@ import { TextMedium, TokenValue } from '@/common/components/typography' | |||
import { BN_ZERO } from '@/common/constants' | |||
import { camelCaseToText } from '@/common/helpers' | |||
import { useCurrentBlockNumber } from '@/common/hooks/useCurrentBlockNumber' | |||
import { useKeyring } from '@/common/hooks/useKeyring' |
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.
Same thing for this file since the validation doesn't happens in the useYupValidationResolver
anymore
export const maxAccounts = (message: string, max: number | undefined): Yup.TestConfig<any, AnyObject> => ({ | ||
message, | ||
name: 'maxAccounts', | ||
params: { max }, | ||
exclusive: false, | ||
test(value: string) { | ||
const pairs = value.split('\n') | ||
return max ? pairs.length <= max : false | ||
}, | ||
}) | ||
|
||
export const duplicateAccounts = (message: string): Yup.TestConfig<any, AnyObject> => ({ | ||
message, | ||
name: 'duplicateAccounts', | ||
exclusive: false, | ||
test(value: string) { | ||
const pairs = value.split('\n') | ||
const addresses: string[] = [] | ||
|
||
for (const pair of pairs) { | ||
const [address] = pair.split(',') | ||
if (addresses.indexOf(address) >= 0) return false | ||
addresses.push(address) | ||
} | ||
|
||
return true | ||
}, | ||
}) | ||
|
||
export const isValidCSV = (message: string): Yup.TestConfig<any, AnyObject> => ({ | ||
message, | ||
name: 'isValidCSV', | ||
exclusive: false, | ||
test(value: string) { | ||
if (!CSV_PATTERN.test(value)) return false | ||
|
||
const pairs = value.split('\n') | ||
|
||
for (const pair of pairs) { | ||
const [, amount] = pair.split(',') | ||
if (!Number(amount)) return false | ||
} | ||
|
||
return true | ||
}, | ||
}) | ||
|
||
export const maxFundingAmount = ( | ||
message: string, | ||
max: Reference<number | BN> | number | BN | undefined, | ||
isJoyValue = true | ||
): Yup.TestConfig<any, AnyObject> => ({ | ||
message, | ||
name: 'maxFundingAmount', | ||
params: { max: isJoyValue && isBn(max) ? formatJoyValue(max, { precision: 2 }) : max }, | ||
exclusive: false, | ||
test(value: string) { | ||
const pairs = value.split('\n') | ||
let total = new BN(0) | ||
const decimals = new BN(10).pow(new BN(JOY_DECIMAL_PLACES)) | ||
for (const pair of pairs) { | ||
const [, amount] = pair.split(',') | ||
total = total.add(new BN(amount)) | ||
} | ||
total = total.mul(decimals) | ||
return !total || typeof max === 'undefined' || !isBn(total) || total.lte(new BN(this.resolve(max))) | ||
}, | ||
}) |
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.
Except for isValidCSV
are any of these functions still in use ? If not please remove them
const shouldDisablePreviewButton = () => { | ||
const input = getFieldState('fundingRequest.csvInput') | ||
return input.error !== undefined || !input.isDirty | ||
} |
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.
Since this function is called on every render, it would be simpler to just have a variable:
const shouldDisablePreviewButton = () => { | |
const input = getFieldState('fundingRequest.csvInput') | |
return input.error !== undefined || !input.isDirty | |
} | |
const canPreviewInput = csvInput.isDirty && !csvInput.error |
{shouldPreview() && <ErrorPrompt>Please preview and validate the inputs to proceed</ErrorPrompt>} | ||
<ButtonPrimary | ||
size="medium" | ||
disabled={shouldDisablePreviewButton()} |
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.
So this becomes:
disabled={shouldDisablePreviewButton()} | |
disabled={!canPreviewInput} |
/> | ||
</InputComponent> | ||
<HiddenCheckBox name="fundingRequest.hasPreviewedInput" checked={hasPreviewedInput} /> | ||
{shouldPreview() && <ErrorPrompt>Please preview and validate the inputs to proceed</ErrorPrompt>} |
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.
And this:
{shouldPreview() && <ErrorPrompt>Please preview and validate the inputs to proceed</ErrorPrompt>} | |
{canPreviewInput && !hasPreviewedInput && <ErrorPrompt>Please preview and validate the inputs to proceed</ErrorPrompt>} |
I might have missed something but AFAICS this should be enough.
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.
It's all good. Great work 🙇
83b4ef3
into
Joystream:feature/multi-recipient-proposal
Ref to this issue #2365
Create Proposal Scope Completed