Files
36-character-flower/src/features/game/components/mobile/mobile-header.tsx
JiaJun 7999a5d709 refactor(types): 统一类型导入导出管理
- 将类型定义从各个模块统一到 type 文件中进行管理
- 移除 auth-session 中不再使用的 AuthSessionInput 和 AuthUser 类型导入
- 移除 game store 中多余的类型导入如 BetSelection、StartAutoHostingInput 等
- 将 i18n 模块中的 AppLanguage 类型改为从 type 文件导入
- 移除 mobile-header 中未使用的 MessageBroadcast 组件导入
- 统一各组件中的类型引用路径,全部指向 type 文件
- 修复 withdraw 组件中 currencies 映射的类型注解问题
- 更新 modal-store 中移除未使用的 ModalKey 类型导入
2026-06-04 18:14:56 +08:00

284 lines
9.6 KiB
TypeScript

import {
Info,
Mail,
UserKey,
UserRoundPlus,
Volume2,
VolumeX,
} from 'lucide-react'
import { motion } from 'motion/react'
import { useTranslation } from 'react-i18next'
import avatar from '@/assets/system/avatar.webp'
import chatImage from '@/assets/system/chat.webp'
import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
import { useModalStore } from '@/store'
function MobileHeaderClock() {
const systemTimeLabel = useHeaderClockLabel()
return (
<div className="mt-design-3 whitespace-nowrap text-design-7 font-bold text-[#D2FCFF]">
{systemTimeLabel}
</div>
)
}
function SignalBars({
activeBars,
toneClassName,
}: {
activeBars: number
toneClassName: string
}) {
const barHeights = [
'h-[calc(var(--design-unit)*4)]',
'h-[calc(var(--design-unit)*6)]',
'h-[calc(var(--design-unit)*8)]',
'h-[calc(var(--design-unit)*10)]',
] as const
return (
<div
className="flex h-design-11 w-design-16 items-end gap-[1px]"
aria-hidden="true"
>
{barHeights.map((heightClassName, index) => {
const isActive = index < activeBars
return (
<div
key={heightClassName}
className={[
'w-[calc(var(--design-unit)*2)] rounded-t-[1px] transition-colors',
heightClassName,
isActive ? `bg-current ${toneClassName}` : 'bg-white/18',
].join(' ')}
/>
)
})}
</div>
)
}
export function MobileHeader() {
const { t } = useTranslation()
const setModalOpen = useModalStore((state) => state.setModalOpen)
const {
authStatus,
currentLanguageLabel,
currentLanguageOption,
currentUser,
isSoundEnabled,
onOpenLanguage,
onOpenLogin,
onOpenNotice,
onOpenProcedures,
onOpenRegister,
onOpenRules,
onOpenUserInfo,
signalPresentation,
toggleSoundEnabled,
} = useHeaderVm()
const actionButtonClassName =
'common-neon-inset flex h-design-19 shrink-0 cursor-pointer items-center justify-center gap-design-5 !rounded-[3px] !px-design-5 !py-0 text-design-7 leading-none transition-opacity hover:opacity-85'
const accountButtonClassName =
'common-neon-inset flex h-design-19 items-center justify-end !rounded-[3px] !py-0 text-design-7 leading-none transition-[opacity,transform] duration-150 group-hover:opacity-90 group-active:scale-[0.98]'
return (
<header className="relative z-30 h-design-72">
<div className="border-b-2 border-[#787553] bg-[#020B14] flex h-design-33 w-full items-center px-design-10">
<div className="flex h-design-23 w-design-130 pr-design-10 shrink-0 items-center justify-center border-r border-[rgba(128,223,231,0.45)]">
<SmartImage
src={logo}
alt="logo"
priority
className="h-full w-full"
imgClassName="object-contain"
/>
</div>
{authStatus === 'authenticated' ? (
<div className="flex h-full min-w-0 flex-1 items-center justify-end gap-design-7 px-design-9">
<button
type="button"
onClick={onOpenUserInfo}
className="group relative flex min-w-0 items-center justify-center transition-transform duration-150 hover:-translate-y-[1px] active:translate-y-[1px]"
>
<SmartImage
src={avatar}
alt="avatar"
priority
className="absolute left-0 z-20 h-design-25 w-design-25 rounded-full"
/>
<div
className={`${accountButtonClassName} w-design-92 pl-design-28 pr-design-6`}
>
<span className="truncate">
{currentUser?.username || '--'}
</span>
</div>
</button>
<button
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
src={diamond}
alt="diamond"
priority
className="absolute left-0 z-20 h-design-25 w-design-25"
/>
<div
className={`${accountButtonClassName} w-design-90 gap-design-4 pl-design-26 pr-design-4`}
>
<span className="min-w-0 truncate">
{currentUser?.coin || '--'}
</span>
{/*<div className="common-neon-inset flex h-design-15 w-design-15 shrink-0 items-center justify-center !rounded-[3px] !p-0">*/}
{/* <Plus*/}
{/* aria-hidden="true"*/}
{/* className="h-design-10 w-design-10 shrink-0"*/}
{/* color="#57B8BF"*/}
{/* strokeWidth={2.5}*/}
{/* />*/}
{/*</div>*/}
</div>
</button>
</div>
) : (
<div className="flex h-full min-w-0 flex-1 items-center justify-end gap-design-8 px-design-9">
<button
type="button"
className={`${actionButtonClassName} w-design-72`}
onClick={onOpenLogin}
>
<UserKey
className="h-design-8 w-design-8 shrink-0"
color="#57B8BF"
/>
<div className="min-w-0 truncate">
{t('gameDesktop.header.login')}
</div>
</button>
<button
type="button"
className={`${actionButtonClassName} w-design-78`}
onClick={onOpenRegister}
>
<UserRoundPlus
className="h-design-8 w-design-8 shrink-0"
color="#57B8BF"
/>
<div className="min-w-0 truncate">
{t('gameDesktop.header.register')}
</div>
</button>
</div>
)}
<motion.button
type="button"
onClick={() => setModalOpen('desktopSupport', true)}
whileTap={{
scale: 0.95,
}}
whileHover={{ scale: 1.05 }}
>
<SmartImage
className={'h-design-20 w-design-20 cursor-pointer'}
alt={'chatImage'}
src={chatImage}
/>
</motion.button>
</div>
<div className={'w-full px-design-10 '}>
<div className="flex h-design-29 w-full items-center gap-design-5 my-design-5 rounded-sm border-[#0A353E] border-1">
<div className="flex h-design-19 w-design-43 shrink-0 items-center justify-center !rounded-[3px] !px-design-6 !py-0">
<div className={signalPresentation.toneClassName}>
<SignalBars
activeBars={signalPresentation.activeBars}
toneClassName={signalPresentation.toneClassName}
/>
</div>
<div
className={`${signalPresentation.toneClassName} whitespace-nowrap text-design-7 font-bold leading-none`}
>
{signalPresentation.latencyLabel}
<span className="pl-[1px] text-design-6">ms</span>
</div>
</div>
<div className="flex flex-col h-full w-design-66 shrink-0 items-center justify-center border-r border-[rgba(128,223,231,0.32)] pr-design-7 text-design-7 leading-none">
<div className="text-[#B4E4E9]">
{t('gameDesktop.header.systemTime')}
</div>
<MobileHeaderClock />
</div>
<button
type="button"
onClick={onOpenRules}
className={`${actionButtonClassName} !px-design-8`}
>
<Info className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate">
{t('gameDesktop.header.rules')}
</div>
</button>
<button
type="button"
onClick={onOpenNotice}
className={`${actionButtonClassName} !px-design-8`}
>
<Mail className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate">
{t('gameDesktop.header.message')}
</div>
</button>
<button
type="button"
onClick={toggleSoundEnabled}
className={`${actionButtonClassName} !px-design-8`}
>
{isSoundEnabled ? (
<Volume2
className="h-design-8 w-design-8 shrink-0"
color="#57B8BF"
/>
) : (
<VolumeX
className="h-design-8 w-design-8 shrink-0"
color="#57B8BF"
/>
)}
<div className="min-w-0 truncate">
{t('gameDesktop.header.bgm')}
</div>
</button>
<button
type="button"
onClick={onOpenLanguage}
className={`${actionButtonClassName} !px-design-8 justify-between`}
>
<SmartImage
src={currentLanguageOption.icon}
alt={currentLanguageLabel}
className="h-design-14 w-design-14 shrink-0 rounded-full"
imgClassName="object-cover"
/>
<div className="min-w-0 truncate">{currentLanguageLabel}</div>
</button>
</div>
</div>
</header>
)
}