@@ -83,6 +83,9 @@ test('settings header uses a generic title instead of the phone number', () => {
|
||||
expect(screen.queryByText(/^登录设备$/)).toBeNull();
|
||||
expect(screen.queryByText(/^操作记录$/)).toBeNull();
|
||||
expect(screen.queryByText('当前账号状态')).toBeNull();
|
||||
expect(screen.queryByText('当前主题')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '退出登录' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '退出全部设备' })).toBeNull();
|
||||
});
|
||||
|
||||
test('account actions open in independent panels instead of inline expansion', async () => {
|
||||
@@ -121,9 +124,13 @@ test('nested settings panels keep back navigation without an extra close action'
|
||||
await user.click(screen.getByRole('button', { name: /账号信息/ }));
|
||||
|
||||
const accountDialog = screen.getByRole('dialog', { name: '账号信息' });
|
||||
const accountHeader = accountDialog.firstElementChild as HTMLElement | null;
|
||||
expect(
|
||||
within(accountDialog).getByRole('button', { name: '返回' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
accountHeader?.lastElementChild?.textContent?.includes('返回'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
within(accountDialog).queryByRole('button', { name: '关闭' }),
|
||||
).toBeNull();
|
||||
@@ -135,9 +142,14 @@ test('nested settings panels keep back navigation without an extra close action'
|
||||
const changePhoneDialog = screen.getByRole('dialog', {
|
||||
name: '绑定新手机号',
|
||||
});
|
||||
const changePhoneHeader =
|
||||
changePhoneDialog.firstElementChild as HTMLElement | null;
|
||||
expect(
|
||||
within(changePhoneDialog).getByRole('button', { name: '返回' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
changePhoneHeader?.lastElementChild?.textContent?.includes('返回'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
within(changePhoneDialog).queryByRole('button', { name: '关闭' }),
|
||||
).toBeNull();
|
||||
@@ -234,6 +246,12 @@ test('account panel includes merged security devices and audit sections', async
|
||||
expect(within(accountDialog).getByText('手机号保护')).toBeTruthy();
|
||||
expect(within(accountDialog).getByText('iPhone 15 Pro')).toBeTruthy();
|
||||
expect(within(accountDialog).getByText('登录成功')).toBeTruthy();
|
||||
expect(
|
||||
within(accountDialog).getByRole('button', { name: '退出登录' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(accountDialog).getByRole('button', { name: '退出全部设备' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('legacy nested section requests now open the merged account panel', () => {
|
||||
|
||||
@@ -173,7 +173,7 @@ function OverlayPanel({
|
||||
onClick={onBack ?? onClose}
|
||||
>
|
||||
<div
|
||||
className="platform-auth-card flex w-full flex-col overflow-hidden rounded-[28px] p-5 sm:max-w-3xl sm:p-6"
|
||||
className="platform-auth-card flex max-h-full w-full min-h-0 flex-col overflow-hidden rounded-[28px] p-5 sm:max-w-3xl sm:p-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
@@ -182,20 +182,8 @@ function OverlayPanel({
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{onBack ? (
|
||||
<button
|
||||
type="button"
|
||||
autoFocus
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-3 py-1.5 text-xs"
|
||||
onClick={onBack}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
) : null}
|
||||
<div className="text-xs uppercase tracking-[0.28em] text-[var(--platform-cool-text)]">
|
||||
{eyebrow}
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-[0.28em] text-[var(--platform-cool-text)]">
|
||||
{eyebrow}
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-semibold text-[var(--platform-text-strong)]">
|
||||
{title}
|
||||
@@ -208,7 +196,16 @@ function OverlayPanel({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{action}
|
||||
{onBack ? null : (
|
||||
{onBack ? (
|
||||
<button
|
||||
type="button"
|
||||
autoFocus
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-3 py-1.5 text-xs"
|
||||
onClick={onBack}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-3 py-1.5 text-xs"
|
||||
@@ -420,7 +417,7 @@ export function AccountModal({
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="platform-auth-card relative flex w-full max-w-5xl flex-col overflow-hidden rounded-[28px] p-5 sm:p-6"
|
||||
className="platform-auth-card relative flex h-[min(100%,calc(100vh-2rem))] w-full max-w-5xl min-h-0 flex-col overflow-hidden rounded-[28px] p-5 sm:p-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="设置与账号安全"
|
||||
@@ -443,7 +440,7 @@ export function AccountModal({
|
||||
</div>
|
||||
|
||||
<div className="mt-5 min-h-0 flex-1 overflow-y-auto overscroll-y-contain pr-1">
|
||||
<div ref={settingsHomeRef} className="space-y-4">
|
||||
<div ref={settingsHomeRef} className="flex min-h-0 flex-col gap-4">
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{SETTINGS_SECTIONS.map((section) => (
|
||||
<SettingsEntryCard
|
||||
@@ -459,39 +456,6 @@ export function AccountModal({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-4">
|
||||
<div className="text-sm font-semibold text-[var(--platform-text-strong)]">
|
||||
当前主题
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-[var(--platform-text-base)]">
|
||||
{platformTheme === 'dark' ? '暗色主题' : '亮色主题'}
|
||||
</div>
|
||||
<span className="platform-pill platform-pill--neutral mt-3 inline-flex px-3 py-1 text-[11px]">
|
||||
{themeStatusText}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--ghost h-11 w-full text-sm"
|
||||
onClick={() => {
|
||||
void onLogout();
|
||||
}}
|
||||
>
|
||||
退出登录
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--danger h-11 w-full text-sm"
|
||||
onClick={() => {
|
||||
void onLogoutAll();
|
||||
}}
|
||||
>
|
||||
退出全部设备
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -503,7 +467,7 @@ export function AccountModal({
|
||||
onBack={closeSectionPanel}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex min-h-0 flex-col gap-4">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<ThemeOptionCard
|
||||
active={platformTheme === 'light'}
|
||||
@@ -548,7 +512,7 @@ export function AccountModal({
|
||||
onBack={closeSectionPanel}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex min-h-0 flex-col gap-4">
|
||||
{accountNotice ? (
|
||||
<div className="platform-banner platform-banner--success text-sm">
|
||||
{accountNotice}
|
||||
@@ -571,6 +535,27 @@ export function AccountModal({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--ghost h-11 w-full text-sm"
|
||||
onClick={() => {
|
||||
void onLogout();
|
||||
}}
|
||||
>
|
||||
退出登录
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--danger h-11 w-full text-sm"
|
||||
onClick={() => {
|
||||
void onLogoutAll();
|
||||
}}
|
||||
>
|
||||
退出全部设备
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
|
||||
@@ -120,7 +120,7 @@ test('auth gate keeps platform content visible when phone login is available', a
|
||||
);
|
||||
|
||||
expect(await screen.findByText('应用内容')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '登录' })).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '登录' })).toBeNull();
|
||||
expect(screen.queryByText('先登录账号,再同步你的冒险进度。')).toBeNull();
|
||||
expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -76,7 +76,6 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
||||
const [initialSettingsSection, setInitialSettingsSection] =
|
||||
useState<PlatformSettingsSection | null>(null);
|
||||
const [showGlobalAccountActions, setShowGlobalAccountActions] = useState(true);
|
||||
const [sessions, setSessions] = useState<AuthSessionSummary[]>([]);
|
||||
const [loadingSessions, setLoadingSessions] = useState(false);
|
||||
const [auditLogs, setAuditLogs] = useState<AuthAuditLogEntry[]>([]);
|
||||
@@ -389,7 +388,6 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
await logoutAuthUser();
|
||||
setShowSettingsModal(false);
|
||||
},
|
||||
setGlobalAccountActionsVisible: setShowGlobalAccountActions,
|
||||
musicVolume: settings.musicVolume,
|
||||
setMusicVolume: settings.setMusicVolume,
|
||||
platformTheme: settings.platformTheme,
|
||||
@@ -516,38 +514,6 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
<AuthUiContext.Provider value={authUiValue}>
|
||||
<div className="relative">
|
||||
<div className={`platform-theme ${platformThemeClass}`}>
|
||||
{showGlobalAccountActions ? (
|
||||
<div className="pointer-events-none fixed right-3 top-3 z-50 flex justify-end">
|
||||
{readyUser ? (
|
||||
<div className="platform-auth-card pointer-events-auto flex items-center gap-2 rounded-full px-3 py-2 text-xs text-[var(--platform-text-base)]">
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--secondary min-h-0 rounded-full px-2.5 py-1 text-[11px]"
|
||||
onClick={() => openAccountModal()}
|
||||
>
|
||||
{readyUser.displayName}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-2.5 py-1 text-[11px]"
|
||||
onClick={() => {
|
||||
void logoutAuthUser();
|
||||
}}
|
||||
>
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-auth-card pointer-events-auto rounded-full px-3 py-2 text-xs font-medium text-[var(--platform-text-strong)] transition hover:-translate-y-px"
|
||||
onClick={() => openLoginModal()}
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{readyUser ? (
|
||||
<AccountModal
|
||||
user={readyUser}
|
||||
|
||||
@@ -17,7 +17,6 @@ type AuthUiContextValue = {
|
||||
openSettingsModal: (section?: PlatformSettingsSection) => void;
|
||||
openAccountModal: () => void;
|
||||
logout: () => Promise<void>;
|
||||
setGlobalAccountActionsVisible: (visible: boolean) => void;
|
||||
musicVolume: number;
|
||||
setMusicVolume: (value: number) => void;
|
||||
platformTheme: PlatformTheme;
|
||||
|
||||
Reference in New Issue
Block a user