Fix first-login username flow

这个提交包含在:
cryptocommuniums-afk
2026-03-14 22:37:15 +08:00
父节点 8d3faecb15
当前提交 bcdd790d91
修改 3 个文件,包含 40 行新增12 行删除

查看文件

@@ -10,23 +10,41 @@ import { Target, Loader2 } from "lucide-react";
export default function Login() { export default function Login() {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [, setLocation] = useLocation(); const [, setLocation] = useLocation();
const loginMutation = trpc.auth.loginWithUsername.useMutation({ const utils = trpc.useUtils();
onSuccess: (data) => { const loginMutation = trpc.auth.loginWithUsername.useMutation();
toast.success(data.isNew ? `欢迎加入,${data.user.name}` : `欢迎回来,${data.user.name}`);
setLocation("/dashboard");
},
onError: (err) => {
toast.error("登录失败: " + err.message);
},
});
const handleLogin = (e: React.FormEvent) => { const syncAuthenticatedUser = async (fallbackUser: Awaited<ReturnType<typeof loginMutation.mutateAsync>>["user"]) => {
// Seed the cache immediately so protected routes do not bounce back to /login.
utils.auth.me.setData(undefined, fallbackUser);
for (let attempt = 0; attempt < 3; attempt++) {
const user = await utils.auth.me.fetch();
if (user) {
utils.auth.me.setData(undefined, user);
return user;
}
await new Promise(resolve => window.setTimeout(resolve, 120 * (attempt + 1)));
}
return fallbackUser;
};
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!username.trim()) { if (!username.trim()) {
toast.error("请输入用户名"); toast.error("请输入用户名");
return; return;
} }
loginMutation.mutate({ username: username.trim() });
try {
const data = await loginMutation.mutateAsync({ username: username.trim() });
const user = await syncAuthenticatedUser(data.user);
toast.success(data.isNew ? `欢迎加入,${user.name}` : `欢迎回来,${user.name}`);
setLocation("/dashboard");
} catch (err) {
const message = err instanceof Error ? err.message : "未知错误";
toast.error("登录失败: " + message);
}
}; };
return ( return (

查看文件

@@ -2,7 +2,10 @@ import { expect, test } from "@playwright/test";
import { installAppMocks } from "./helpers/mockApp"; import { installAppMocks } from "./helpers/mockApp";
test("login redirects into dashboard with mocked auth", async ({ page }) => { test("login redirects into dashboard with mocked auth", async ({ page }) => {
await installAppMocks(page, { authenticated: false }); await installAppMocks(page, {
authenticated: false,
authMeNullResponsesAfterLogin: 1,
});
await page.goto("/login"); await page.goto("/login");
await expect(page.getByTestId("login-title")).toBeVisible(); await expect(page.getByTestId("login-title")).toBeVisible();

查看文件

@@ -61,6 +61,7 @@ type MockAppState = {
analyses: any[]; analyses: any[];
mediaSession: MockMediaSession | null; mediaSession: MockMediaSession | null;
nextVideoId: number; nextVideoId: number;
authMeNullResponsesAfterLogin: number;
}; };
function trpcResult(json: unknown) { function trpcResult(json: unknown) {
@@ -154,6 +155,10 @@ async function handleTrpc(route: Route, state: MockAppState) {
const results = operations.map((operation) => { const results = operations.map((operation) => {
switch (operation) { switch (operation) {
case "auth.me": case "auth.me":
if (state.authenticated && state.authMeNullResponsesAfterLogin > 0) {
state.authMeNullResponsesAfterLogin -= 1;
return trpcResult(null);
}
return trpcResult(state.authenticated ? state.user : null); return trpcResult(state.authenticated ? state.user : null);
case "auth.loginWithUsername": case "auth.loginWithUsername":
state.authenticated = true; state.authenticated = true;
@@ -282,6 +287,7 @@ export async function installAppMocks(
videos?: any[]; videos?: any[];
analyses?: any[]; analyses?: any[];
userName?: string; userName?: string;
authMeNullResponsesAfterLogin?: number;
} }
) { ) {
const state: MockAppState = { const state: MockAppState = {
@@ -312,6 +318,7 @@ export async function installAppMocks(
], ],
mediaSession: null, mediaSession: null,
nextVideoId: 100, nextVideoId: 100,
authMeNullResponsesAfterLogin: options?.authMeNullResponsesAfterLogin ?? 0,
}; };
await page.addInitScript(() => { await page.addInitScript(() => {