新增PlatformUtilityInfoModal统一工具信息弹窗白底骨架 收口profile副弹层的摘要头列表骨架与内容行 同步更新PlatformUiKit收口计划与共享决策记录
318 lines
9.8 KiB
TypeScript
318 lines
9.8 KiB
TypeScript
import { Copy } from 'lucide-react';
|
||
import type { ReactNode } from 'react';
|
||
|
||
import communityQqQrImage from '../../../media/social-media-group/qq.png';
|
||
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
|
||
import type { ProfileReferralInviteCenterResponse } from '../../../packages/shared/src/contracts/runtime';
|
||
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
|
||
import { CopyFeedbackButton } from '../common/CopyFeedbackButton';
|
||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||
import { PlatformEmptyState } from '../common/PlatformEmptyState';
|
||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||
import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow';
|
||
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
|
||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||
import { PlatformTextField } from '../common/PlatformTextField';
|
||
import type { CopyFeedbackState } from '../common/useCopyFeedback';
|
||
import { PlatformProfileSecondaryModalShell } from './PlatformProfileModalShell';
|
||
import type { ProfileReferralPanel } from './usePlatformProfileCenterController';
|
||
|
||
type PlatformProfileReferralModalProps = {
|
||
panel: ProfileReferralPanel;
|
||
center: ProfileReferralInviteCenterResponse | null;
|
||
isLoading: boolean;
|
||
isSubmittingRedeem: boolean;
|
||
redeemCode: string;
|
||
copyInviteState: CopyFeedbackState;
|
||
error: string | null;
|
||
success: string | null;
|
||
onClose: () => void;
|
||
onCopyInvite: () => void;
|
||
onRedeemCodeChange: (value: string) => void;
|
||
onSubmitRedeemCode: () => void;
|
||
};
|
||
|
||
const COMMUNITY_QR_CODES = [
|
||
{
|
||
label: '微信群',
|
||
src: communityWechatQrImage,
|
||
alt: '玩家社区微信群二维码',
|
||
},
|
||
{
|
||
label: 'QQ群',
|
||
src: communityQqQrImage,
|
||
alt: '玩家社区 QQ 群二维码',
|
||
},
|
||
] as const;
|
||
|
||
function ProfileReferralUserAvatar({
|
||
name,
|
||
avatarUrl,
|
||
}: {
|
||
name: string;
|
||
avatarUrl: string | null;
|
||
}) {
|
||
const avatarLabel = (name.trim() || '玩').slice(0, 1).toUpperCase();
|
||
|
||
return (
|
||
<span className="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[#ff4056] text-xs font-black text-white">
|
||
{avatarUrl ? (
|
||
<img
|
||
src={avatarUrl}
|
||
alt=""
|
||
className="h-full w-full object-cover"
|
||
loading="lazy"
|
||
decoding="async"
|
||
/>
|
||
) : (
|
||
avatarLabel
|
||
)}
|
||
</span>
|
||
);
|
||
}
|
||
|
||
function resolvePanelTitle(panel: ProfileReferralPanel) {
|
||
if (panel === 'invite') {
|
||
return '邀请好友';
|
||
}
|
||
if (panel === 'redeem') {
|
||
return '填邀请码';
|
||
}
|
||
return '玩家社区';
|
||
}
|
||
|
||
/**
|
||
* 个人中心邀请能力统一弹层。
|
||
* 承接邀请码、填码和社区二维码三种 profile panel,避免首页继续内联重复白底浮层实现。
|
||
*/
|
||
export function PlatformProfileReferralModal({
|
||
panel,
|
||
center,
|
||
isLoading,
|
||
isSubmittingRedeem,
|
||
redeemCode,
|
||
copyInviteState,
|
||
error,
|
||
success,
|
||
onClose,
|
||
onCopyInvite,
|
||
onRedeemCodeChange,
|
||
onSubmitRedeemCode,
|
||
}: PlatformProfileReferralModalProps) {
|
||
const title = resolvePanelTitle(panel);
|
||
const normalizedRedeemCode = redeemCode
|
||
.trim()
|
||
.replace(/[^0-9a-z]/gi, '')
|
||
.toUpperCase();
|
||
|
||
let content: ReactNode;
|
||
|
||
if (panel === 'community') {
|
||
content = (
|
||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||
{COMMUNITY_QR_CODES.map((qrCode) => (
|
||
<PlatformSubpanel
|
||
as="div"
|
||
key={qrCode.label}
|
||
surface="flat"
|
||
radius="xs"
|
||
padding="xs"
|
||
className="text-center"
|
||
>
|
||
<div className="aspect-square overflow-hidden rounded-lg border border-zinc-200 bg-white p-1.5">
|
||
<img
|
||
src={qrCode.src}
|
||
alt={qrCode.alt}
|
||
className="h-full w-full object-contain"
|
||
loading="lazy"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
<div className="mt-2 text-sm font-bold text-zinc-700">
|
||
{qrCode.label}
|
||
</div>
|
||
</PlatformSubpanel>
|
||
))}
|
||
</div>
|
||
);
|
||
} else if (panel === 'redeem') {
|
||
content = (
|
||
<PlatformAsyncStatePanel
|
||
isLoading={isLoading}
|
||
loadingState={
|
||
<PlatformProfileSkeletonList
|
||
count={2}
|
||
containerClassName="mt-5 space-y-3"
|
||
itemClassName="h-12 even:h-11"
|
||
/>
|
||
}
|
||
isEmpty={Boolean(center?.hasRedeemedCode)}
|
||
emptyState={
|
||
<PlatformEmptyState
|
||
surface="subpanel"
|
||
size="inline"
|
||
tone="base"
|
||
className="mt-5"
|
||
>
|
||
已填写邀请码
|
||
</PlatformEmptyState>
|
||
}
|
||
>
|
||
<form
|
||
className="mt-5 space-y-3"
|
||
onSubmit={(event) => {
|
||
event.preventDefault();
|
||
onSubmitRedeemCode();
|
||
}}
|
||
>
|
||
<PlatformTextField
|
||
value={redeemCode}
|
||
onChange={(event) => onRedeemCodeChange(event.target.value)}
|
||
size="lg"
|
||
density="roomy"
|
||
tone="rose"
|
||
className="rounded-xl text-center font-black uppercase tracking-[0.16em]"
|
||
placeholder="邀请码"
|
||
aria-label="邀请码"
|
||
autoComplete="off"
|
||
autoFocus
|
||
/>
|
||
<PlatformActionButton
|
||
type="submit"
|
||
surface="profile"
|
||
fullWidth
|
||
size="md"
|
||
className="rounded-xl"
|
||
disabled={isSubmittingRedeem || !normalizedRedeemCode}
|
||
>
|
||
{isSubmittingRedeem ? '提交中' : '提交'}
|
||
</PlatformActionButton>
|
||
</form>
|
||
</PlatformAsyncStatePanel>
|
||
);
|
||
} else {
|
||
content = (
|
||
<PlatformAsyncStatePanel
|
||
isLoading={isLoading}
|
||
loadingState={
|
||
<PlatformProfileSkeletonList
|
||
count={2}
|
||
containerClassName="mt-5 space-y-3"
|
||
itemClassName="h-20 odd:h-20 even:h-10"
|
||
/>
|
||
}
|
||
>
|
||
<div className="mt-5 space-y-3">
|
||
<PlatformSubpanel
|
||
as="div"
|
||
surface="flat"
|
||
radius="xs"
|
||
padding="md"
|
||
className="text-center"
|
||
>
|
||
<PlatformFieldLabel
|
||
variant="section"
|
||
className="block text-[11px] text-zinc-500"
|
||
>
|
||
邀请码
|
||
</PlatformFieldLabel>
|
||
<div className="mt-1 text-3xl font-black tracking-[0.16em] text-[#ff4056]">
|
||
{center?.inviteCode ?? '--------'}
|
||
</div>
|
||
</PlatformSubpanel>
|
||
<PlatformStatusMessage
|
||
tone="warning"
|
||
surface="profile"
|
||
size="md"
|
||
className="space-y-0.5 px-3.5 font-semibold"
|
||
>
|
||
<div>
|
||
{`邀请一个用户注册,双方都可以获得${center?.rewardPoints ?? 30}泥点。`}
|
||
</div>
|
||
<div>每日最多获得十次邀请奖励。</div>
|
||
</PlatformStatusMessage>
|
||
<CopyFeedbackButton
|
||
state={copyInviteState}
|
||
onClick={onCopyInvite}
|
||
disabled={!center?.inviteCode}
|
||
idleLabel="复制邀请"
|
||
copiedLabel="已复制"
|
||
failedLabel="复制失败"
|
||
idleIcon={<Copy className="h-4 w-4" />}
|
||
actionSurface="profile"
|
||
actionSize="md"
|
||
actionFullWidth
|
||
className="gap-2 rounded-xl"
|
||
/>
|
||
<PlatformSubpanel as="div" surface="flat" radius="xs" padding="sm">
|
||
<PlatformFieldLabel
|
||
variant="section"
|
||
className="block text-zinc-900"
|
||
>
|
||
成功邀请
|
||
</PlatformFieldLabel>
|
||
{center?.invitedUsers?.length ? (
|
||
<div className="mt-3 max-h-44 space-y-2 overflow-y-auto pr-1">
|
||
{center.invitedUsers.map((user) => (
|
||
<PlatformProfileContentRow
|
||
key={`${user.userId}-${user.boundAt}`}
|
||
surface="soft"
|
||
radius="xs"
|
||
padding="row"
|
||
className="flex items-center gap-3"
|
||
>
|
||
<ProfileReferralUserAvatar
|
||
name={user.displayName}
|
||
avatarUrl={user.avatarUrl}
|
||
/>
|
||
<div className="min-w-0 flex-1">
|
||
<div className="truncate text-sm font-bold text-zinc-900">
|
||
{user.displayName || '玩家'}
|
||
</div>
|
||
</div>
|
||
</PlatformProfileContentRow>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<PlatformEmptyState
|
||
surface="subpanel"
|
||
size="compact"
|
||
className="mt-3 text-center text-xs font-semibold leading-normal"
|
||
>
|
||
暂无成功邀请
|
||
</PlatformEmptyState>
|
||
)}
|
||
</PlatformSubpanel>
|
||
</div>
|
||
</PlatformAsyncStatePanel>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<PlatformProfileSecondaryModalShell
|
||
title={title}
|
||
onClose={onClose}
|
||
closeVariant="floatingPlain"
|
||
closeIcon="×"
|
||
overlayTone="soft"
|
||
panelClassName="relative !max-w-[24rem] bg-white text-zinc-950 shadow-2xl !rounded-[1.35rem] sm:!rounded-[1.35rem]"
|
||
contentClassName="relative px-5 pb-5 pt-4"
|
||
>
|
||
<div className="text-center text-xl font-black">{title}</div>
|
||
{content}
|
||
|
||
{error ? (
|
||
<PlatformStatusMessage tone="error" className="mt-4">
|
||
{error}
|
||
</PlatformStatusMessage>
|
||
) : null}
|
||
{success ? (
|
||
<PlatformStatusMessage tone="success" className="mt-4">
|
||
{success}
|
||
</PlatformStatusMessage>
|
||
) : null}
|
||
</PlatformProfileSecondaryModalShell>
|
||
);
|
||
}
|