Skip to content

Conversation

@Devasy
Copy link
Owner

@Devasy Devasy commented Feb 1, 2026

Implemented a new SettledUpState component with a confetti animation and Framer Motion entrance effects. Replaced the static "All Settled Up!" text in GroupDetails.tsx.

Key changes:

  • Created web/components/ui/Confetti.tsx: Reusable confetti particle system.
  • Created web/components/SettledUpState.tsx: Themed success state with animations.
  • Updated web/pages/GroupDetails.tsx: Integrated the new component.
  • Verified with Playwright (handling HashRouter and CORS mocks).

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

Summary by CodeRabbit

  • New Features

    • Added an animated "All Settled Up!" celebration view displayed when all group expenses are settled, featuring animated visual confirmations, celebratory confetti effects, and smooth theme-aware transitions with staggered animations.
  • Refactor

    • Extracted the "All Settled Up!" UI from the Groups detail page into reusable components, improving code organization and maintainability across the application.

✏️ Tip: You can customize this high-level summary in your review settings.

Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com>
@Devasy Devasy requested a review from vrajpatelll as a code owner February 1, 2026 20:00
@google-labs-jules
Copy link
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

netlify bot commented Feb 1, 2026

Deploy Preview for split-but-wiser ready!

Name Link
🔨 Latest commit c66f8a1
🔍 Latest deploy log https://app.netlify.com/projects/split-but-wiser/deploys/697fb0e7f65b1a0008e89c59
😎 Deploy Preview https://deploy-preview-270--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
Contributor

coderabbitai bot commented Feb 1, 2026

Walkthrough

Introduces new reusable UI components for displaying settled states: a SettledUpState component with Framer Motion animations and confetti effects, plus a Confetti component for animated particles. Updates GroupDetails page to use the new SettledUpState component instead of inline UI code.

Changes

Cohort / File(s) Summary
New UI Components
web/components/SettledUpState.tsx, web/components/ui/Confetti.tsx
Introduces SettledUpState component with Framer Motion animations and confetti effects; adds Confetti component for animated particle rendering with customizable particle count.
Page Updates
web/pages/GroupDetails.tsx
Replaces inline "All Settled Up!" UI with the new reusable SettledUpState component.
🚥 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 'ux: Add animated success celebration when settled up' directly aligns with the main changes: introducing animated SettledUpState and Confetti components to replace static 'All Settled Up!' UI with celebratory visuals.
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.

Copy link
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: 2

🤖 Fix all issues with AI agents
In `@web/components/SettledUpState.tsx`:
- Around line 1-49: The component ignores user "prefers-reduced-motion" and must
respect reduced-motion settings: use framer-motion's useReducedMotion() inside
SettledUpState to detect reduced motion, pass a flag into Confetti (e.g.,
reducedMotion or disableAnimation) and conditionally render Confetti (skip or
set count=0) when reduced; for the motion.div elements (the outer icon wrapper,
inner check, and text container) use the reducedMotion flag to replace animate
with initial (or set animate equal to initial/disable transitions) so no
entrance animations run when reducedMotion is true; update the Confetti
component similarly to accept and honor the prop so it avoids rendering or
animating when reducedMotion is true.

In `@web/components/ui/Confetti.tsx`:
- Around line 9-18: The Particle interface and Confetti rendering use
Math.random() during render which causes particle positions and border radii to
change on re-renders; modify the Particle type to include stable precomputed
fields (e.g., initialX, initialY, borderRadius, initialRotation, duration,
delay) and generate these random values once when creating particles (inside a
createInitialParticles function, useEffect, or useRef in the Confetti component)
so the render uses particle.initialX / particle.borderRadius etc. instead of
calling Math.random() inline; update any places referencing x, y, rotation or
border radius to use the new stored properties on Particle (and remove runtime
Math.random calls from the JSX) to prevent jitter.

Comment on lines +1 to +49
import { motion } from 'framer-motion';
import { Check } from 'lucide-react';
import React from 'react';
import { THEMES } from '../constants';
import { useTheme } from '../contexts/ThemeContext';
import { Confetti } from './ui/Confetti';

export const SettledUpState = () => {
const { style } = useTheme();

return (
<div className="col-span-full flex flex-col items-center justify-center py-20 relative">
<Confetti count={100} />

<motion.div
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{
type: "spring",
stiffness: 260,
damping: 20,
delay: 0.1
}}
className={`w-24 h-24 flex items-center justify-center mx-auto mb-6 ${
style === THEMES.NEOBRUTALISM
? 'bg-emerald-100 border-2 border-black rounded-none shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]'
: 'bg-emerald-100 dark:bg-emerald-900/20 rounded-full ring-4 ring-emerald-500/20'
}`}
>
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.2 }}
>
<Check size={48} strokeWidth={3} className={style === THEMES.NEOBRUTALISM ? 'text-black' : 'text-emerald-500'} />
</motion.div>
</motion.div>

<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="text-center"
>
<h3 className={`text-3xl font-black mb-2 ${style === THEMES.NEOBRUTALISM ? 'text-black' : 'text-emerald-500'}`}>
All Settled Up!
</h3>
<p className="opacity-60 text-lg">No outstanding balances in this group.</p>
</motion.div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

find . -name "package.json" -type f | head -5 | xargs grep -l "framer-motion" 2>/dev/null

Repository: Devasy/splitwiser

Length of output: 79


🏁 Script executed:

cat package.json | grep -A 2 -B 2 "framer-motion"

Repository: Devasy/splitwiser

Length of output: 105


🏁 Script executed:

find . -name "Confetti*" -o -name "confetti*" | head -10

Repository: Devasy/splitwiser

Length of output: 93


🌐 Web query:

framer-motion v11 useReducedMotion API prefers-reduced-motion

💡 Result:

In Framer Motion v11, reduced-motion support is built around the user’s OS/browser setting exposed via the CSS media query (prefers-reduced-motion: reduce). [3]

1) Manual: useReducedMotion()

useReducedMotion() is a hook that returns a boolean indicating whether Reduced Motion is enabled, and it will re-render when the setting changes. [2]

import { useReducedMotion, motion } from "framer-motion" // v11

export function Sidebar({ isOpen }: { isOpen: boolean }) {
  const shouldReduceMotion = useReducedMotion()

  return (
    <motion.div
      animate={
        shouldReduceMotion
          ? { opacity: isOpen ? 1 : 0 } // reduce transforms
          : { x: isOpen ? 0 : "-100%" }
      }
    />
  )
}

[1][2]

2) Automatic (site-wide): <MotionConfig reducedMotion="user" />

If you want Motion to automatically comply across your app, set:

import { MotionConfig } from "framer-motion" // v11

<MotionConfig reducedMotion="user">{children}</MotionConfig>

With "user", Motion will disable transform and layout animations while preserving other animations like opacity. [1]

Sources: Motion accessibility guide + API docs [1][2], MDN prefers-reduced-motion [3].

[1] https://motion.dev/docs/react-accessibility
[2] https://motion.dev/motion/use-reduced-motion/
[3] https://developer.mozilla.org/en-US/docs/Web/CSS/%40media/prefers-reduced-motion


🏁 Script executed:

cat ./web/package.json

Repository: Devasy/splitwiser

Length of output: 768


🏁 Script executed:

cat -n ./web/components/ui/Confetti.tsx

Repository: Devasy/splitwiser

Length of output: 2251


Honor prefers-reduced-motion to improve accessibility for motion-sensitive users.

This component is animation-heavy with entrance animations and confetti effects. Please respect user reduced-motion settings by gating confetti and disabling animations when prefers-reduced-motion is active.

Example approach using `useReducedMotion()` from framer-motion v11
-import { motion } from 'framer-motion';
+import { motion, useReducedMotion } from 'framer-motion';
@@
 export const SettledUpState = () => {
     const { style } = useTheme();
+    const reduceMotion = useReducedMotion();
@@
-            <Confetti count={100} />
+            {!reduceMotion && <Confetti count={100} />}
@@
             <motion.div
-                initial={{ scale: 0, rotate: -180 }}
-                animate={{ scale: 1, rotate: 0 }}
-                transition={{
-                    type: "spring",
-                    stiffness: 260,
-                    damping: 20,
-                    delay: 0.1
-                }}
+                initial={reduceMotion ? false : { scale: 0, rotate: -180 }}
+                animate={{ scale: 1, rotate: 0 }}
+                transition={
+                    reduceMotion
+                        ? { duration: 0 }
+                        : { type: "spring", stiffness: 260, damping: 20, delay: 0.1 }
+                }
@@
             <motion.div
-                initial={{ opacity: 0, scale: 0.5 }}
-                animate={{ opacity: 1, scale: 1 }}
-                transition={{ delay: 0.2 }}
+                initial={reduceMotion ? false : { opacity: 0, scale: 0.5 }}
+                animate={{ opacity: 1, scale: 1 }}
+                transition={reduceMotion ? { duration: 0 } : { delay: 0.2 }}
@@
             <motion.div
-                initial={{ opacity: 0, y: 20 }}
-                animate={{ opacity: 1, y: 0 }}
-                transition={{ delay: 0.3 }}
+                initial={reduceMotion ? false : { opacity: 0, y: 20 }}
+                animate={{ opacity: 1, y: 0 }}
+                transition={reduceMotion ? { duration: 0 } : { delay: 0.3 }}

Also consider adding reduced-motion support to the Confetti component itself so it respects user preferences when used elsewhere.

🤖 Prompt for AI Agents
In `@web/components/SettledUpState.tsx` around lines 1 - 49, The component ignores
user "prefers-reduced-motion" and must respect reduced-motion settings: use
framer-motion's useReducedMotion() inside SettledUpState to detect reduced
motion, pass a flag into Confetti (e.g., reducedMotion or disableAnimation) and
conditionally render Confetti (skip or set count=0) when reduced; for the
motion.div elements (the outer icon wrapper, inner check, and text container)
use the reducedMotion flag to replace animate with initial (or set animate equal
to initial/disable transitions) so no entrance animations run when reducedMotion
is true; update the Confetti component similarly to accept and honor the prop so
it avoids rendering or animating when reducedMotion is true.

Comment on lines +9 to +18
interface Particle {
id: number;
x: number;
y: number;
color: string;
size: number;
rotation: number;
duration: number;
delay: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stabilize particle randomness to avoid jitter on re-renders.

Math.random() in render (x drift and borderRadius) changes on every re-render, so particles can jump or re‑animate unexpectedly. Store those values in Particle so they’re stable for the component’s lifetime.

💡 Proposed fix
 interface Particle {
   id: number;
   x: number;
   y: number;
   color: string;
   size: number;
   rotation: number;
   duration: number;
   delay: number;
+  drift: number;
+  isCircle: boolean;
 }
@@
     const newParticles: Particle[] = Array.from({ length: count }).map((_, i) => ({
       id: i,
       x: Math.random() * 100, // percent
       y: -10, // start above
       color: COLORS[Math.floor(Math.random() * COLORS.length)],
       size: Math.random() * 8 + 4,
       rotation: Math.random() * 360,
       duration: Math.random() * 2 + 2,
       delay: Math.random() * 0.5,
+      drift: Math.random() * 20 - 10,
+      isCircle: Math.random() > 0.5,
     }));
@@
           animate={{
             y: '105vh',
             rotate: p.rotation + 720,
-            x: `${p.x + (Math.random() * 20 - 10)}vw`,
+            x: `${p.x + p.drift}vw`,
             opacity: 0,
           }}
@@
           style={{
             position: 'absolute',
             width: p.size,
             height: p.size,
             backgroundColor: p.color,
-            borderRadius: Math.random() > 0.5 ? '50%' : '0%',
+            borderRadius: p.isCircle ? '50%' : '0%',
           }}

Also applies to: 24-60

🤖 Prompt for AI Agents
In `@web/components/ui/Confetti.tsx` around lines 9 - 18, The Particle interface
and Confetti rendering use Math.random() during render which causes particle
positions and border radii to change on re-renders; modify the Particle type to
include stable precomputed fields (e.g., initialX, initialY, borderRadius,
initialRotation, duration, delay) and generate these random values once when
creating particles (inside a createInitialParticles function, useEffect, or
useRef in the Confetti component) so the render uses particle.initialX /
particle.borderRadius etc. instead of calling Math.random() inline; update any
places referencing x, y, rotation or border radius to use the new stored
properties on Particle (and remove runtime Math.random calls from the JSX) to
prevent jitter.

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.

2 participants