import { ArrowRight, BookOpen, Camera, ChevronDown, ChevronRight, Clock3, Coins, Compass, Copy, FileText, Gamepad2, GitFork, Heart, LogIn, MessageCircle, Pencil, Plus, Search, Settings, Share2, SlidersHorizontal, Sparkles, Star, ThumbsUp, Ticket, UserPlus, UserRound, } from 'lucide-react'; import { type ComponentType, type CSSProperties, type PointerEvent, type ReactNode, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import communityQqQrImage from '../../../media/social-media-group/qq.png'; import communityWechatQrImage from '../../../media/social-media-group/wechat.png'; import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth'; import type { CustomWorldLibraryEntry, PlatformBrowseHistoryEntry, ProfileDashboardCardKey, ProfileDashboardSummary, ProfilePlayedWorkSummary, ProfilePlayStatsResponse, ProfileReferralInviteCenterResponse, ProfileRechargeCenterResponse, ProfileRechargeProduct, WechatMiniProgramPayParams, ProfileSaveArchiveSummary, ProfileTaskCenterResponse, ProfileTaskItem, ProfileWalletLedgerResponse, RedeemProfileRewardCodeResponse, } from '../../../packages/shared/src/contracts/runtime'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes'; import type { AuthUser } from '../../services/authService'; import { getPublicAuthUserByCode, getPublicAuthUserById, updateAuthProfile, } from '../../services/authService'; import { copyTextToClipboard } from '../../services/clipboard'; import { claimRpgProfileTaskReward, createRpgProfileRechargeOrder, getRpgProfileReferralInviteCenter, getRpgProfileRechargeCenter, getRpgProfileTasks, getRpgProfileWalletLedger, redeemRpgProfileReferralInviteCode, redeemRpgProfileRewardCode, } from '../../services/rpg-entry/rpgProfileClient'; import type { CustomWorldProfile } from '../../types'; import { useAuthUi } from '../auth/AuthUiContext'; import { LegalDocumentModal } from '../common/LegalDocumentModal'; import { getLegalDocument, ICP_RECORD_NUMBER, ICP_RECORD_URL, LEGAL_DOCUMENTS, type LegalDocumentId, } from '../common/legalDocuments'; import { canExposePublicWork, EDUTAINMENT_WORK_TAG, filterEdutainmentPublicWorks, filterGeneralPublicWorks, findPublicWorkForHistoryEntry, isEdutainmentEntryEnabled, } from '../platform-entry/platformEdutainmentVisibility'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; import { RpgEntryBrandLogo } from './RpgEntryBrandLogo'; import { buildPlatformWorldDisplayTags, describePlatformThemeLabel, formatPlatformWorkDisplayName, formatPlatformWorkDisplayTag, formatPlatformWorldTime, isBigFishGalleryEntry, isEdutainmentGalleryEntry, isMatch3DGalleryEntry, isPuzzleGalleryEntry, isSquareHoleGalleryEntry, isVisualNovelGalleryEntry, type PlatformPublicGalleryCard, type PlatformWorldCardLike, resolvePlatformPublicWorkCode, resolvePlatformWorldCoverImage, resolvePlatformWorldCoverSlides, resolvePlatformWorldLeadPortrait, } from './rpgEntryWorldPresentation'; export type PlatformHomeTab = | 'home' | 'category' | 'create' | 'saves' | 'profile'; export interface RpgEntryHomeViewProps { activeTab: PlatformHomeTab; onTabChange: (tab: PlatformHomeTab) => void; hasSavedGame: boolean; savedSnapshot: HydratedSavedGameSnapshot | null; saveEntries: ProfileSaveArchiveSummary[]; saveError: string | null; featuredEntries: PlatformPublicGalleryCard[]; latestEntries: PlatformPublicGalleryCard[]; myEntries: CustomWorldLibraryEntry[]; historyEntries: PlatformBrowseHistoryEntry[]; profileDashboard: ProfileDashboardSummary | null; isLoadingPlatform: boolean; isLoadingDashboard: boolean; isResumingSaveWorldKey: string | null; platformError: string | null; dashboardError: string | null; onContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void; onResumeSave: (entry: ProfileSaveArchiveSummary) => void; onOpenCreateWorld: () => void; onOpenCreateTypePicker: () => void; onOpenGalleryDetail: (entry: PlatformPublicGalleryCard) => void; onOpenRecommendGalleryDetail?: (entry: PlatformPublicGalleryCard) => void; recommendRuntimeContent?: ReactNode; activeRecommendEntryKey?: string | null; isStartingRecommendEntry?: boolean; recommendRuntimeError?: string | null; onSelectNextRecommendEntry?: () => void; onSelectPreviousRecommendEntry?: () => void; onLikeRecommendEntry?: (entry: PlatformPublicGalleryCard) => void; onRemixRecommendEntry?: (entry: PlatformPublicGalleryCard) => void; onOpenLibraryDetail: ( entry: CustomWorldLibraryEntry, ) => void; onDeleteLibraryEntry?: ( entry: CustomWorldLibraryEntry, ) => void; deletingLibraryEntryId?: string | null; onSearchPublicCode?: (keyword: string) => void | Promise; isSearchingPublicCode?: boolean; onOpenProfileDashboardCard?: (cardKey: ProfileDashboardCardKey) => void; profilePlayStats?: ProfilePlayStatsResponse | null; isProfilePlayStatsOpen?: boolean; isProfilePlayStatsLoading?: boolean; profilePlayStatsError?: string | null; onCloseProfilePlayStats?: () => void; onOpenPlayedWork?: (work: ProfilePlayedWorkSummary) => void; onOpenFeedback?: () => void; onRechargeSuccess?: () => void | Promise; createTabContent?: ReactNode; draftTabContent?: ReactNode; hasUnreadDraftUpdate?: boolean; } const PANEL_SURFACE_CLASS = 'platform-surface platform-surface--soft'; const HERO_SURFACE_CLASS = 'platform-surface platform-surface--hero platform-interactive-card min-w-0'; const MOBILE_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2'; const MOBILE_RECOMMEND_PAGE_STAGE_CLASS = 'platform-page-stage min-w-0 space-y-4 overflow-hidden pb-2'; const DESKTOP_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-5 pb-4'; const DESKTOP_LAYOUT_QUERY = '(min-width: 1024px)'; const PLATFORM_HOME_TABS: PlatformHomeTab[] = [ 'home', 'category', 'create', 'saves', 'profile', ]; const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024; const AVATAR_OUTPUT_SIZE = 256; const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']); const PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS = 4200; const PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS = 24 * 60 * 60 * 1000; const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const; const RECOMMEND_ENTRY_SWIPE_THRESHOLD_PX = 36; const RECOMMEND_ENTRY_COMMIT_ANIMATION_MS = 180; const RECOMMEND_ENTRY_DRAG_LIMIT_PX = 160; const WECHAT_MINI_PROGRAM_PAYMENT_CHANNEL = 'wechat_mp'; type ProfilePopupPanel = 'invite' | 'redeem' | 'community'; type RechargeTab = 'points' | 'membership'; type WechatMiniProgramPaymentStatus = 'success' | 'fail' | 'cancel'; type DiscoverChannel = | 'recommend' | 'today' | 'category' | 'ranking' | 'edutainment'; type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like'; const COMMUNITY_QR_CODES = [ { label: '微信群', src: communityWechatQrImage, alt: '玩家社区微信群二维码', }, { label: 'QQ群', src: communityQqQrImage, alt: '玩家社区 QQ 群二维码', }, ] as const; const DISCOVER_CHANNELS: Array<{ id: DiscoverChannel; label: string; }> = [ { id: 'recommend', label: '推荐' }, { id: 'today', label: '今日' }, { id: 'category', label: '分类' }, { id: 'ranking', label: '排行' }, ]; const EDUTAINMENT_DISCOVER_CHANNEL = { id: 'edutainment', label: EDUTAINMENT_WORK_TAG, } as const; const PLATFORM_RANKING_TABS: Array<{ id: PlatformRankingTab; label: string; metricLabel: string; emptyText: string; }> = [ { id: 'hot', label: '热门榜', metricLabel: '游玩', emptyText: '公开广场暂时还没有热门作品。', }, { id: 'remix', label: '改造榜', metricLabel: '改造', emptyText: '公开广场暂时还没有改造作品。', }, { id: 'new', label: '新品榜', metricLabel: '近7日', emptyText: '近 7 日暂时还没有新品。', }, { id: 'like', label: '点赞榜', metricLabel: '点赞', emptyText: '公开广场暂时还没有点赞作品。', }, ]; function usePlatformDesktopLayout() { const [isDesktopLayout, setIsDesktopLayout] = useState(() => { if ( typeof window === 'undefined' || typeof window.matchMedia !== 'function' ) { return false; } return window.matchMedia(DESKTOP_LAYOUT_QUERY).matches; }); useEffect(() => { if ( typeof window === 'undefined' || typeof window.matchMedia !== 'function' ) { return; } const mediaQuery = window.matchMedia(DESKTOP_LAYOUT_QUERY); const updateLayout = (event?: MediaQueryListEvent) => { setIsDesktopLayout(event?.matches ?? mediaQuery.matches); }; updateLayout(); // 平台页只挂载当前断点外壳,避免隐藏的移动端/桌面端内容重复抢占查询。 if (typeof mediaQuery.addEventListener === 'function') { mediaQuery.addEventListener('change', updateLayout); return () => mediaQuery.removeEventListener('change', updateLayout); } mediaQuery.addListener(updateLayout); return () => mediaQuery.removeListener(updateLayout); }, []); return isDesktopLayout; } function ResolvedAssetBackdrop({ src, alt, className, ariaHidden = false, }: { src?: string | null; alt: string; className: string; ariaHidden?: boolean; }) { return ( ); } function SectionHeader({ title, detail }: { title: string; detail: string }) { return (
{detail}
{title}
); } function PublicCodeSearchBar({ value, onChange, onSubmit, isSearching, className, }: { value: string; onChange: (value: string) => void; onSubmit: () => void; isSearching: boolean; className?: string; }) { return (
onChange(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); onSubmit(); } }} placeholder="搜索作品号、名称、作者、描述" className="w-full min-w-0 bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]" />
); } function EmptyShelf({ text }: { text: string }) { return (
{text}
); } function SaveArchivePreview({ entry, className, }: { entry: ProfileSaveArchiveSummary; className: string; }) { return (