Fix reward code modal and mobile viewport dock
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
11
src/App.tsx
11
src/App.tsx
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
114
src/index.css
114
src/index.css
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user