@@ -66,6 +66,21 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
6666 // Ref for the accent color section (current page token display)
6767 const accentSectionRef = useRef < HTMLDivElement > ( null ) ;
6868
69+ // State for springy click animation
70+ const [ isPlusSpringAnimating , setIsPlusSpringAnimating ] = useState ( false ) ;
71+ const [ isMinusSpringAnimating , setIsMinusSpringAnimating ] = useState ( false ) ;
72+
73+ // Function to trigger spring animation
74+ const triggerPlusSpringAnimation = ( ) => {
75+ setIsPlusSpringAnimating ( true ) ;
76+ setTimeout ( ( ) => setIsPlusSpringAnimating ( false ) , 200 ) ; // Animation duration
77+ } ;
78+
79+ const triggerMinusSpringAnimation = ( ) => {
80+ setIsMinusSpringAnimating ( true ) ;
81+ setTimeout ( ( ) => setIsMinusSpringAnimating ( false ) , 200 ) ; // Animation duration
82+ } ;
83+
6984 // Ref for the plus button (for pulsing effect)
7085 const plusButtonRef = useRef < HTMLButtonElement > ( null ) ;
7186
@@ -383,7 +398,7 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
383398 const allocationsData = retryData ;
384399
385400 if ( allocationsData . success ) {
386- setCurrentTokenAllocation ( Math . max ( 0 , currentTokenAllocation + change ) ) ;
401+ // Don't override optimistic UI state - it's already been set
387402
388403 // Update allocations data for modal
389404 if ( allocationsData . allocations ) {
@@ -396,16 +411,19 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
396411 }
397412
398413 // Update token balance with the most current data
399- const balance = allocationsData . summary . balance ;
400- const actualAllocatedTokens = allocationsData . summary . totalTokensAllocated || 0 ;
401-
402- if ( balance ) {
403- setTokenBalance ( {
404- totalTokens : balance . totalTokens ,
405- allocatedTokens : actualAllocatedTokens ,
406- availableTokens : balance . totalTokens - actualAllocatedTokens ,
407- lastUpdated : new Date ( balance . lastUpdated )
408- } ) ;
414+ // Only update if this response is for the current user action
415+ if ( Date . now ( ) - actionTimestamp < 5000 ) { // Within 5 seconds of user action
416+ const balance = allocationsData . summary . balance ;
417+ const actualAllocatedTokens = allocationsData . summary . totalTokensAllocated || 0 ;
418+
419+ if ( balance ) {
420+ setTokenBalance ( {
421+ totalTokens : balance . totalTokens ,
422+ allocatedTokens : actualAllocatedTokens ,
423+ availableTokens : balance . totalTokens - actualAllocatedTokens ,
424+ lastUpdated : new Date ( balance . lastUpdated )
425+ } ) ;
426+ }
409427 }
410428
411429 // Clear pending update for this page
@@ -431,7 +449,7 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
431449 console . log ( '✅ PledgeBar: Token allocation successful' , result ) ;
432450
433451 // Handle successful allocation
434- setCurrentTokenAllocation ( Math . max ( 0 , currentTokenAllocation + change ) ) ;
452+ // Don't override optimistic UI state - it's already been set
435453
436454 // Refresh allocations data to get accurate server state
437455 const allocationsResponse = await fetch ( '/api/tokens/allocations' ) ;
@@ -449,16 +467,19 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
449467 } ) ) ) ;
450468
451469 // Update token balance with the most current data
452- const balance = allocationsData . summary . balance ;
453- const actualAllocatedTokens = allocationsData . summary . totalTokensAllocated || 0 ;
454-
455- if ( balance ) {
456- setTokenBalance ( {
457- totalTokens : balance . totalTokens ,
458- allocatedTokens : actualAllocatedTokens ,
459- availableTokens : balance . totalTokens - actualAllocatedTokens ,
460- lastUpdated : new Date ( balance . lastUpdated )
461- } ) ;
470+ // Only update if this response is for the current user action
471+ if ( Date . now ( ) - actionTimestamp < 5000 ) { // Within 5 seconds of user action
472+ const balance = allocationsData . summary . balance ;
473+ const actualAllocatedTokens = allocationsData . summary . totalTokensAllocated || 0 ;
474+
475+ if ( balance ) {
476+ setTokenBalance ( {
477+ totalTokens : balance . totalTokens ,
478+ allocatedTokens : actualAllocatedTokens ,
479+ availableTokens : balance . totalTokens - actualAllocatedTokens ,
480+ lastUpdated : new Date ( balance . lastUpdated )
481+ } ) ;
482+ }
462483 }
463484
464485 // Clear pending update for this page
@@ -637,13 +658,21 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
637658 variant = "outline"
638659 onClick = { ( e ) => {
639660 e . stopPropagation ( ) ;
661+
662+ // Trigger spring animation
663+ triggerMinusSpringAnimation ( ) ;
664+
640665 // Only allow minus if we have tokens to remove
641666 if ( currentTokenAllocation > 0 ) {
642667 handleTokenChange ( - incrementAmount ) ;
643668 }
644669 } }
645- className = { `h-8 w-8 p-0 ${ currentTokenAllocation <= 0 || isOutOfTokens ? 'opacity-50' : '' } ${ isRefreshing ? 'opacity-75' : '' } ` }
646- disabled = { isOutOfTokens }
670+ className = { `h-8 w-8 p-0 transition-transform duration-200 ${
671+ isMinusSpringAnimating
672+ ? 'animate-[spring_0.2s_cubic-bezier(0.68,-0.55,0.265,1.55)]'
673+ : ''
674+ } ${ currentTokenAllocation <= 0 ? 'opacity-50' : '' } `}
675+ disabled = { false } // Make truly optimistic - never disable
647676 >
648677 < Minus className = "h-4 w-4" />
649678 </ Button >
@@ -709,6 +738,10 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
709738 variant = "outline"
710739 onClick = { ( e ) => {
711740 e . stopPropagation ( ) ;
741+
742+ // Trigger spring animation
743+ triggerPlusSpringAnimation ( ) ;
744+
712745 if ( isOutOfTokens ) {
713746 // Redirect to subscription page when out of tokens
714747 router . push ( '/settings/subscription' ) ;
@@ -730,8 +763,12 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
730763 handleTokenChange ( incrementAmount ) ;
731764 }
732765 } }
733- className = { `h-8 w-8 p-0 ${ isRefreshing ? 'opacity-75' : '' } ` }
734- disabled = { isRefreshing }
766+ className = { `h-8 w-8 p-0 transition-transform duration-200 ${
767+ isPlusSpringAnimating
768+ ? 'animate-[spring_0.2s_cubic-bezier(0.68,-0.55,0.265,1.55)]'
769+ : ''
770+ } `}
771+ disabled = { false } // Make truly optimistic - never disable
735772 >
736773 < Plus className = "h-4 w-4" />
737774 </ Button >
@@ -812,9 +849,9 @@ const PledgeBar = React.forwardRef<HTMLDivElement, PledgeBarProps>(({
812849 trigger = { triggerEffect }
813850 originElement = { originElement }
814851 onComplete = { resetEffect }
815- particleCount = { 10 }
816- duration = { 900 }
817- maxDistance = { 60 }
852+ particleCount = { 12 }
853+ duration = { 1000 }
854+ maxDistance = { 80 }
818855 />
819856
820857 { /* Pulsing Button Effect for Logged-Out Users */ }
0 commit comments