This commit is contained in:
2026-04-21 19:17:31 +08:00
parent d234d27cc0
commit 89129ef1f4
83 changed files with 13329 additions and 176 deletions

View File

@@ -125,6 +125,21 @@ test('auth gate keeps platform content visible when phone login is available', a
expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
});
test('auth gate does not auto-create a guest account when dev guest switch is not explicitly enabled', async () => {
authMocks.getAuthLoginOptions.mockResolvedValue({
availableLoginMethods: [],
});
render(
<AuthGate>
<div></div>
</AuthGate>,
);
expect(await screen.findByText('应用内容')).toBeTruthy();
expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
});
test('auth gate opens a login modal for protected actions and resumes after login', async () => {
const user = userEvent.setup();
const onAuthenticated = vi.fn();
@@ -159,3 +174,39 @@ test('auth gate opens a login modal for protected actions and resumes after logi
expect(screen.queryByRole('dialog', { name: '登录账号' })).toBeNull();
});
test('auth gate shows sms send feedback in the login modal', async () => {
const user = userEvent.setup();
authMocks.getAuthLoginOptions.mockResolvedValue({
availableLoginMethods: ['phone'],
});
render(
<AuthGate>
<ProtectedActionButton onAuthenticated={vi.fn()} />
</AuthGate>,
);
await user.click(await screen.findByRole('button', { name: '进入作品' }));
const dialog = screen.getByRole('dialog', { name: '登录账号' });
await user.type(within(dialog).getByLabelText('手机号'), '13800000000');
await user.click(within(dialog).getByRole('button', { name: '获取验证码' }));
await waitFor(() => {
expect(authMocks.sendPhoneLoginCode).toHaveBeenCalledWith(
'13800000000',
'login',
{
challengeId: undefined,
answer: '',
},
);
});
expect(
within(dialog).getByText('验证码已发送,有效期约 5 分钟。'),
).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '60s' })).toBeTruthy();
});

View File

@@ -59,7 +59,8 @@ type AuthStatus =
const allowDevGuestAutoAuth =
import.meta.env.DEV &&
import.meta.env.VITE_AUTH_ALLOW_DEV_GUEST !== 'false';
// 开发游客兜底必须显式开启,避免抢占正式手机号验证码登录入口。
import.meta.env.VITE_AUTH_ALLOW_DEV_GUEST === 'true';
export function AuthGate({ children }: AuthGateProps) {
const [status, setStatus] = useState<AuthStatus>('checking');

View File

@@ -50,6 +50,7 @@ export function LoginScreen({
const [code, setCode] = useState('');
const [captchaAnswer, setCaptchaAnswer] = useState('');
const [cooldownSeconds, setCooldownSeconds] = useState(0);
const [hint, setHint] = useState('');
const phoneLoginEnabled = availableLoginMethods.includes('phone');
const wechatLoginEnabled = availableLoginMethods.includes('wechat');
@@ -142,12 +143,16 @@ export function LoginScreen({
className="platform-button platform-button--secondary h-12 shrink-0 px-4 text-sm disabled:cursor-not-allowed disabled:opacity-55"
onClick={() => {
void (async () => {
setHint('');
try {
const result = await onSendCode(phone, {
challengeId: captchaChallenge?.challengeId,
answer: captchaAnswer,
});
setCooldownSeconds(result.cooldownSeconds);
setHint(
`验证码已发送,有效期约 ${Math.max(1, Math.round(result.expiresInSeconds / 60))} 分钟。`,
);
setCaptchaAnswer('');
} catch {
// Error state is handled by the parent.
@@ -169,6 +174,12 @@ export function LoginScreen({
answer={captchaAnswer}
onAnswerChange={setCaptchaAnswer}
/>
{hint ? (
<div className="platform-banner platform-banner--success text-sm">
{hint}
</div>
) : null}
</>
) : null}