Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
## [Unreleased]

### Added
- **Web Success Celebration:** Implemented a delightful confetti animation when users settle up.
- **Features:**
- Fires fireworks animation when a payment is recorded.
- Shows a "Celebrate!" button with confetti burst in the "All Settled Up" empty state.
- Uses `canvas-confetti` library for lightweight, high-performance particles.
- **Technical:** Created `web/hooks/useConfetti.ts` hook. Integrated into `web/pages/GroupDetails.tsx`.

- **Mobile Accessibility:** Completed accessibility audit for all mobile screens.
- **Features:**
- Added `accessibilityLabel` to all interactive elements (buttons, inputs, list items).
Expand Down
9 changes: 7 additions & 2 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@

### Web

- [ ] **[ux]** Animated success celebration when settled up
- File: `web/pages/GroupDetails.tsx`
- [x] **[ux]** Animated success celebration when settled up
- Completed: 2026-02-04
- Files: `web/hooks/useConfetti.ts`, `web/pages/GroupDetails.tsx`
- Context: Show confetti or checkmark animation when no debts
- Impact: Delightful moment, positive reinforcement
- Size: ~45 lines
Expand Down Expand Up @@ -163,3 +164,7 @@
- Completed: 2026-01-21
- Files modified: `mobile/screens/HomeScreen.js`, `mobile/screens/GroupDetailsScreen.js`, `mobile/screens/FriendsScreen.js`
- Impact: Native feel, users can easily refresh data
- [x] **[ux]** Animated success celebration when settled up
- Completed: 2026-02-04
- Files modified: `web/hooks/useConfetti.ts`, `web/pages/GroupDetails.tsx`
- Impact: Delightful moment when users settle up debts
58 changes: 58 additions & 0 deletions web/hooks/useConfetti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import confetti from 'canvas-confetti';
import { useCallback, useEffect, useRef } from 'react';

export const useConfetti = () => {
const fireworksIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

// Cleanup interval on unmount
useEffect(() => {
return () => {
if (fireworksIntervalRef.current) {
clearInterval(fireworksIntervalRef.current);
}
};
}, []);

const fireConfetti = useCallback(() => {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
colors: ['#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff'],
disableForReducedMotion: true,
});
}, []);

const fireFireworks = useCallback(() => {
// Clear any existing interval
if (fireworksIntervalRef.current) {
clearInterval(fireworksIntervalRef.current);
}

const duration = 5 * 1000;
const animationEnd = Date.now() + duration;
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0, disableForReducedMotion: true };

const randomInRange = (min: number, max: number) => {
return Math.random() * (max - min) + min;
};

fireworksIntervalRef.current = setInterval(function () {
const timeLeft = animationEnd - Date.now();

if (timeLeft <= 0) {
if (fireworksIntervalRef.current) {
clearInterval(fireworksIntervalRef.current);
}
return;
}

const particleCount = 50 * (timeLeft / duration);
// since particles fall down, start a bit higher than random
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } });
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } });
}, 250);
}, []);

return { fireConfetti, fireFireworks };
};
Loading
Loading