Fix reward code modal and mobile viewport dock
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-30 16:25:46 +08:00
parent 22f3f963de
commit 4ef1d27021
8 changed files with 177 additions and 16 deletions

View File

@@ -1,4 +1,11 @@
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';
import {
lazy,
Suspense,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { useAuthUi } from './components/auth/AuthUiContext';
import { PlatformEntryFlowShell } from './components/platform-entry/PlatformEntryFlowShell';
@@ -133,7 +140,7 @@ export default function App() {
return (
<div
className={`platform-ui-shell platform-theme ${platformThemeClass} flex h-screen max-h-screen flex-col overflow-hidden bg-[image:var(--platform-body-fill)] p-2 font-sans text-[var(--platform-text-strong)] sm:p-4`}
className={`platform-ui-shell platform-viewport-shell platform-theme ${platformThemeClass} flex flex-col overflow-hidden bg-[image:var(--platform-body-fill)] p-2 font-sans text-[var(--platform-text-strong)] sm:p-4`}
>
<PlatformEntryFlowShell
selectionStage={selectionStage}

View File

@@ -464,6 +464,18 @@ test('wallet ledger modal shows empty and error states', async () => {
expect(screen.getByText('重新加载')).toBeTruthy();
});
test('opens reward code modal from profile action on mobile', async () => {
const user = userEvent.setup();
renderProfileView();
await user.click(screen.getByRole('button', { name: //u }));
const modal = await screen.findByPlaceholderText('输入兑换码');
expect(modal).toBeTruthy();
expect(screen.getByRole('button', { name: '兑换' })).toBeTruthy();
expect(screen.getByLabelText('关闭兑换码')).toBeTruthy();
});
test('shows a reachable login entry in logged out mobile shell', async () => {
const user = userEvent.setup();
const openLoginModal = vi.fn();

View File

@@ -1788,7 +1788,7 @@ function RewardCodeRedeemModal({
onClose: () => void;
}) {
return (
<div className="platform-modal-backdrop fixed inset-0 z-50 flex items-center justify-center px-4 py-6">
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
<div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
<div className="text-base font-black"></div>
@@ -3448,6 +3448,17 @@ export function RpgEntryHomeView({
) : null}
</>
);
const rewardCodeModal: ReactNode = isRewardCodeOpen ? (
<RewardCodeRedeemModal
value={rewardCodeInput}
isSubmitting={isSubmittingRewardCode}
error={rewardCodeError}
success={rewardCodeSuccess}
onChange={setRewardCodeInput}
onSubmit={submitRewardCode}
onClose={() => setIsRewardCodeOpen(false)}
/>
) : null;
if (!isDesktopLayout) {
return (
@@ -3471,10 +3482,9 @@ export function RpgEntryHomeView({
</div>
<div
className="mt-3 min-w-0 shrink-0 border-t pt-2"
className="platform-mobile-bottom-dock mt-3 min-w-0 shrink-0 border-t pt-2"
style={{
borderColor: 'var(--platform-line-soft)',
paddingBottom: 'calc(env(safe-area-inset-bottom) + 0.2rem)',
}}
>
<div
@@ -3507,6 +3517,7 @@ export function RpgEntryHomeView({
onSubmitRedeem={submitReferralInviteCode}
/>
) : null}
{rewardCodeModal}
{isProfilePlayStatsOpen ? (
<ProfilePlayedWorksModal
stats={profilePlayStats}
@@ -3604,17 +3615,7 @@ export function RpgEntryHomeView({
</div>
</div>
</div>
{isRewardCodeOpen ? (
<RewardCodeRedeemModal
value={rewardCodeInput}
isSubmitting={isSubmittingRewardCode}
error={rewardCodeError}
success={rewardCodeSuccess}
onChange={setRewardCodeInput}
onSubmit={submitRewardCode}
onClose={() => setIsRewardCodeOpen(false)}
/>
) : null}
{rewardCodeModal}
{profilePopupPanel ? (
<ProfileReferralModal
panel={profilePopupPanel}

View File

@@ -21,6 +21,10 @@
:root {
--ui-scale: clamp(0.78, 0.72 + 0.45vw, 1.06);
--platform-bottom-nav-height: 3.5rem;
--platform-bottom-dock-outer-height: calc(
var(--platform-bottom-nav-height) + env(safe-area-inset-bottom, 0px) +
1.15rem
);
--platform-bottom-nav-padding: 0.25rem;
--platform-bottom-nav-gap: 0.25rem;
--platform-bottom-nav-radius: 1.2rem;
@@ -50,6 +54,20 @@ body {
-webkit-font-smoothing: antialiased;
}
.platform-viewport-shell {
height: 100vh;
max-height: 100vh;
min-height: 100vh;
}
@supports (height: 100dvh) {
.platform-viewport-shell {
height: 100dvh;
max-height: 100dvh;
min-height: 100dvh;
}
}
@keyframes character-animator-portrait-death-fall {
0% {
transform: translateY(0) rotate(0deg) scaleX(1) scale(1);
@@ -754,6 +772,10 @@ body {
overflow: hidden;
}
.platform-mobile-bottom-dock {
flex: none;
}
.platform-tab-panel {
box-sizing: border-box;
width: 100%;
@@ -1707,6 +1729,20 @@ body {
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.16);
}
.platform-modal-backdrop {
background: var(--platform-overlay-fill);
color: var(--platform-text-strong);
backdrop-filter: blur(12px);
}
.platform-recharge-modal {
border: 1px solid var(--platform-modal-border);
background: var(--platform-modal-fill);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.1),
0 24px 80px rgba(0, 0, 0, 0.18);
}
.platform-overlay {
background: var(--platform-overlay-fill);
}
@@ -1790,6 +1826,20 @@ body {
min-width: 0;
}
.platform-mobile-entry-shell {
box-sizing: border-box;
padding-bottom: var(--platform-bottom-dock-outer-height);
}
.platform-mobile-bottom-dock {
position: fixed;
right: max(0.75rem, env(safe-area-inset-right, 0px));
bottom: 0;
left: max(0.75rem, env(safe-area-inset-left, 0px));
z-index: 60;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
}
.platform-mobile-home-stage {
box-sizing: border-box;
width: 100%;
@@ -2191,6 +2241,70 @@ body {
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
}
.platform-profile-input {
border: 1px solid var(--platform-subpanel-border);
background: var(--platform-input-fill);
color: var(--platform-text-strong);
outline: none;
box-shadow: inset 0 1px 0 var(--platform-input-highlight);
transition:
border-color 180ms ease,
background-color 180ms ease,
box-shadow 180ms ease;
}
.platform-profile-input::placeholder {
color: var(--platform-text-soft);
}
.platform-profile-input:focus {
border-color: var(--platform-nav-active-border);
background: var(--platform-input-fill-focus);
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
}
.platform-primary-button {
border: 1px solid var(--platform-button-primary-border);
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
box-shadow: var(--platform-profile-action-shadow);
transition:
transform 180ms ease,
filter 180ms ease,
opacity 180ms ease;
}
.platform-primary-button:hover:not(:disabled) {
transform: translateY(-1px);
filter: brightness(1.02);
}
.platform-modal-close {
background: var(--platform-profile-chip-fill);
color: var(--platform-profile-chip-text);
transition:
background-color 180ms ease,
color 180ms ease,
transform 180ms ease;
}
.platform-modal-close:hover {
background: var(--platform-profile-chip-hover-fill);
color: var(--platform-text-strong);
}
.platform-profile-error {
border: 1px solid var(--platform-button-danger-border);
background: var(--platform-button-danger-fill);
color: var(--platform-button-danger-text);
}
.platform-profile-success {
border: 1px solid var(--platform-success-border);
background: var(--platform-success-bg);
color: var(--platform-success-text);
}
.platform-profile-hero {
overflow: hidden;
border: 1px solid var(--platform-profile-hero-border);