feat(login-ui): dedupe submissions and surface guard error codes

Collapse rapid double-submits into a single in-flight request across
admin-web, ops-web and the desktop client so users cannot fire parallel
login attempts and trip the new in-progress guard. Map the new
login_rate_limited / login_locked / login_in_progress / login_guard_unavailable
error codes to localized messages in every login surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 01:07:32 +08:00
parent 19037a9e4b
commit 5216a4847c
5 changed files with 104 additions and 31 deletions
+20 -7
View File
@@ -137,15 +137,27 @@ export const useAuthStore = defineStore("auth", () => {
initialized.value = true;
}
let loginPromise: Promise<UserInfo> | null = null;
async function login(payload: LoginRequest): Promise<UserInfo> {
const response = await authApi.login(payload);
setStoredTokens({
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresAt: response.expires_at,
if (loginPromise) {
return loginPromise;
}
const loginPayload = { ...payload };
loginPromise = authApi.login(loginPayload).then((response) => {
setStoredTokens({
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresAt: response.expires_at,
});
setStoredUser(response.user);
return response.user;
}).finally(() => {
loginPromise = null;
});
setStoredUser(response.user);
return response.user;
return loginPromise;
}
async function logout(): Promise<void> {
@@ -164,6 +176,7 @@ export const useAuthStore = defineStore("auth", () => {
const value = membership.value;
return Boolean(
value?.blocked ||
value?.blocked_reason === "user_disabled" ||
value?.status === "expired" ||
value?.blocked_reason === "trial_plan_expired" ||
value?.blocked_reason === "subscription_required" ||