feat(game): 添加钱包余额刷新功能
- 在桌面端和移动端头部组件中添加余额刷新按钮 - 实现 useWalletBalanceRefresh 钩子用于余额刷新逻辑 - 添加刷新图标并集成到钻石币显示区域 - 支持多语言本地化包括英语、印尼语、马来语和中文 - 优化消息广播组件的动画过渡效果 - 添加防重复提交机制避免并发刷新请求 - 集成加载状态显示和错误处理通知功能
This commit is contained in:
@@ -120,7 +120,7 @@ export function MessageBroadcast({
|
|||||||
src={broadcast}
|
src={broadcast}
|
||||||
/>
|
/>
|
||||||
<div className="relative h-design-16 min-w-0 flex-1 overflow-hidden md:h-design-28">
|
<div className="relative h-design-16 min-w-0 flex-1 overflow-hidden md:h-design-28">
|
||||||
<AnimatePresence>
|
<AnimatePresence mode="wait">
|
||||||
{activeBroadcast ? (
|
{activeBroadcast ? (
|
||||||
<motion.div
|
<motion.div
|
||||||
aria-label={activeBroadcastLabel}
|
aria-label={activeBroadcastLabel}
|
||||||
@@ -136,7 +136,10 @@ export function MessageBroadcast({
|
|||||||
? { opacity: 1 }
|
? { opacity: 1 }
|
||||||
: { opacity: 1, x: '-108%' }
|
: { opacity: 1, x: '-108%' }
|
||||||
}
|
}
|
||||||
exit={{ opacity: 0 }}
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.15, ease: 'easeOut' },
|
||||||
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: prefersReducedMotion
|
duration: prefersReducedMotion
|
||||||
? 0.18
|
? 0.18
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Mail,
|
Mail,
|
||||||
Maximize,
|
Maximize,
|
||||||
Minimize,
|
Minimize,
|
||||||
|
RefreshCw,
|
||||||
UserKey,
|
UserKey,
|
||||||
UserRoundPlus,
|
UserRoundPlus,
|
||||||
Volume2,
|
Volume2,
|
||||||
@@ -17,6 +18,8 @@ import diamond from '@/assets/system/diamond.webp'
|
|||||||
import logo from '@/assets/system/logo.webp'
|
import logo from '@/assets/system/logo.webp'
|
||||||
import { SmartImage } from '@/components/smart-image.tsx'
|
import { SmartImage } from '@/components/smart-image.tsx'
|
||||||
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
|
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
|
||||||
|
import { useWalletBalanceRefresh } from '@/hooks/use-wallet-balance-refresh'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { useModalStore } from '@/store'
|
import { useModalStore } from '@/store'
|
||||||
|
|
||||||
function HeaderClock() {
|
function HeaderClock() {
|
||||||
@@ -60,6 +63,8 @@ function SignalBars({
|
|||||||
export function DesktopHeader() {
|
export function DesktopHeader() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const setModalOpen = useModalStore((state) => state.setModalOpen)
|
const setModalOpen = useModalStore((state) => state.setModalOpen)
|
||||||
|
const { isRefreshingBalance, refreshWalletBalance } =
|
||||||
|
useWalletBalanceRefresh()
|
||||||
const {
|
const {
|
||||||
authStatus,
|
authStatus,
|
||||||
currentLanguageLabel,
|
currentLanguageLabel,
|
||||||
@@ -217,10 +222,7 @@ export function DesktopHeader() {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<div className="group relative flex items-center justify-center">
|
||||||
type="button"
|
|
||||||
className="group relative flex items-center justify-center transition-transform duration-150"
|
|
||||||
>
|
|
||||||
<SmartImage
|
<SmartImage
|
||||||
src={diamond}
|
src={diamond}
|
||||||
alt="diamond"
|
alt="diamond"
|
||||||
@@ -234,14 +236,32 @@ export function DesktopHeader() {
|
|||||||
>
|
>
|
||||||
<div className="truncate">{currentUser?.coin || '--'}</div>
|
<div className="truncate">{currentUser?.coin || '--'}</div>
|
||||||
|
|
||||||
<SmartImage
|
<button
|
||||||
onClick={onOpenProcedures}
|
type="button"
|
||||||
className={'w-design-24 h-design-24 cursor-pointer'}
|
aria-label={t('commonUi.actions.refreshBalance')}
|
||||||
alt={'add'}
|
title={t('commonUi.actions.refreshBalance')}
|
||||||
src={add}
|
disabled={isRefreshingBalance}
|
||||||
|
onClick={() => void refreshWalletBalance()}
|
||||||
|
className="flex h-design-24 w-design-24 shrink-0 cursor-pointer items-center justify-center rounded-[3px] text-[#8DE4EA] transition-colors hover:bg-white/10 disabled:cursor-wait disabled:opacity-65"
|
||||||
|
>
|
||||||
|
<RefreshCw
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
'h-design-16 w-design-16',
|
||||||
|
isRefreshingBalance && 'animate-spin',
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onOpenProcedures}
|
||||||
|
className="h-design-24 w-design-24 shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
|
<SmartImage className="h-full w-full" alt="add" src={add} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Info,
|
Info,
|
||||||
Mail,
|
Mail,
|
||||||
|
RefreshCw,
|
||||||
UserKey,
|
UserKey,
|
||||||
UserRoundPlus,
|
UserRoundPlus,
|
||||||
Volume2,
|
Volume2,
|
||||||
@@ -15,6 +16,7 @@ import diamond from '@/assets/system/diamond.webp'
|
|||||||
import logo from '@/assets/system/logo.webp'
|
import logo from '@/assets/system/logo.webp'
|
||||||
import { SmartImage } from '@/components/smart-image.tsx'
|
import { SmartImage } from '@/components/smart-image.tsx'
|
||||||
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
|
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
|
||||||
|
import { useWalletBalanceRefresh } from '@/hooks/use-wallet-balance-refresh'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useModalStore } from '@/store'
|
import { useModalStore } from '@/store'
|
||||||
|
|
||||||
@@ -68,6 +70,8 @@ function SignalBars({
|
|||||||
export function MobileHeader() {
|
export function MobileHeader() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const setModalOpen = useModalStore((state) => state.setModalOpen)
|
const setModalOpen = useModalStore((state) => state.setModalOpen)
|
||||||
|
const { isRefreshingBalance, refreshWalletBalance } =
|
||||||
|
useWalletBalanceRefresh()
|
||||||
const {
|
const {
|
||||||
authStatus,
|
authStatus,
|
||||||
currentLanguageLabel,
|
currentLanguageLabel,
|
||||||
@@ -133,11 +137,7 @@ export function MobileHeader() {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<div className="group relative flex min-w-0 items-center justify-center">
|
||||||
type="button"
|
|
||||||
onClick={onOpenProcedures}
|
|
||||||
className="group relative flex min-w-0 items-center justify-center transition-transform duration-150 hover:-translate-y-[1px] active:translate-y-[1px]"
|
|
||||||
>
|
|
||||||
<SmartImage
|
<SmartImage
|
||||||
src={diamond}
|
src={diamond}
|
||||||
alt="diamond"
|
alt="diamond"
|
||||||
@@ -150,15 +150,37 @@ export function MobileHeader() {
|
|||||||
<span className="min-w-0 truncate">
|
<span className="min-w-0 truncate">
|
||||||
{currentUser?.coin || '--'}
|
{currentUser?.coin || '--'}
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={t('commonUi.actions.refreshBalance')}
|
||||||
|
title={t('commonUi.actions.refreshBalance')}
|
||||||
|
disabled={isRefreshingBalance}
|
||||||
|
onClick={() => void refreshWalletBalance()}
|
||||||
|
className="flex h-design-13 w-design-13 shrink-0 cursor-pointer items-center justify-center text-[#8DE4EA] disabled:cursor-wait disabled:opacity-65"
|
||||||
|
>
|
||||||
|
<RefreshCw
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
'h-design-9 w-design-9',
|
||||||
|
isRefreshingBalance && 'animate-spin',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onOpenProcedures}
|
||||||
|
className="h-design-13 w-design-13 shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
<SmartImage
|
<SmartImage
|
||||||
src={add}
|
src={add}
|
||||||
alt="add"
|
alt="add"
|
||||||
priority
|
priority
|
||||||
className="h-design-13 w-design-13 shrink-0 cursor-pointer"
|
className="h-full w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full shrink-0 items-center justify-end gap-design-8 px-design-9">
|
<div className="flex h-full shrink-0 items-center justify-end gap-design-8 px-design-9">
|
||||||
<button
|
<button
|
||||||
|
|||||||
45
src/hooks/use-wallet-balance-refresh.ts
Normal file
45
src/hooks/use-wallet-balance-refresh.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { getCurrentUserProfile } from '@/api'
|
||||||
|
import { notify } from '@/lib/notify'
|
||||||
|
import { useAuthStore } from '@/store'
|
||||||
|
|
||||||
|
export function useWalletBalanceRefresh() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const setCurrentUser = useAuthStore((state) => state.setCurrentUser)
|
||||||
|
const [isRefreshingBalance, setIsRefreshingBalance] = useState(false)
|
||||||
|
const refreshPromiseRef = useRef<Promise<void> | null>(null)
|
||||||
|
|
||||||
|
const refreshWalletBalance = useCallback(() => {
|
||||||
|
if (refreshPromiseRef.current) {
|
||||||
|
return refreshPromiseRef.current
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRefreshingBalance(true)
|
||||||
|
|
||||||
|
const refreshPromise = getCurrentUserProfile()
|
||||||
|
.then((currentUser) => {
|
||||||
|
setCurrentUser(currentUser)
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
notify.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: t('commonUi.toast.requestFailed'),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
refreshPromiseRef.current = null
|
||||||
|
setIsRefreshingBalance(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
refreshPromiseRef.current = refreshPromise
|
||||||
|
|
||||||
|
return refreshPromise
|
||||||
|
}, [setCurrentUser, t])
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRefreshingBalance,
|
||||||
|
refreshWalletBalance,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -479,6 +479,7 @@ export default {
|
|||||||
},
|
},
|
||||||
commonUi: {
|
commonUi: {
|
||||||
actions: {
|
actions: {
|
||||||
|
refreshBalance: 'Refresh balance',
|
||||||
submitting: TEXT_SUBMITTING,
|
submitting: TEXT_SUBMITTING,
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
|
|||||||
@@ -478,6 +478,7 @@ export default {
|
|||||||
},
|
},
|
||||||
commonUi: {
|
commonUi: {
|
||||||
actions: {
|
actions: {
|
||||||
|
refreshBalance: 'Segarkan saldo',
|
||||||
submitting: TEXT_SUBMITTING,
|
submitting: TEXT_SUBMITTING,
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
|
|||||||
@@ -481,6 +481,7 @@ export default {
|
|||||||
},
|
},
|
||||||
commonUi: {
|
commonUi: {
|
||||||
actions: {
|
actions: {
|
||||||
|
refreshBalance: 'Segarkan baki',
|
||||||
submitting: TEXT_SUBMITTING,
|
submitting: TEXT_SUBMITTING,
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
|
|||||||
@@ -462,6 +462,7 @@ export default {
|
|||||||
},
|
},
|
||||||
commonUi: {
|
commonUi: {
|
||||||
actions: {
|
actions: {
|
||||||
|
refreshBalance: '刷新余额',
|
||||||
submitting: TEXT_SUBMITTING,
|
submitting: TEXT_SUBMITTING,
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
|
|||||||
Reference in New Issue
Block a user