diff --git a/src/components/common/GraduatedCurveMilestoneChart.tsx b/src/components/common/GraduatedCurveMilestoneChart.tsx index 733067f9..f4945a37 100644 --- a/src/components/common/GraduatedCurveMilestoneChart.tsx +++ b/src/components/common/GraduatedCurveMilestoneChart.tsx @@ -95,6 +95,18 @@ export const GraduatedCurveMilestoneChart: React.FC a.supplyThreshold - b.supplyThreshold); + const finalMilestone = normalizedMilestones[normalizedMilestones.length - 1]; + const nextMilestone = + currentSupply === undefined + ? finalMilestone + : normalizedMilestones.find(m => m.supplyThreshold > currentSupply); + const progressPercent = + currentSupply === undefined || finalMilestone.supplyThreshold <= 0 + ? 0 + : Math.min(100, Math.max(0, (currentSupply / finalMilestone.supplyThreshold) * 100)); + const remainingSupply = nextMilestone + ? Math.max(0, nextMilestone.supplyThreshold - (currentSupply ?? 0)) + : 0; // Ensure milestone step points for Recharts step chart rendering const chartData = [...normalizedMilestones]; @@ -125,6 +137,40 @@ export const GraduatedCurveMilestoneChart: React.FC )} +
+
+
+

Graduation progress

+

+ {currentSupply === undefined + ? 'Connect live supply data to see your position.' + : nextMilestone + ? `${remainingSupply.toLocaleString()} keys until the ${nextMilestone.supplyThreshold.toLocaleString()}-key milestone` + : 'Final milestone reached — the key is graduated.'} +

+
+ + {Math.round(progressPercent)}% + +
+
+
+
+
+ {normalizedMilestones.map(milestone => ( + + {milestone.supplyThreshold.toLocaleString()} keys · {milestone.simulatedPrice} XLM + + ))} +
+
{ { supplyThreshold: 50, exponent: 1.5, simulatedPrice: 5.0, exponentChange: '+0.3' }, { supplyThreshold: 100, exponent: 2.0, simulatedPrice: 10.0, exponentChange: '+0.5' }, ]); + expect(screen.getByTestId('milestone-progress-panel')).toBeInTheDocument(); + expect(screen.getByTestId('milestone-progress-value')).toHaveTextContent('45%'); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '45'); + expect(screen.getByText('5 keys until the 50-key milestone')).toBeInTheDocument(); }); it('marks each milestone threshold with vertical dashed lines and labels showing exponent change', () => { diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index 8e0f5e9d..6afa74f5 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -47,6 +47,7 @@ import { useKeyConfig } from '@/hooks/useKeyConfig'; import KeyStatsPanel from '@/components/common/KeyStatsPanel'; import Skeleton from '@/components/ui/skeleton'; import { Tooltip } from '@/components/ui/tooltip'; +import GraduatedCurveMilestoneChart from '@/components/common/GraduatedCurveMilestoneChart'; import KeyDeprecationBanner from '@/components/common/KeyDeprecationBanner'; import MergeProposalBanner from '@/components/common/MergeProposalBanner'; import KeyBuybackModal from '@/components/common/KeyBuybackModal'; @@ -483,12 +484,16 @@ function CreatorDetailPageContent() {

Price Curve

- +
+ {/* Buy Simulation Tool */}