From 810ec6efa7467df1429d5589ca76a23a45d187cf Mon Sep 17 00:00:00 2001 From: MananTank Date: Tue, 3 Jun 2025 22:41:54 +0000 Subject: [PATCH] [TOOL-4651] Dashboard: Show error on invalid token distribution in ERC20 asset creation form (#7260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on enhancing the `TokenDistributionFieldset` component by adding error handling for token distribution and improving the UI by displaying error messages. It also updates the `StepCard` and `DistributionBarChart` components to accommodate new features. ### Detailed summary - Added `disabled` prop to `StepCard`. - Integrated error handling in `TokenDistributionFieldset` for distribution validation. - Displayed error messages when distribution exceeds supply. - Improved `DistributionBarChart` to conditionally style text based on segment percentages. - Introduced `getDistributionError` and `SafeNumber` utility functions for validation logic. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit - **New Features** - Added validation to ensure token distribution does not exceed total supply, with error messages displayed when limits are surpassed. - The "Next" submit button in the token creation flow can now be disabled based on validation errors. - **Bug Fixes** - Owner percentage in the distribution chart is now prevented from displaying negative values. - **Style** - Segment labels in the distribution chart legend are visually highlighted if their percentage is outside the 0–100% range. --- .../components/blocks/distribution-chart.tsx | 9 +++- .../assets/create/create-token-card.tsx | 2 + .../distribution/token-distribution.tsx | 49 +++++++++++++++++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/apps/dashboard/src/@/components/blocks/distribution-chart.tsx b/apps/dashboard/src/@/components/blocks/distribution-chart.tsx index 186f8671cf4..2c9b8ea1c5c 100644 --- a/apps/dashboard/src/@/components/blocks/distribution-chart.tsx +++ b/apps/dashboard/src/@/components/blocks/distribution-chart.tsx @@ -10,6 +10,7 @@ type DistributionBarChartProps = { segments: Segment[]; title: string; }; + export function DistributionBarChart(props: DistributionBarChartProps) { const totalPercentage = props.segments.reduce( (sum, segment) => sum + segment.percent, @@ -59,7 +60,13 @@ export function DistributionBarChart(props: DistributionBarChartProps) { backgroundColor: segment.color, }} /> -

+

100 || segment.percent < 0) && + "text-destructive-text", + )} + > {segment.label}: {segment.percent}%

diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/create-token-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/create-token-card.tsx index fbcf382cda3..f46166dce68 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/create-token-card.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/create-token-card.tsx @@ -15,6 +15,7 @@ export function StepCard(props: { | undefined | { type: "submit"; + disabled?: boolean; } | { type: "custom"; @@ -56,6 +57,7 @@ export function StepCard(props: { variant="default" className="gap-2" type="submit" + disabled={props.nextButton.disabled} onClick={() => { trackEvent( getStepCardTrackingData({ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/distribution/token-distribution.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/distribution/token-distribution.tsx index eb1fcc0e63d..b35585c9e89 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/distribution/token-distribution.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/distribution/token-distribution.tsx @@ -26,6 +26,7 @@ export function TokenDistributionFieldset(props: { tokenSymbol: string | undefined; }) { const { form } = props; + const distributionError = getDistributionError(form); return (
@@ -38,6 +39,7 @@ export function TokenDistributionFieldset(props: { }} nextButton={{ type: "submit", + disabled: !!distributionError, }} >
@@ -56,9 +58,17 @@ export function TokenDistributionFieldset(props: {
- +
+ + + {distributionError && ( +
+ {distributionError} +
+ )} +
@@ -74,6 +84,36 @@ export function TokenDistributionFieldset(props: { ); } +function getDistributionError(form: TokenDistributionForm) { + const supply = Number(form.watch("supply")); + const totalAirdrop = form.watch("airdropAddresses").reduce((sum, addr) => { + return sum + SafeNumber(addr.quantity); + }, 0); + + if (totalAirdrop > supply) { + return "Total airdrop quantity exceeds total supply"; + } + + const saleSupplyPercentage = SafeNumber( + form.watch("saleAllocationPercentage"), + ); + + const saleSupply = (saleSupplyPercentage / 100) * supply; + const ownerSupply = Math.max(supply - totalAirdrop - saleSupply, 0); + const totalSumOfSupply = totalAirdrop + saleSupply + ownerSupply; + + if (totalSumOfSupply > supply) { + return "Token distribution exceeds total supply"; + } + + return undefined; +} + +function SafeNumber(value: string) { + const num = Number(value); + return Number.isNaN(num) ? 0 : num; +} + export function TokenDistributionBarChart(props: { distributionFormValues: TokenDistributionFormValues; }) { @@ -87,7 +127,8 @@ export function TokenDistributionBarChart(props: { const salePercentage = Number( props.distributionFormValues.saleAllocationPercentage, ); - const ownerPercentage = 100 - airdropPercentage - salePercentage; + + const ownerPercentage = Math.max(100 - airdropPercentage - salePercentage, 0); const tokenAllocations: Segment[] = [ {