-
Notifications
You must be signed in to change notification settings - Fork 54
feat: support sablier airdrop proposal type #901
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
Changes from 4 commits
efabdc8
ad4c9df
fc8f35d
09a3e38
f372d37
13f2e84
ebf98dc
ecdfaeb
7236bf3
4424048
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 | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,15 @@ | ||||||
| import { BuilderTransaction } from '@buildeross/types' | ||||||
| import * as Yup from 'yup' | ||||||
|
|
||||||
| import { | ||||||
| PROPOSAL_SUMMARY_REQUIRED_ERROR, | ||||||
| PROPOSAL_TITLE_FORMAT_ERROR, | ||||||
| PROPOSAL_TITLE_MAX_ERROR, | ||||||
| PROPOSAL_TITLE_MAX_LENGTH, | ||||||
| PROPOSAL_TITLE_REGEX, | ||||||
| PROPOSAL_TITLE_REQUIRED_ERROR, | ||||||
| } from '../../constants' | ||||||
|
|
||||||
| export const ERROR_CODE: Record<string, string> = { | ||||||
| GENERIC: `Oops. Looks like there was a problem submitting this proposal, please try again..`, | ||||||
| WRONG_NETWORK: `Oops. Looks like you're on the wrong network. Please switch and try again.`, | ||||||
|
|
@@ -16,9 +25,9 @@ export interface FormValues { | |||||
|
|
||||||
| export const validationSchema = Yup.object().shape({ | ||||||
| title: Yup.string() | ||||||
| .required('Proposal title is required') | ||||||
| .matches(/^[A-Za-z0-9 _.-]*[A-Za-z0-9][A-Za-z0-9 _.-]*$/, 'only numbers or letters') | ||||||
| .max(5000, '< 256 characters'), | ||||||
| summary: Yup.string().optional().required('Summary is required'), | ||||||
| .required(PROPOSAL_TITLE_REQUIRED_ERROR) | ||||||
| .matches(PROPOSAL_TITLE_REGEX, PROPOSAL_TITLE_FORMAT_ERROR) | ||||||
| .max(PROPOSAL_TITLE_MAX_LENGTH, PROPOSAL_TITLE_MAX_ERROR), | ||||||
| summary: Yup.string().optional().required(PROPOSAL_SUMMARY_REQUIRED_ERROR), | ||||||
|
||||||
| summary: Yup.string().optional().required(PROPOSAL_SUMMARY_REQUIRED_ERROR), | |
| summary: Yup.string().required(PROPOSAL_SUMMARY_REQUIRED_ERROR), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/create-proposal-ui/src/components/ReviewProposalForm/fields.ts` at
line 31, The Yup validation for the summary field currently chains .optional()
and .required(), which is contradictory; update the schema by removing the
.optional() call on the summary field so it reads as
Yup.string().required(PROPOSAL_SUMMARY_REQUIRED_ERROR) (locate the "summary"
field in the schema definition in fields.ts).
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| import type { AddressType } from '@buildeross/types' | ||
| import { | ||
| addressValidationOptionalSchema, | ||
| addressValidationSchemaWithError, | ||
| } from '@buildeross/utils/yup' | ||
| import * as yup from 'yup' | ||
|
|
||
| import { TokenMetadataFormValidated, TokenMetadataSchema } from '../../shared' | ||
|
|
||
| export interface AirdropRecipientFormValues { | ||
| recipientAddress: string | AddressType | ||
| amount: string | ||
| } | ||
|
|
||
| export type AirdropType = 'instant' | 'll' | ||
|
|
||
| export interface AirdropTokensValues { | ||
| airdropType: AirdropType | ||
| campaignName: string | ||
| adminAddress: string | AddressType | ||
| campaignStartDate: string | ||
| expirationDate: string | ||
| vestingStartDate?: string | ||
| totalDurationDays?: number | ||
| cliffDurationDays?: number | ||
| cancelable: boolean | ||
| transferable: boolean | ||
| tokenAddress?: AddressType | ||
| tokenMetadata?: TokenMetadataFormValidated | ||
| recipients: AirdropRecipientFormValues[] | ||
| } | ||
|
|
||
| const DECIMAL_REGEX = /^(\d+\.?\d*|\.\d+)$/ | ||
|
|
||
| const recipientSchema = yup.object({ | ||
| recipientAddress: addressValidationSchemaWithError( | ||
| 'Recipient address is invalid.', | ||
| 'Recipient address is required.' | ||
| ), | ||
| amount: yup | ||
| .string() | ||
| .required('Amount is required.') | ||
| .test( | ||
| 'is-valid-decimal', | ||
| 'Amount must be a valid decimal number (no scientific notation)', | ||
| (value) => { | ||
| if (!value) return false | ||
| return DECIMAL_REGEX.test(value) | ||
| } | ||
| ) | ||
| .test('is-greater-than-0', 'Amount must be greater than 0', (value) => { | ||
| if (!value) return false | ||
| const num = parseFloat(value) | ||
| return !isNaN(num) && num > 0 | ||
| }), | ||
| }) | ||
|
|
||
| const airdropTokensSchema = () => | ||
| yup.object({ | ||
| airdropType: yup | ||
| .string() | ||
| .oneOf(['instant', 'll']) | ||
| .required('Airdrop type is required.'), | ||
| campaignName: yup | ||
| .string() | ||
| .trim() | ||
| .min(3, 'Campaign name must be at least 3 characters.') | ||
| .max(64, 'Campaign name must be 64 characters or fewer.') | ||
| .required('Campaign name is required.'), | ||
| adminAddress: addressValidationSchemaWithError( | ||
| 'Admin address is invalid.', | ||
| 'Admin address is required.' | ||
| ), | ||
| campaignStartDate: yup.string().required('Campaign start date is required.'), | ||
| expirationDate: yup | ||
| .string() | ||
| .required('Campaign expiration date is required.') | ||
| .test( | ||
| 'is-after-campaign-start', | ||
| 'Expiration must be after campaign start.', | ||
| function (v) { | ||
| const campaignStartDate = this.parent.campaignStartDate | ||
| if (!v || !campaignStartDate) return true | ||
| return new Date(v).getTime() > new Date(campaignStartDate).getTime() | ||
| } | ||
| ), | ||
| vestingStartDate: yup | ||
| .string() | ||
| .optional() | ||
| .when('airdropType', { | ||
| is: 'll', | ||
| then: (schema) => | ||
| schema | ||
| .required('Vesting start date is required for LL airdrops.') | ||
| .test( | ||
| 'is-on-or-after-campaign-start', | ||
| 'Vesting start must be at or after campaign start.', | ||
| function (v) { | ||
| const campaignStartDate = this.parent.campaignStartDate | ||
| if (!v || !campaignStartDate) return true | ||
| return new Date(v).getTime() >= new Date(campaignStartDate).getTime() | ||
| } | ||
| ), | ||
| otherwise: (schema) => schema.notRequired(), | ||
| }), | ||
| totalDurationDays: yup | ||
| .number() | ||
| .optional() | ||
| .when('airdropType', { | ||
| is: 'll', | ||
| then: (schema) => | ||
| schema | ||
| .required('Total duration (days) is required for LL airdrops.') | ||
| .integer('Total duration must be a whole number.') | ||
| .positive('Total duration must be greater than 0.'), | ||
| otherwise: (schema) => schema.notRequired(), | ||
| }), | ||
| cliffDurationDays: yup | ||
| .number() | ||
| .optional() | ||
| .integer('Cliff duration must be a whole number.') | ||
| .min(0, 'Cliff duration cannot be negative.') | ||
| .when(['airdropType', 'totalDurationDays'], { | ||
| is: (airdropType: AirdropType, totalDurationDays: number | undefined) => | ||
| airdropType === 'll' && typeof totalDurationDays === 'number', | ||
| then: (schema) => | ||
| schema.max( | ||
| yup.ref('totalDurationDays'), | ||
| 'Cliff duration cannot exceed total duration.' | ||
| ), | ||
| }), | ||
| cancelable: yup.boolean().required('Cancelable is required.'), | ||
| transferable: yup.boolean().required('Transferable is required.'), | ||
| tokenAddress: addressValidationOptionalSchema, | ||
| tokenMetadata: TokenMetadataSchema.optional(), | ||
| recipients: yup | ||
| .array() | ||
| .of(recipientSchema) | ||
| .required('Recipients are required.') | ||
| .min(1, 'At least one recipient is required.'), | ||
| }) | ||
|
|
||
| export default airdropTokensSchema |
Uh oh!
There was an error while loading. Please reload this page.