Add coin-flies-to-wallet animation to the gift reveal modal

Web mirror of the RN change — same timing constants, same idea: the amount
flies from its pop-in position toward a wallet badge that pulses on
arrival, and the "credited" hint fades in at that moment instead of
showing immediately. Wallet crediting itself was already correctly
deferred server-side until POST /gifts/:id/open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
payacom
2026-09-16 14:22:51 +03:30
parent e652c255c9
commit 71da2b53fe
3 changed files with 56 additions and 10 deletions

View File

@@ -7,6 +7,11 @@ import RoundedButton from "@/components/elements/RoundedButton";
import { useTranslation } from "react-i18next";
const PARTICLES = ["🎉", "🎊", "✨", "🎈", "🎁"];
// طبقِ خواسته‌ی کاربر: بعدِ باز شدنِ کادو، مبلغ به‌صورتِ انیمیشنی «پرواز» می‌کنه و توی
// آیکونِ کیف‌پول فرود میاد — همون زمان‌بندی نسخه‌ی اندروید (GiftRevealModal.tsx: Reanimated)
const COIN_FLY_DELAY_S = 0.75;
const COIN_FLY_DURATION_S = 0.55;
const WALLET_ARRIVAL_S = COIN_FLY_DELAY_S + COIN_FLY_DURATION_S;
type Particle = {
id: number;
@@ -89,14 +94,46 @@ export default function GiftRevealModal({
🎁
</motion.div>
<motion.p
initial={{ scale: 0.6, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, type: "spring", stiffness: 220, damping: 14 }}
className="text-2xl font-extrabold text-white drop-shadow"
<div className="relative flex items-center justify-center">
<motion.p
initial={{ scale: 0.6, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, type: "spring", stiffness: 220, damping: 14 }}
className="text-2xl font-extrabold text-white drop-shadow"
>
{amount.toLocaleString()} {t("settings.toman")}
</motion.p>
<motion.span
initial={{ opacity: 1, y: 6, scale: 1 }}
animate={{ y: 96, scale: 0.45, opacity: 0 }}
transition={{
delay: COIN_FLY_DELAY_S,
duration: COIN_FLY_DURATION_S,
ease: "easeIn",
}}
className="pointer-events-none absolute top-1 text-2xl"
>
🪙
</motion.span>
</div>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.25, type: "spring", stiffness: 240, damping: 18 }}
className="flex items-center gap-1.5 rounded-full bg-white/10 px-3.5 py-1.5"
>
{amount.toLocaleString()} {t("settings.toman")}
</motion.p>
<motion.span
animate={{ scale: [1, 1, 1.25, 1] }}
transition={{ delay: WALLET_ARRIVAL_S, duration: 0.35, times: [0, 0.4, 0.7, 1] }}
className="text-lg"
>
👛
</motion.span>
<span className="text-xs font-medium text-white">
{t("chats.gift.walletLabel")}
</span>
</motion.div>
{message ? (
<motion.p
@@ -109,7 +146,14 @@ export default function GiftRevealModal({
</motion.p>
) : null}
<p className="text-xs text-white/70">{t("chats.gift.creditedHint")}</p>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: WALLET_ARRIVAL_S, duration: 0.3 }}
className="text-xs text-white/70"
>
{t("chats.gift.creditedHint")}
</motion.p>
<RoundedButton
type="button"

View File

@@ -1923,7 +1923,8 @@
"tapToOpen": "Tap to open",
"sentSuccess": "Your gift was sent",
"sending": "Sending…",
"creditedHint": "This amount was added to your wallet"
"creditedHint": "This amount was added to your wallet",
"walletLabel": "Wallet"
},
"timedBanner": "Timed message — auto-delete after {{duration}}",
"viewOnceBanner": "{{label}} — recipient can view only once",

View File

@@ -1923,7 +1923,8 @@
"tapToOpen": "برای باز کردن لمس کنید",
"sentSuccess": "هدیه شما ارسال شد",
"sending": "در حال ارسال…",
"creditedHint": "این مبلغ به کیف پول شما اضافه شد"
"creditedHint": "این مبلغ به کیف پول شما اضافه شد",
"walletLabel": "کیف پول"
},
"timedBanner": "پیام زماندار — حذف خودکار بعد از {{duration}}",
"viewOnceBanner": "{{label}} — گیرنده فقط یک‌بار می‌تواند ببیند",