fix(auth): redirect to splash after logout
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
import { MobileShell, SettingsSection } from "@/app/_components/core";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
@@ -36,15 +37,24 @@ export function SidebarScreen() {
|
||||
const auth = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const router = useRouter();
|
||||
const isLogoutRedirectPendingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
userDispatch({ type: "UserInit" });
|
||||
}, [userDispatch]);
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
authDispatch({ type: "AuthReset" });
|
||||
userDispatch({ type: "UserLogout" });
|
||||
useEffect(() => {
|
||||
if (!isLogoutRedirectPendingRef.current) return;
|
||||
if (auth.isLoading || auth.loginStatus !== "notLoggedIn") return;
|
||||
isLogoutRedirectPendingRef.current = false;
|
||||
router.replace(ROUTES.splash);
|
||||
}, [auth.isLoading, auth.loginStatus, router]);
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
isLogoutRedirectPendingRef.current = true;
|
||||
userDispatch({ type: "UserClearLocal" });
|
||||
authDispatch({ type: "AuthLogoutSubmitted" });
|
||||
void signOut({ redirect: false });
|
||||
};
|
||||
|
||||
// 状态派生:未登录 / 已登录未 VIP / 已登录 VIP
|
||||
|
||||
@@ -97,6 +97,11 @@ export const oauthSignInActor = fromPromise<void, AuthProvider>(async ({ input }
|
||||
}
|
||||
});
|
||||
|
||||
export const logoutActor = fromPromise<void>(async () => {
|
||||
const result = await authRepo.logout();
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
});
|
||||
|
||||
// ========== OAuth token → 后端业务 token sync actors ==========
|
||||
// 由 <OAuthSessionSync /> 在 NextAuth 回调后派发(监听 useSession())。
|
||||
// 用 OAuth provider 的 idToken / accessToken 调后端换业务 token + user。
|
||||
|
||||
@@ -67,6 +67,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
state.matches("loadingEmailLogin") ||
|
||||
state.matches("loadingEmailRegister") ||
|
||||
state.matches("loadingOAuth") ||
|
||||
state.matches("loggingOut") ||
|
||||
state.matches("syncingGoogleBackend") ||
|
||||
state.matches("syncingFacebookBackend") ||
|
||||
state.matches("loadingGuestLogin"),
|
||||
|
||||
@@ -13,6 +13,7 @@ export type AuthEvent =
|
||||
| { type: "AuthModeChanged"; mode: AuthMode }
|
||||
| { type: "AuthFormCleared" }
|
||||
| { type: "AuthReset" }
|
||||
| { type: "AuthLogoutSubmitted" }
|
||||
/** App 启动时一次性派发 —— 读 storage 把 loginStatus 同步到状态机 */
|
||||
| { type: "AuthInit" }
|
||||
/** 显式游客登录 —— splash 上点"游客模式"按钮触发(不自动) */
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
syncFacebookBackendActor,
|
||||
guestLoginActor,
|
||||
checkAuthStatusActor,
|
||||
logoutActor,
|
||||
} from "./auth-actors";
|
||||
|
||||
// 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变
|
||||
@@ -46,6 +47,7 @@ export const authMachine = setup({
|
||||
syncFacebookBackend: syncFacebookBackendActor,
|
||||
guestLogin: guestLoginActor,
|
||||
checkAuthStatus: checkAuthStatusActor,
|
||||
logout: logoutActor,
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEMCuAXAFgOgJYQBswBiAQQ0wAVkA7MAgWQHsIwBhTWmCAbQAYAuolAAHJrFzpcTGsJAAPRABYA7NgAc6gEzqAjCvUBOLbvUqlWgDQgAnohWnsAZgCsSpXyeHdTgGy6lXwBfIOs0LDxCEnIsZlYOLkh+ISQQMQkpGTlFBF81Y1MzX3UvdT5NaztcrS1sD10tQ1dvPi0+YNCQcJx8IjIKADEmACcAWzYiZGGkwTl0yWlZVJzzbHMarSUvNpctYsrEXd86pT0SvfULXyUQsIpIvpjMACU4MHRkufEFrOXEXT4fGwvkMLicmxUTT4qj4hgOCC2QKUoL4KkaShc5ycty69160QoAEkaJJPql5pklqAcl4nGtDCCtGDdqotCp4YY9MC0Xw3CpfH5Gi4cd0HgSsABxVBwdAAGSYUFwNAAyqgAEajSToGYpUTfSnZRC0+mM5kQtnwlzlDQuQynEpOJz6BoqEV4qL9LAAUVGyFwBHlipV6s16G1vFm5P1i0NCGNkNNrnN7Ns9gx2D2LhUDjcl0BN06ovxnswPr9BFeitg2uGqo1Wp1XwyMb+caaJuMZtZKaquhBQKMsOhHiafY6dwixaeEqYCqIgaVddD4bJeubv2pRvbCc7Se78P0SjqYLaewuTtObsnHqeA2QAGMwGrZwBrBfB+thxtR9dUhRbukdyZPc0R7RAaiPUEnRKREjH0dQrx6G8KFIEQRHnBVFxDBsI11NJow3f8EVqVRfCzJ14L4Qp4UdI83F0Bkz3BJxtEQsUSxnOcwGVGwaHvJccNXfDf1jeMGV3FlQIPDEj1OQITAYjwijYqdBgfJ9Xx4viBK-XCmx+P8aW3cTgMki1UwQBpHFRB1IQMdRMXHXFr0eCgJjAKZKDAGgICVKBXl86Z7w+SM1wM2MLGwQFopimKAnhLQWOwTFNF2JxAWuDwtBUkkpGQAhcAALz84gIBkMA8BoAA3JgXwqotctwfKir8hAlRq+9kEpZIhIpFtNwRAVkt8JkdCaXxWhMeFNHUNZTEMTwXExaEXF0HKFma4qaCgYgwGGYYRmwdCuoAMxGUZsAajaCq2qA2uqphOu6wReoIwzlGubAFoFUw3BMEpQXhSEXDWMiVDSlkJtdQt7gIJhkF87apRld9SvKyqarqy7YfhxGoGR6t33ujqusWHrQuE8LWytIFwT0fxAW0F14SUXQQe+x0HDZRy7TYuGEb8gm5Uwmhdv2w7jvQM6xmxiJ+bxoWifax7SZkcm8L6wicitXRuQYq1-C2cwXBomovrBX7jDaVonNFeW-LLf1UbKugMdq+qcYF7bHYDEXiZV56BFekTqdaPXxs5PJdGj+EyNqJ0z08Cx6b53GHd9J2RbFg7hiOghTvO2WcHt72M99oN-aesmXopzX3oQK0jyolxrkS05JuzS1bWcW1Am8Jw0SdW3Pbxn3K1was9rR13laxu209L8tx8n4ZK9Vmh1f0g1WwsXWLacEcjAPxuD1tfIB-B8FPBUcFsphuWF6gMewCrGts4l-OpcL+evafsvl5rGvQOwcqYDQsLUMwjcBQGxbmBSyjcvqXAmgxTkAQDCp1-gAeSeNPCqs8PYPywU8IB1cg61zehFUEyUDCmD8M6TQ-IErg2PEPCwB9QQmAwXjbBFB3650ltLC6P9uHEOVlXNWNcNYUJ3gyL6-1NCGAMDTPw0097RVWrvCEAQ2KwF4veQWs4oBEAAEIPjqj5XBbs573F0XxAxXFTH3nMRAEhEiyFSJDmArYUU2Y-VMCCTQugDx0OSheEEnIHKeDvhOHAtj9FI0MSYsx3kIB8LzgXGWoo4n2KMWARxzjXEb0kVvfqRF3BHmaBYVEo4TYWQaMUahJRTAGEaIUHRei-J3kfM+Wq+SUmWPwUXbA2TtpdI0r05JPlCmbx-KAspexnANHIiYTkqIBQHiWdgJZ-jszgnWO0uxoz1I9JfH0ixe0c7pK-pkmxHSjndNfGclxYj14zLCtvMBbJgQtzZA5PQ0cmgHiiVsnkrhYHFE5CEToNAWBwDkN0EpWtEAAFp2aGHRRizF6L1C+HhMi4eLkwCIvrpsowlxsxgjRAEUwqjDDOFRBoxKI1ygIXvj0Rqm0-LEtjAEnudo-DjQ8CNVRQJo43zWQKR0pguGC2lITEW3LWy8sqQKvwQqrAWU5vS1QrNEo6BvgS4uj8fbvkVQNZVvdVXXHaBqqo5E5oAl8GDXkvyZWL39AAvaZqiIWv5SCNVNrT4MWcHQ8iQ5mluqgDwrA3qci+q2P661wq6lh30E68pmh3CbGxGy4Zdz8aJLyZMiAsbEDxqteqg8OhajGE5OYWEA8jDQxiXmw5UAxknKeaW3InI+UJsFYGlNgRkqJVtOUJRE1WUhCAA */
|
||||
@@ -79,6 +81,7 @@ export const authMachine = setup({
|
||||
AuthReset: {
|
||||
actions: assign(() => initialState),
|
||||
},
|
||||
AuthLogoutSubmitted: "loggingOut",
|
||||
// 启动一次性 init:从 storage 同步 loginStatus 到状态机 —— 由 <AuthStatusChecker /> 派发
|
||||
AuthInit: "initializing",
|
||||
|
||||
@@ -230,6 +233,25 @@ export const authMachine = setup({
|
||||
},
|
||||
},
|
||||
|
||||
loggingOut: {
|
||||
entry: assign({ errorMessage: null, pendingRedirect: false }),
|
||||
invoke: {
|
||||
src: "logout",
|
||||
onDone: {
|
||||
target: "idle",
|
||||
actions: assign(() => initialState),
|
||||
},
|
||||
onError: {
|
||||
target: "idle",
|
||||
actions: assign(({ event }) => ({
|
||||
...initialState,
|
||||
errorMessage:
|
||||
event.error instanceof Error ? event.error.message : String(event.error),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
syncingGoogleBackend: {
|
||||
entry: assign({ errorMessage: null }),
|
||||
invoke: {
|
||||
|
||||
@@ -9,6 +9,7 @@ export type UserEvent =
|
||||
| { type: "UserUpdate"; user: UserView }
|
||||
| { type: "UserUpdateUsername"; username: string }
|
||||
| { type: "UserUpdatePronouns"; pronouns: string }
|
||||
| { type: "UserClearLocal" }
|
||||
| { type: "UserLogout" }
|
||||
| { type: "UserDeleteChatHistory" }
|
||||
| { type: "UserDeleteAccount" };
|
||||
|
||||
@@ -70,6 +70,9 @@ export const userMachine = setup({
|
||||
UserUpdatePronouns: {
|
||||
actions: "updatePronouns",
|
||||
},
|
||||
UserClearLocal: {
|
||||
actions: "clearUser",
|
||||
},
|
||||
UserLogout: "loggingOut",
|
||||
UserDeleteChatHistory: {},
|
||||
UserDeleteAccount: {},
|
||||
@@ -125,4 +128,4 @@ export const userMachine = setup({
|
||||
},
|
||||
});
|
||||
|
||||
export type UserMachine = typeof userMachine;
|
||||
export type UserMachine = typeof userMachine;
|
||||
|
||||
Reference in New Issue
Block a user