feat(org): add celebration confetti when joining or creating organization

Add a celebratory experience when users join or create an organization:
- Add canvas-confetti dependency for visual effects
- Store organization name in localStorage after successful join/create
- Display celebration dialog with confetti animation on ProfilePage
- Clear the celebration flag after showing to prevent repeat displays
This commit is contained in:
2026-04-21 17:11:05 +09:30
parent 5fc24b7a42
commit 78ac65169e
5 changed files with 73 additions and 5 deletions
+50 -1
View File
@@ -18,7 +18,21 @@ import {
import { useAuth } from "@/contexts/AuthContext";
import { api, ApiError, PendingInvite } from "@/lib/api";
import { toast } from "@/hooks/use-toast";
import { useNavigate } from "react-router-dom";
import { useNavigate, useLocation } from "react-router-dom";
import confetti from "canvas-confetti";
// Wrapper to handle confetti failures gracefully
const fireConfetti = () => {
try {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
});
} catch (e) {
console.warn('Confetti failed:', e);
}
};
function ProfileSkeleton() {
return (
@@ -68,6 +82,7 @@ function ProfileSkeleton() {
export default function ProfilePage() {
const { user, isLoading: authLoading, refreshUser, logout } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const [name, setName] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [isSaving, setIsSaving] = useState(false);
@@ -79,6 +94,10 @@ export default function ProfilePage() {
const [isDeleting, setIsDeleting] = useState(false);
const [confirmEmail, setConfirmEmail] = useState("");
// Celebration dialog state
const [showCelebration, setShowCelebration] = useState(false);
const [celebrationOrgName, setCelebrationOrgName] = useState("");
// Sync local name state with user data
useEffect(() => {
if (user?.full_name) {
@@ -86,6 +105,17 @@ export default function ProfilePage() {
}
}, [user?.full_name]);
// Check for org creation/join celebration
useEffect(() => {
const fromStorage = localStorage.getItem('justJoinedOrg');
if (fromStorage) {
setCelebrationOrgName(fromStorage);
setShowCelebration(true);
fireConfetti();
localStorage.removeItem('justJoinedOrg');
}
}, [location.pathname]);
// Fetch pending invitations for this user
useEffect(() => {
if (!user) return;
@@ -477,6 +507,25 @@ export default function ProfilePage() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Celebration dialog for org creation/join */}
<Dialog open={showCelebration} onOpenChange={setShowCelebration}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-center text-xl">
🎉 Congratulations!
</DialogTitle>
<DialogDescription className="text-center text-base">
You've joined <span className="font-semibold">{celebrationOrgName}</span>!
</DialogDescription>
</DialogHeader>
<div className="flex justify-center py-4">
<Button onClick={() => setShowCelebration(false)}>
Get Started
</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
}