[jules] enhance: Add animated success celebration when settled up#306
[jules] enhance: Add animated success celebration when settled up#306
Conversation
* Added `canvas-confetti` dependency to `/web/package.json` * Trigger a 3-second celebratory confetti animation on `web/pages/GroupDetails.tsx` when navigating to the settlements tab if all balances are zero and there are recorded expenses. * Added Framer Motion pop-in and fade animations to the "All Settled Up" empty state elements. * Correctly implemented `setInterval` cleanup to prevent background memory leaks upon component unmount. * Updated `.Jules/todo.md` (marked complete) and `.Jules/changelog.md`. Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
✅ Deploy Preview for split-but-wiser ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughThis PR implements animated celebration effects when group expenses are fully settled. It adds canvas-confetti animations and Framer Motion transitions to the "All Settled Up" UI state in GroupDetails, installs required dependencies, and updates documentation to reflect completion of this feature. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #306 +/- ##
=======================================
Coverage ? 63.55%
=======================================
Files ? 21
Lines ? 2456
Branches ? 254
=======================================
Hits ? 1561
Misses ? 831
Partials ? 64
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/pages/GroupDetails.tsx`:
- Around line 252-284: The celebration eligibility uses the filtered page count
(expenses.length) inside the useEffect (checking activeTab, settlements,
hasCelebrated) which prevents celebration when a search/filter hides expenses;
change the check to use the unfiltered total (e.g. totalSummary?.expenseCount or
another unfiltered count populated by fetchData/getExpenses) instead of
expenses.length so the condition becomes settlements.length === 0 &&
(totalSummary?.expenseCount ?? 0) > 0 while keeping the same useEffect
dependencies updated to include totalSummary?.expenseCount.
- Around line 252-284: The confetti effect and the Framer Motion spring/rotate
animations should respect the user's prefers-reduced-motion setting: inside the
useEffect that triggers confetti (the block using activeTab, hasCelebrated,
confetti, settlements, expenses) check
window.matchMedia('(prefers-reduced-motion: reduce)').matches and if true skip
starting the interval (or run a non-animated fallback), and for the "All Settled
Up!" Framer Motion component(s) (the elements using spring/rotate animations)
add a similar prefers-reduced-motion check to either remove/disable the
animate/transition props or switch to a simplified/no-motion variant; update
references to hasCelebrated and any state that depends on the animation so
behavior remains correct when animations are skipped.
- Line 91: The confetti guard state hasCelebrated (useState) is causing the
effect to clear its own interval and never re-trigger for new groups; replace
hasCelebrated state with a ref (e.g., hasCelebratedRef) used only inside the
effect (set hasCelebratedRef.current = true when celebration runs) or remove
hasCelebrated from the effect dependencies and explicitly reset it when the
group id (id) changes or when settlements.length becomes > 0 so the 3s interval
can complete and future settle-ups can retrigger; additionally, import and use
Framer Motion's useReducedMotion in the component and wrap/conditionalize the
Framer Motion animate props (the rotation and scale animations currently applied
around the "All Settled Up" UI) to disable or simplify animations when reduced
motion is requested.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 027ea848-c412-4aa0-a431-945837a8f82d
⛔ Files ignored due to path filters (1)
web/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
.Jules/changelog.md.Jules/todo.mdweb/package.jsonweb/pages/GroupDetails.tsx
| const [paymentPayerId, setPaymentPayerId] = useState(''); | ||
| const [paymentPayeeId, setPaymentPayeeId] = useState(''); | ||
| const [paymentAmount, setPaymentAmount] = useState(''); | ||
| const [hasCelebrated, setHasCelebrated] = useState(false); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 92
🏁 Script executed:
sed -n '85,105p' web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 969
🏁 Script executed:
sed -n '250,290p' web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 1595
🏁 Script executed:
sed -n '880,920p' web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 2710
🏁 Script executed:
head -50 web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 1865
The effect cancels its own confetti loop because hasCelebrated is updated and also listed in dependencies.
When setHasCelebrated(true) runs at line 257, React re-renders and re-runs the effect. Before executing the effect body, it first runs cleanup from the previous effect execution, which clears the interval started at line 265. The interval never completes its 3-second duration.
Additionally, once hasCelebrated is set to true, it is never reset when the group ID changes or when settlements become non-zero again. Subsequent settle-ups in the same mounted page will not trigger the celebration.
The "All Settled Up" animations do not respect user motion preferences. The Framer Motion animations at lines 903–906 (rotation, spring scaling) and similar use animate without checking prefers-reduced-motion. This violates WCAG 2.1 SC 2.3.3. Use useReducedMotion() from Framer Motion or CSS media queries to disable or simplify animations for users with reduced-motion preferences.
Possible fixes:
- Use a
refinstead of state for the guard, or removehasCelebratedfrom dependencies and reset it whenidchanges orsettlements.length > 0. - Wrap motion animations with
useReducedMotion()hook to respect accessibility preferences.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/pages/GroupDetails.tsx` at line 91, The confetti guard state
hasCelebrated (useState) is causing the effect to clear its own interval and
never re-trigger for new groups; replace hasCelebrated state with a ref (e.g.,
hasCelebratedRef) used only inside the effect (set hasCelebratedRef.current =
true when celebration runs) or remove hasCelebrated from the effect dependencies
and explicitly reset it when the group id (id) changes or when
settlements.length becomes > 0 so the 3s interval can complete and future
settle-ups can retrigger; additionally, import and use Framer Motion's
useReducedMotion in the component and wrap/conditionalize the Framer Motion
animate props (the rotation and scale animations currently applied around the
"All Settled Up" UI) to disable or simplify animations when reduced motion is
requested.
| // Trigger celebration when all settled up | ||
| useEffect(() => { | ||
| let interval: any; | ||
|
|
||
| if (activeTab === 'settlements' && !loading && settlements.length === 0 && !hasCelebrated && expenses.length > 0) { | ||
| setHasCelebrated(true); | ||
|
|
||
| const duration = 3 * 1000; | ||
| const animationEnd = Date.now() + duration; | ||
| const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }; | ||
|
|
||
| const randomInRange = (min: number, max: number) => Math.random() * (max - min) + min; | ||
|
|
||
| interval = setInterval(function() { | ||
| const timeLeft = animationEnd - Date.now(); | ||
|
|
||
| if (timeLeft <= 0) { | ||
| return clearInterval(interval); | ||
| } | ||
|
|
||
| const particleCount = 50 * (timeLeft / duration); | ||
|
|
||
| confetti(Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } })); | ||
| confetti(Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } })); | ||
| }, 250); | ||
| } | ||
|
|
||
| return () => { | ||
| if (interval) { | ||
| clearInterval(interval); | ||
| } | ||
| }; | ||
| }, [activeTab, loading, settlements.length, hasCelebrated, expenses.length]); |
There was a problem hiding this comment.
Use the unfiltered expense count for this eligibility check.
expenses.length on Line 256 only reflects the current fetched expense page/filter. Because fetchData() passes debouncedSearch into getExpenses, an active search that hides all expenses will suppress the celebration even when the group already has recorded activity. Key this off totalSummary?.expenseCount or another unfiltered count instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/pages/GroupDetails.tsx` around lines 252 - 284, The celebration
eligibility uses the filtered page count (expenses.length) inside the useEffect
(checking activeTab, settlements, hasCelebrated) which prevents celebration when
a search/filter hides expenses; change the check to use the unfiltered total
(e.g. totalSummary?.expenseCount or another unfiltered count populated by
fetchData/getExpenses) instead of expenses.length so the condition becomes
settlements.length === 0 && (totalSummary?.expenseCount ?? 0) > 0 while keeping
the same useEffect dependencies updated to include totalSummary?.expenseCount.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's check if the file exists and its size
wc -l web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 92
🏁 Script executed:
#!/bin/bash
# Read the entire file to understand the context
cat -n web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 50373
🏁 Script executed:
#!/bin/bash
# Read the specific lines mentioned in the review comment
sed -n '252,284p' web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 1392
🏁 Script executed:
#!/bin/bash
# Read lines 886-915 for Framer Motion code
sed -n '886,920p' web/pages/GroupDetails.tsxRepository: Devasy/splitwiser
Length of output: 2371
🏁 Script executed:
#!/bin/bash
# Search for prefers-reduced-motion usage in the file
grep -n "prefers-reduced-motion" web/pages/GroupDetails.tsx || echo "No prefers-reduced-motion found"Repository: Devasy/splitwiser
Length of output: 92
🏁 Script executed:
#!/bin/bash
# Search for useReducedMotion or any motion-related hooks
grep -n "useReducedMotion\|prefersReducedMotion\|mediaQuery" web/pages/GroupDetails.tsx || echo "No reduced motion utilities found"Repository: Devasy/splitwiser
Length of output: 94
Gate confetti and Framer Motion animations behind prefers-reduced-motion.
The confetti celebration (lines 252-284) and the spring/rotate animations in the "All Settled Up!" message (lines 886-915) bypass users' motion preferences. Users who set prefers-reduced-motion: reduce at the OS level will still see these animations. Use window.matchMedia('(prefers-reduced-motion: reduce)').matches to skip or simplify these animations for accessibility.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/pages/GroupDetails.tsx` around lines 252 - 284, The confetti effect and
the Framer Motion spring/rotate animations should respect the user's
prefers-reduced-motion setting: inside the useEffect that triggers confetti (the
block using activeTab, hasCelebrated, confetti, settlements, expenses) check
window.matchMedia('(prefers-reduced-motion: reduce)').matches and if true skip
starting the interval (or run a non-animated fallback), and for the "All Settled
Up!" Framer Motion component(s) (the elements using spring/rotate animations)
add a similar prefers-reduced-motion check to either remove/disable the
animate/transition props or switch to a simplified/no-motion variant; update
references to hasCelebrated and any state that depends on the animation so
behavior remains correct when animations are skipped.
This PR addresses the user-facing task "Animated success celebration when settled up".
When users settle their debts in a group and check the "Balances" tab, they are now greeted with a delightful, 3-second
canvas-confettianimation to celebrate the milestone. The checkmark and text elements in the "All Settled Up!" state were also enhanced with staggered Framer Motion pop-in animations, which seamlessly fit both the Neobrutalism and Glassmorphism themes natively.A frontend verification Playwright script was utilized to capture a screenshot of the animation, and a cleanup memory leak fix was successfully applied to the interval loop post-review.
Updates:
web/pages/GroupDetails.tsxmodified to hold thehasCelebratedstate and theuseEffectanimation trigger.package.jsonandpackage-lock.jsonupdated withcanvas-confetti..Jules/todo.mdand.Jules/changelog.md.PR created automatically by Jules for task 7395995221662751088 started by @Devasy23
Summary by CodeRabbit