Skip to content

[jules] enhance: Add animated success celebration when settled up#306

Open
Devasy wants to merge 1 commit intomainfrom
jules-7395995221662751088-b031e1e4
Open

[jules] enhance: Add animated success celebration when settled up#306
Devasy wants to merge 1 commit intomainfrom
jules-7395995221662751088-b031e1e4

Conversation

@Devasy
Copy link
Copy Markdown
Owner

@Devasy Devasy commented Mar 25, 2026

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-confetti animation 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.tsx modified to hold the hasCelebrated state and the useEffect animation trigger.
  • package.json and package-lock.json updated with canvas-confetti.
  • Added related tracking entries in .Jules/todo.md and .Jules/changelog.md.

PR created automatically by Jules for task 7395995221662751088 started by @Devasy23

Summary by CodeRabbit

  • New Features
    • Added celebratory confetti animation when all group expenses are settled.
    • Enhanced "All Settled Up" state with smooth, staggered animations including icon scaling, rotation, and text fade-in effects for improved user experience.

* 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>
@Devasy Devasy requested a review from vrajpatelll as a code owner March 25, 2026 20:03
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 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 @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 25, 2026

Deploy Preview for split-but-wiser ready!

Name Link
🔨 Latest commit ef621f1
🔍 Latest deploy log https://app.netlify.com/projects/split-but-wiser/deploys/69c43f894b3abc0008dea338
😎 Deploy Preview https://deploy-preview-306--split-but-wiser.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Walkthrough

This 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

Cohort / File(s) Summary
Documentation & Tracking
.Jules/changelog.md, .Jules/todo.md
Updated changelog to document web UI enhancement for "All Settled Up" animations and marked "Animated success celebration when settled up" task as completed with metadata (2026-03-01).
Dependencies
web/package.json
Added runtime dependency canvas-confetti (^1.9.4) and TypeScript types @types/canvas-confetti (^1.9.0).
UI Implementation
web/pages/GroupDetails.tsx
Implemented confetti animations triggered when settlements tab is active with zero settlements and existing expenses. Added hasCelebrated state to prevent retriggering. Enhanced empty-state UI with Framer Motion animations for icon (scale/rotate spring transition) and text (fade/slide with staggered delays).

Possibly related PRs

Suggested reviewers

  • vrajpatelll
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main enhancement: adding animated success celebration when a group is settled up, which aligns directly with the core changes in GroupDetails.tsx and dependency additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@9404621). Learn more about missing BASE report.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #306   +/-   ##
=======================================
  Coverage        ?   63.55%           
=======================================
  Files           ?       21           
  Lines           ?     2456           
  Branches        ?      254           
=======================================
  Hits            ?     1561           
  Misses          ?      831           
  Partials        ?       64           
Components Coverage Δ
Authentication System 71.35% <ø> (?)
Expense Management 70.15% <ø> (?)
Group Management 73.78% <ø> (?)
User Management 97.16% <ø> (?)
Backend Core 70.78% <ø> (?)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9404621 and ef621f1.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • .Jules/changelog.md
  • .Jules/todo.md
  • web/package.json
  • web/pages/GroupDetails.tsx

const [paymentPayerId, setPaymentPayerId] = useState('');
const [paymentPayeeId, setPaymentPayeeId] = useState('');
const [paymentAmount, setPaymentAmount] = useState('');
const [hasCelebrated, setHasCelebrated] = useState(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

wc -l web/pages/GroupDetails.tsx

Repository: Devasy/splitwiser

Length of output: 92


🏁 Script executed:

sed -n '85,105p' web/pages/GroupDetails.tsx

Repository: Devasy/splitwiser

Length of output: 969


🏁 Script executed:

sed -n '250,290p' web/pages/GroupDetails.tsx

Repository: Devasy/splitwiser

Length of output: 1595


🏁 Script executed:

sed -n '880,920p' web/pages/GroupDetails.tsx

Repository: Devasy/splitwiser

Length of output: 2710


🏁 Script executed:

head -50 web/pages/GroupDetails.tsx

Repository: 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 ref instead of state for the guard, or remove hasCelebrated from dependencies and reset it when id changes or settlements.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.

Comment on lines +252 to +284
// 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]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check if the file exists and its size
wc -l web/pages/GroupDetails.tsx

Repository: Devasy/splitwiser

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Read the entire file to understand the context
cat -n web/pages/GroupDetails.tsx

Repository: 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.tsx

Repository: 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.tsx

Repository: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant