Skip to content

Commit

Permalink
fix: gem sidebar animations (#1241)
Browse files Browse the repository at this point in the history
Description
---

- cleaned up animation timeouts (couldn't repro the original issue but
there was an issue of the animaions not actually going away which was
reporoducable every time)
- cleaned up the `useAirdropUserPointsListener` listener and how the
zustand store was updated there:
- biggest one being the check against the count from the payload vs the
store - made sure to only update the store after
- added triggers for the animations for very BASIC testing in the admin
ui (not that helpful since it's not directly related to the points)

Motivation and Context
---
- resolves an issue picked up when checking
#1238, but should also
hopefully resolve that issue. couldn't reproduce but it's all related to
the listener and the timeouts being weird 😬

How Has This Been Tested?
---

- locally, pointing to airdrop dev BE and adding the referrals (and
target for the extra animation!) manually to trigger the points
listener:


https://github.com/user-attachments/assets/db2b2049-d9f1-4398-a6f5-e1296430a56a



What process can a PR reviewer use to test or verify this change?
---

little bit tough if you don't have access to the BE (dev!) admin for
testing, but you can also just make sure your local dev build is
pointing to airdrop dev, then on a separate dev build use your referral
code, and wait for the points to test
  • Loading branch information
shanimal08 authored Dec 12, 2024
1 parent bfb8de8 commit 3eebae1
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 111 deletions.
11 changes: 9 additions & 2 deletions src/components/AdminUI/groups/OtherUIGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import { useUIStore } from '@app/store/useUIStore';
import { useShellOfSecretsStore } from '../../../store/useShellOfSecretsStore';
import { Button, ButtonGroup, CategoryLabel } from '../styles';
import { useAirdropStore } from '@app/store/useAirdropStore.ts';

export function OtherUIGroup() {
const setAdminShow = useUIStore((s) => s.setAdminShow); // prevent messing up the actual setup progress value
const adminShow = useUIStore((s) => s.adminShow);
const { showWidget, setShowWidget } = useShellOfSecretsStore();

const setFlare = useAirdropStore((s) => s.setFlareAnimationType);
return (
<>
<CategoryLabel>Other UI</CategoryLabel>
Expand All @@ -24,7 +25,13 @@ export function OtherUIGroup() {
>
Orphan chain warning
</Button>
{/* TODO: add the other sections if we want */}
</ButtonGroup>
<CategoryLabel>Gem animations</CategoryLabel>
{/* TODO: add the other sections if we want */}
<ButtonGroup>
<Button onClick={() => setFlare('FriendAccepted')}>FriendAccepted</Button>
<Button onClick={() => setFlare('GoalComplete')}>GoalComplete</Button>
<Button onClick={() => setFlare('BonusGems')}>BonusGems</Button>
</ButtonGroup>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,40 @@ import { AnimatePresence } from 'framer-motion';

export default function LoggedIn() {
const [gems, setGems] = useState(0);

const {
userDetails,
userPoints,
flareAnimationType,
bonusTiers,
setFlareAnimationType,
referralQuestPoints,
miningRewardPoints,
} = useAirdropStore();
const { userRankGems, userPointsGems, flareAnimationType, bonusTiers, referralGems, miningRewardPoints } =
useAirdropStore((s) => ({
userRankGems: s.userDetails?.user?.rank?.gems,
userPointsGems: s.userPoints?.base?.gems,
flareAnimationType: s.flareAnimationType,
bonusTiers: s.bonusTiers,
referralGems: s.referralQuestPoints?.pointsForClaimingReferral || REFERRAL_GEMS,
miningRewardPoints: s.miningRewardPoints,
}));

useEffect(() => {
setGems(userPoints?.base?.gems || userDetails?.user?.rank?.gems || 0);
}, [userPoints?.base?.gems, userDetails?.user?.rank?.gems]);
setGems(userPointsGems || userRankGems || 0);
}, [userPointsGems, userRankGems]);

const bonusTier = useMemo(
() =>
bonusTiers
?.sort((a, b) => a.target - b.target)
.find((t) => t.target == (userPoints?.base?.gems || userDetails?.user?.rank?.gems || 0)),
[bonusTiers, userDetails?.user?.rank?.gems, userPoints?.base?.gems]
.find((t) => t.target == (userPointsGems || userRankGems || 0)),
[bonusTiers, userRankGems, userPointsGems]
);

const flareGems = useMemo(() => {
switch (flareAnimationType) {
case 'GoalComplete':
return bonusTier?.bonusGems || 0;
case 'FriendAccepted':
return referralQuestPoints?.pointsForClaimingReferral || REFERRAL_GEMS;
return referralGems;
case 'BonusGems':
return miningRewardPoints?.reward || 0;
default:
return 0;
}
}, [
flareAnimationType,
bonusTier?.bonusGems,
referralQuestPoints?.pointsForClaimingReferral,
miningRewardPoints?.reward,
]);
}, [flareAnimationType, bonusTier?.bonusGems, referralGems, miningRewardPoints?.reward]);

return (
<Wrapper>
Expand All @@ -60,14 +54,7 @@ export default function LoggedIn() {
<Invite />

<AnimatePresence>
{flareAnimationType && (
<Flare
gems={flareGems}
animationType={flareAnimationType}
onAnimationComplete={() => setFlareAnimationType()}
onClick={() => setFlareAnimationType()}
/>
)}
{flareAnimationType ? <Flare gems={flareGems} animationType={flareAnimationType} /> : null}
</AnimatePresence>
</Wrapper>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

import GemsAnimation from '../GemsAnimation/GemsAnimation';
Expand All @@ -8,21 +7,12 @@ import { Background, Wrapper } from './styles';

interface Props {
gems: number;
onAnimationComplete: () => void;
}

export default function BonusGems({ gems, onAnimationComplete }: Props) {
export default function BonusGems({ gems }: Props) {
const { t } = useTranslation('airdrop', { useSuspense: false });
const formattedNumber = formatNumber(gems, FormatPreset.DECIMAL_COMPACT);

useEffect(() => {
const timer = setTimeout(() => {
onAnimationComplete();
}, 3500);

return () => clearTimeout(timer);
}, [onAnimationComplete]);

return (
<Wrapper>
<Number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,46 @@ import BonusGems from './BonusGems/BonusGems';
import FriendAccepted from './FriendAccepted/FriendAccepted';
import GoalComplete from './GoalComplete/GoalComplete';
import { Wrapper } from './styles';
import { useAirdropStore } from '@app/store/useAirdropStore.ts';
import { useCallback, useEffect } from 'react';

interface Props {
gems: number;
animationType: 'GoalComplete' | 'FriendAccepted' | 'BonusGems';
onAnimationComplete: () => void;
onClick: () => void;
}

export default function Flare({ gems, animationType, onAnimationComplete, onClick }: Props) {
return (
<Wrapper initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={onClick}>
{animationType === 'GoalComplete' && <GoalComplete gems={gems} onAnimationComplete={onAnimationComplete} />}
const GOAL_COMPLETE_DURATION = 1000 * 11.5;
const REFERRAL_DURATION = 1000 * 10;
const BONUS_GEMS_DURATION = 3500;

const durations = {
GoalComplete: GOAL_COMPLETE_DURATION,
FriendAccepted: REFERRAL_DURATION,
BonusGems: BONUS_GEMS_DURATION,
};

{animationType === 'FriendAccepted' && (
<FriendAccepted gems={gems} onAnimationComplete={onAnimationComplete} />
)}
export default function Flare({ gems, animationType }: Props) {
const setFlareAnimationType = useAirdropStore((s) => s.setFlareAnimationType);
const clearFlareAnimationType = useCallback(() => setFlareAnimationType(), [setFlareAnimationType]);

{animationType === 'BonusGems' && <BonusGems gems={gems} onAnimationComplete={onAnimationComplete} />}
useEffect(() => {
const duration = durations[animationType] || 0;
const animationTimeout = setTimeout(clearFlareAnimationType, duration);
return () => {
clearTimeout(animationTimeout);
};
}, [animationType, clearFlareAnimationType]);

return (
<Wrapper
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={clearFlareAnimationType}
>
{animationType === 'GoalComplete' && <GoalComplete gems={gems} />}
{animationType === 'FriendAccepted' && <FriendAccepted gems={gems} />}
{animationType === 'BonusGems' && <BonusGems gems={gems} />}
</Wrapper>
);
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import { useEffect } from 'react';
import GemsAnimation from '../GemsAnimation/GemsAnimation';
import { Background, Wrapper } from './styles';
import { Number, Text, TextBottom, TextBottomPosition } from '../styles';
import { useTranslation } from 'react-i18next';

interface Props {
gems: number;
onAnimationComplete: () => void;
}

export default function FriendAccepted({ gems, onAnimationComplete }: Props) {
export default function FriendAccepted({ gems }: Props) {
const { t } = useTranslation('airdrop', { useSuspense: false });

useEffect(() => {
const timer = setTimeout(() => {
onAnimationComplete();
}, 10000);

return () => clearTimeout(timer);
}, [onAnimationComplete]);

return (
<Wrapper>
<Number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,25 @@ import { useTranslation } from 'react-i18next';

interface Props {
gems: number;
onAnimationComplete: () => void;
}

export default function FriendAccepted({ gems, onAnimationComplete }: Props) {
export default function GoalComplete({ gems }: Props) {
const { t } = useTranslation('airdrop', { useSuspense: false });
const [showIntro, setShowIntro] = useState(true);
const [showGiftBox, setShowGiftBox] = useState(true);

const introDuration = 2000;
const mainDuration = 11500;

useEffect(() => {
const introTimer = setTimeout(() => {
setShowIntro(false);
setShowGiftBox(false);
}, introDuration);

const mainTimer = setTimeout(() => {
onAnimationComplete();
}, mainDuration);

return () => {
clearTimeout(introTimer);
clearTimeout(mainTimer);
};
}, [onAnimationComplete]);
}, []);

return (
<Wrapper>
Expand Down
61 changes: 24 additions & 37 deletions src/hooks/airdrop/stateHelpers/useAirdropUserPointsListener.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,38 @@
import { useAirdropStore, UserPoints } from '@app/store/useAirdropStore';
import { listen } from '@tauri-apps/api/event';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';

export const useAirdropUserPointsListener = () => {
const setUserPoints = useAirdropStore((state) => state.setUserPoints);
const referralCount = useAirdropStore((state) => state.referralCount);
const setUserPoints = useAirdropStore((state) => state?.setUserPoints);
const currentReferralData = useAirdropStore((state) => state?.referralCount);
const bonusTiers = useAirdropStore((state) => state.bonusTiers);
const setUserPointsReferralCount = useAirdropStore((state) => state.setReferralCount);
const setFlareAnimationType = useAirdropStore((state) => state.setFlareAnimationType);

useEffect(() => {
let unListen: () => void = () => {
//do nothing
};
const handleAirdropPoints = useCallback(
(pointsPayload: UserPoints) => {
const incomingReferralData = pointsPayload?.referralCount;
if (incomingReferralData?.count && incomingReferralData?.count !== currentReferralData?.count) {
setFlareAnimationType('FriendAccepted');

listen('UserPoints', (event) => {
if (event.payload) {
const payload = event.payload as UserPoints;
setUserPoints(payload);
if (payload.referralCount) {
if (referralCount?.count !== payload.referralCount.count) {
if (referralCount?.count) {
setFlareAnimationType('FriendAccepted');
if (
payload.referralCount.count &&
bonusTiers?.find((t) => t.target === payload?.referralCount?.count)
) {
setTimeout(() => {
setFlareAnimationType('GoalComplete');
}, 2000);
}
}
setUserPointsReferralCount(payload.referralCount);
}
const goalComplete = bonusTiers?.find((t) => t.target === incomingReferralData?.count);
if (goalComplete) {
setTimeout(() => setFlareAnimationType('GoalComplete'), 3000);
}

setUserPoints(pointsPayload);
}
})
.then((unListenFunction) => {
unListen = unListenFunction;
})
.catch((e) => {
console.error('User points error: ', e);
});
},
[bonusTiers, currentReferralData?.count, setFlareAnimationType, setUserPoints]
);

useEffect(() => {
const ul = listen('UserPoints', ({ payload }) => {
if (payload) {
handleAirdropPoints(payload as UserPoints);
}
});
return () => {
unListen();
ul.then((unlisten) => unlisten());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bonusTiers, referralCount?.count]);
}, [handleAirdropPoints]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface QuestDataResponse {

export const useGetReferralQuestPoints = () => {
const handleRequest = useAirdropRequest();
const { setReferralQuestPoints } = useAirdropStore();
const setReferralQuestPoints = useAirdropStore((s) => s.setReferralQuestPoints);

useEffect(() => {
const handleFetch = async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/store/useAirdropStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export const useAirdropStore = create<AirdropStore>()(
}
},
setReferralCount: (referralCount) => set({ referralCount }),
setUserPoints: (userPoints) => set({ userPoints }),
setUserPoints: (userPoints) => set({ userPoints, referralCount: userPoints?.referralCount }),
setUserGems: (userGems: number) =>
set((state) => {
const userPointsFormatted = {
Expand Down

0 comments on commit 3eebae1

Please sign in to comment.