From e83d4834543b2abd364e140bfe67cc92bf4943d2 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 1 Dec 2023 09:15:02 +0800 Subject: [PATCH] refactor(frontend): use auth service --- web/src/App.tsx | 9 +- .../components/ChangeMemberPasswordDialog.tsx | 4 +- web/src/components/ChangePasswordDialog.tsx | 12 +- web/src/components/Memo.tsx | 8 +- web/src/components/MemoEditor/index.tsx | 16 +-- web/src/components/Settings/MemberSection.tsx | 10 +- .../Settings/PreferencesSection.tsx | 51 ++++--- web/src/components/UpdateAccountDialog.tsx | 29 ++-- web/src/components/UserBanner.tsx | 7 +- web/src/hooks/useCurrentUser.ts | 14 +- web/src/pages/AuthCallback.tsx | 6 +- web/src/pages/SignIn.tsx | 7 +- web/src/pages/SignUp.tsx | 7 +- web/src/router/index.tsx | 28 +--- web/src/store/index.ts | 2 - web/src/store/module/index.ts | 1 - web/src/store/module/user.ts | 132 ------------------ web/src/store/reducer/user.ts | 40 ------ web/src/store/v1/user.ts | 52 ++++++- 19 files changed, 136 insertions(+), 299 deletions(-) delete mode 100644 web/src/store/module/user.ts delete mode 100644 web/src/store/reducer/user.ts diff --git a/web/src/App.tsx b/web/src/App.tsx index 13906505e..09347843d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -6,7 +6,6 @@ import storage from "./helpers/storage"; import { getSystemColorScheme } from "./helpers/utils"; import useNavigateTo from "./hooks/useNavigateTo"; import Loading from "./pages/Loading"; -import store from "./store"; import { useGlobalStore } from "./store/module"; import { useUserV1Store } from "./store/v1"; @@ -28,9 +27,11 @@ const App = () => { useEffect(() => { const initialState = async () => { - const { user } = store.getState().user; - if (user) { - await userV1Store.getOrFetchUserByUsername(user.username); + try { + await userV1Store.fetchCurrentUser(); + console.log("here"); + } catch (error) { + // Skip. } setLoading(false); }; diff --git a/web/src/components/ChangeMemberPasswordDialog.tsx b/web/src/components/ChangeMemberPasswordDialog.tsx index 3eb98166a..f9b701a06 100644 --- a/web/src/components/ChangeMemberPasswordDialog.tsx +++ b/web/src/components/ChangeMemberPasswordDialog.tsx @@ -12,7 +12,7 @@ interface Props extends DialogProps { const ChangeMemberPasswordDialog: React.FC = (props: Props) => { const { user, destroy } = props; const t = useTranslate(); - const userStore = useUserV1Store(); + const userV1Store = useUserV1Store(); const [newPassword, setNewPassword] = useState(""); const [newPasswordAgain, setNewPasswordAgain] = useState(""); @@ -47,7 +47,7 @@ const ChangeMemberPasswordDialog: React.FC = (props: Props) => { } try { - await userStore.updateUser( + await userV1Store.updateUser( { name: `${UserNamePrefix}${user.username}`, password: newPassword, diff --git a/web/src/components/ChangePasswordDialog.tsx b/web/src/components/ChangePasswordDialog.tsx index 556fcf278..5a5a954f3 100644 --- a/web/src/components/ChangePasswordDialog.tsx +++ b/web/src/components/ChangePasswordDialog.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; -import { useGlobalStore, useUserStore } from "@/store/module"; -import { useUserV1Store, UserNamePrefix } from "@/store/v1"; +import useCurrentUser from "@/hooks/useCurrentUser"; +import { useGlobalStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; import { generateDialog } from "./Dialog"; import Icon from "./Icon"; @@ -10,7 +11,7 @@ type Props = DialogProps; const ChangePasswordDialog: React.FC = ({ destroy }: Props) => { const t = useTranslate(); - const userStore = useUserStore(); + const currentUser = useCurrentUser(); const userV1Store = useUserV1Store(); const globalStore = useGlobalStore(); const profile = globalStore.state.systemStatus.profile; @@ -18,7 +19,7 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => { const [newPasswordAgain, setNewPasswordAgain] = useState(""); useEffect(() => { - if (profile.mode === "demo" && userStore.state.user?.id === userStore.state.host?.id) { + if (profile.mode === "demo") { toast.error("Demo mode does not support this operation."); destroy(); } @@ -51,10 +52,9 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => { } try { - const user = userStore.getState().user as User; await userV1Store.updateUser( { - name: `${UserNamePrefix}${user.username}`, + name: currentUser.name, password: newPassword, }, ["password"] diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index d972bb470..7e89ff634 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -7,7 +7,7 @@ import { UNKNOWN_ID } from "@/helpers/consts"; import { getRelativeTimeString } from "@/helpers/datetime"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useFilterStore, useMemoStore, useUserStore } from "@/store/module"; +import { useFilterStore, useMemoStore } from "@/store/module"; import { useUserV1Store, extractUsernameFromName } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; @@ -36,7 +36,6 @@ const Memo: React.FC = (props: Props) => { const navigateTo = useNavigateTo(); const { i18n } = useTranslation(); const filterStore = useFilterStore(); - const userStore = useUserStore(); const memoStore = useMemoStore(); const userV1Store = useUserV1Store(); const user = useCurrentUser(); @@ -211,11 +210,6 @@ const Memo: React.FC = (props: Props) => { if (readonly) { return; } - - const loginUser = userStore.state.user; - if (loginUser && !loginUser.localSetting.enableDoubleClickEditing) { - return; - } const targetEl = e.target as HTMLElement; if (targetEl.className === "tag-span") { diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 83a5ddb56..2d4fb8d8a 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -8,9 +8,10 @@ import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "@/helper import { clearContentQueryParam } from "@/helpers/utils"; import useCurrentUser from "@/hooks/useCurrentUser"; import { getMatchedNodes } from "@/labs/marked"; -import { useFilterStore, useGlobalStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "@/store/module"; +import { useFilterStore, useGlobalStore, useMemoStore, useResourceStore, useTagStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; import { Resource } from "@/types/proto/api/v2/resource_service"; -import { User_Role } from "@/types/proto/api/v2/user_service"; +import { UserSetting, User_Role } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; import showCreateMemoRelationDialog from "../CreateMemoRelationDialog"; import showCreateResourceDialog from "../CreateResourceDialog"; @@ -50,7 +51,7 @@ const MemoEditor = (props: Props) => { const { state: { systemStatus }, } = useGlobalStore(); - const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const filterStore = useFilterStore(); const memoStore = useMemoStore(); const tagStore = useTagStore(); @@ -66,8 +67,7 @@ const MemoEditor = (props: Props) => { const [hasContent, setHasContent] = useState(false); const [isInIME, setIsInIME] = useState(false); const editorRef = useRef(null); - const user = userStore.state.user as User; - const setting = user.setting; + const userSetting = userV1Store.userSetting as UserSetting; const referenceRelations = memoId ? state.relationList.filter( (relation) => relation.memoId === memoId && relation.relatedMemoId !== memoId && relation.type === "REFERENCE" @@ -80,15 +80,15 @@ const MemoEditor = (props: Props) => { }, []); useEffect(() => { - let visibility = setting.memoVisibility; + let visibility = userSetting.memoVisibility; if (systemStatus.disablePublicMemos && visibility === "PUBLIC") { visibility = "PRIVATE"; } setState((prevState) => ({ ...prevState, - memoVisibility: visibility, + memoVisibility: visibility as Visibility, })); - }, [setting.memoVisibility, systemStatus.disablePublicMemos]); + }, [userSetting.memoVisibility, systemStatus.disablePublicMemos]); useEffect(() => { if (memoId) { diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index a132b2237..6ce531c7d 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -3,8 +3,8 @@ import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { userServiceClient } from "@/grpcweb"; import * as api from "@/helpers/api"; -import { useUserStore } from "@/store/module"; -import { UserNamePrefix } from "@/store/v1"; +import useCurrentUser from "@/hooks/useCurrentUser"; +import { UserNamePrefix, useUserV1Store } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v2/common"; import { User_Role } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; @@ -19,8 +19,8 @@ interface State { const MemberSection = () => { const t = useTranslate(); - const userStore = useUserStore(); - const currentUser = userStore.state.user; + const currentUser = useCurrentUser(); + const userV1Store = useUserV1Store(); const [state, setState] = useState({ createUserUsername: "", createUserPassword: "", @@ -115,7 +115,7 @@ const MemberSection = () => { style: "danger", dialogName: "delete-user-dialog", onConfirm: async () => { - await userStore.deleteUser(`${UserNamePrefix}${user.username}`); + await userV1Store.deleteUser(`${UserNamePrefix}${user.username}`); fetchUserList(); }, }); diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index 784c727e3..759dc2025 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -1,8 +1,10 @@ -import { Button, Divider, Input, Option, Select, Switch } from "@mui/joy"; -import React, { useState } from "react"; +import { Button, Divider, Input, Option, Select } from "@mui/joy"; +import { useState } from "react"; import { toast } from "react-hot-toast"; import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts"; -import { useGlobalStore, useUserStore } from "@/store/module"; +import { useGlobalStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; +import { UserSetting } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; import AppearanceSelect from "../AppearanceSelect"; import LearnMore from "../LearnMore"; @@ -14,32 +16,48 @@ import "@/less/settings/preferences-section.less"; const PreferencesSection = () => { const t = useTranslate(); const globalStore = useGlobalStore(); - const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const { appearance, locale } = globalStore.state; - const { setting, localSetting } = userStore.state.user as User; + const setting = userV1Store.userSetting as UserSetting; const [telegramUserId, setTelegramUserId] = useState(setting.telegramUserId); const handleLocaleSelectChange = async (locale: Locale) => { - await userStore.upsertUserSetting("locale", locale); + await userV1Store.updateUserSetting( + { + locale, + }, + ["locale"] + ); globalStore.setLocale(locale); }; const handleAppearanceSelectChange = async (appearance: Appearance) => { - await userStore.upsertUserSetting("appearance", appearance); + await userV1Store.updateUserSetting( + { + appearance, + }, + ["appearance"] + ); globalStore.setAppearance(appearance); }; const handleDefaultMemoVisibilityChanged = async (value: string) => { - await userStore.upsertUserSetting("memo-visibility", value); - }; - - const handleDoubleClickEnabledChanged = (event: React.ChangeEvent) => { - userStore.upsertLocalSetting({ ...localSetting, enableDoubleClickEditing: event.target.checked }); + await userV1Store.updateUserSetting( + { + memoVisibility: value, + }, + ["memo_visibility"] + ); }; const handleSaveTelegramUserId = async () => { try { - await userStore.upsertUserSetting("telegram-user-id", telegramUserId); + await userV1Store.updateUserSetting( + { + telegramUserId: telegramUserId, + }, + ["telegram_user_id"] + ); toast.success(t("message.update-succeed")); } catch (error: any) { console.error(error); @@ -68,7 +86,7 @@ const PreferencesSection = () => { - -
diff --git a/web/src/components/UpdateAccountDialog.tsx b/web/src/components/UpdateAccountDialog.tsx index 60c2768db..afd9d1baf 100644 --- a/web/src/components/UpdateAccountDialog.tsx +++ b/web/src/components/UpdateAccountDialog.tsx @@ -2,8 +2,8 @@ import { isEqual } from "lodash-es"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { convertFileToBase64 } from "@/helpers/utils"; -import { useUserStore } from "@/store/module"; -import { UserNamePrefix } from "@/store/v1"; +import useCurrentUser from "@/hooks/useCurrentUser"; +import { UserNamePrefix, useUserV1Store } from "@/store/v1"; import { User as UserPb } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; import { generateDialog } from "./Dialog"; @@ -21,13 +21,13 @@ interface State { const UpdateAccountDialog: React.FC = ({ destroy }: Props) => { const t = useTranslate(); - const userStore = useUserStore(); - const user = userStore.state.user as User; + const currentUser = useCurrentUser(); + const userV1Store = useUserV1Store(); const [state, setState] = useState({ - avatarUrl: user.avatarUrl, - username: user.username, - nickname: user.nickname, - email: user.email, + avatarUrl: currentUser.avatarUrl, + username: currentUser.name.replace(UserNamePrefix, ""), + nickname: currentUser.nickname, + email: currentUser.email, }); useEffect(() => { @@ -95,24 +95,23 @@ const UpdateAccountDialog: React.FC = ({ destroy }: Props) => { } try { - const user = userStore.getState().user as User; const updateMask = []; - if (!isEqual(user.avatarUrl, state.avatarUrl)) { + if (!isEqual(currentUser.avatarUrl, state.avatarUrl)) { updateMask.push("avatar_url"); } - if (!isEqual(user.nickname, state.nickname)) { + if (!isEqual(currentUser.nickname, state.nickname)) { updateMask.push("nickname"); } - if (!isEqual(user.username, state.username)) { + if (!isEqual(currentUser.name.replace(UserNamePrefix, ""), state.username)) { updateMask.push("username"); } - if (!isEqual(user.email, state.email)) { + if (!isEqual(currentUser.email, state.email)) { updateMask.push("email"); } - await userStore.patchUser( + await userV1Store.updateUser( UserPb.fromPartial({ name: `${UserNamePrefix}${state.username}`, - id: user.id, + id: currentUser.id, nickname: state.nickname, email: state.email, avatarUrl: state.avatarUrl, diff --git a/web/src/components/UserBanner.tsx b/web/src/components/UserBanner.tsx index f96ec7c34..b6770685c 100644 --- a/web/src/components/UserBanner.tsx +++ b/web/src/components/UserBanner.tsx @@ -1,6 +1,7 @@ +import * as api from "@/helpers/api"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useGlobalStore, useUserStore } from "@/store/module"; +import { useGlobalStore } from "@/store/module"; import { extractUsernameFromName } from "@/store/v1"; import { User_Role } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; @@ -13,7 +14,6 @@ const UserBanner = () => { const t = useTranslate(); const navigateTo = useNavigateTo(); const globalStore = useGlobalStore(); - const userStore = useUserStore(); const { systemStatus } = globalStore.state; const user = useCurrentUser(); const title = user ? user.nickname : systemStatus.customizedProfile.name || "memos"; @@ -27,7 +27,8 @@ const UserBanner = () => { }; const handleSignOutBtnClick = async () => { - await userStore.doSignOut(); + await api.signout(); + localStorage.removeItem("userId"); window.location.href = "/auth"; }; diff --git a/web/src/hooks/useCurrentUser.ts b/web/src/hooks/useCurrentUser.ts index 22a8b6ba9..db3e28c80 100644 --- a/web/src/hooks/useCurrentUser.ts +++ b/web/src/hooks/useCurrentUser.ts @@ -1,19 +1,9 @@ -import { useEffect } from "react"; -import { useUserStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; +import { User } from "@/types/proto/api/v2/user_service"; const useCurrentUser = () => { - const userStore = useUserStore(); const userV1Store = useUserV1Store(); - const currentUsername = userStore.state.user?.username; - - useEffect(() => { - if (currentUsername) { - userV1Store.getOrFetchUserByUsername(currentUsername); - } - }, [currentUsername]); - - return userV1Store.getUserByUsername(currentUsername || ""); + return userV1Store.currentUser as User; }; export default useCurrentUser; diff --git a/web/src/pages/AuthCallback.tsx b/web/src/pages/AuthCallback.tsx index 77f5982a9..b017b1fd8 100644 --- a/web/src/pages/AuthCallback.tsx +++ b/web/src/pages/AuthCallback.tsx @@ -6,7 +6,6 @@ import Icon from "@/components/Icon"; import * as api from "@/helpers/api"; import { absolutifyLink } from "@/helpers/utils"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; @@ -19,7 +18,6 @@ const AuthCallback = () => { const t = useTranslate(); const navigateTo = useNavigateTo(); const [searchParams] = useSearchParams(); - const userStore = useUserStore(); const userV1Store = useUserV1Store(); const [state, setState] = useState({ loading: true, @@ -42,9 +40,7 @@ const AuthCallback = () => { errorMessage: "", }); if (user) { - userStore.setCurrentUser(user); - await userStore.fetchCurrentUser(); - await userV1Store.getOrFetchUserByUsername(user.username); + await userV1Store.fetchCurrentUser(); navigateTo("/"); } else { toast.error(t("message.login-failed")); diff --git a/web/src/pages/SignIn.tsx b/web/src/pages/SignIn.tsx index 5ac459e21..205d7181d 100644 --- a/web/src/pages/SignIn.tsx +++ b/web/src/pages/SignIn.tsx @@ -8,7 +8,7 @@ import * as api from "@/helpers/api"; import { absolutifyLink } from "@/helpers/utils"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useGlobalStore, useUserStore } from "@/store/module"; +import { useGlobalStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; @@ -16,7 +16,6 @@ const SignIn = () => { const t = useTranslate(); const navigateTo = useNavigateTo(); const globalStore = useGlobalStore(); - const userStore = useUserStore(); const userV1Store = useUserV1Store(); const actionBtnLoadingState = useLoading(false); const { appearance, locale, systemStatus } = globalStore.state; @@ -78,9 +77,7 @@ const SignIn = () => { actionBtnLoadingState.setLoading(); const { data: user } = await api.signin(username, password, remember); if (user) { - userStore.setCurrentUser(user); - await userStore.fetchCurrentUser(); - await userV1Store.getOrFetchUserByUsername(user.username); + await userV1Store.fetchCurrentUser(); navigateTo("/"); } else { toast.error(t("message.login-failed")); diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx index 3dc12a00e..dfdc77937 100644 --- a/web/src/pages/SignUp.tsx +++ b/web/src/pages/SignUp.tsx @@ -7,7 +7,7 @@ import LocaleSelect from "@/components/LocaleSelect"; import * as api from "@/helpers/api"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useGlobalStore, useUserStore } from "@/store/module"; +import { useGlobalStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; @@ -15,7 +15,6 @@ const SignUp = () => { const t = useTranslate(); const navigateTo = useNavigateTo(); const globalStore = useGlobalStore(); - const userStore = useUserStore(); const userV1Store = useUserV1Store(); const actionBtnLoadingState = useLoading(false); const { appearance, locale, systemStatus } = globalStore.state; @@ -58,9 +57,7 @@ const SignUp = () => { actionBtnLoadingState.setLoading(); const { data: user } = await api.signup(username, password); if (user) { - userStore.setCurrentUser(user); - await userStore.fetchCurrentUser(); - await userV1Store.getOrFetchUserByUsername(user.username); + await userV1Store.fetchCurrentUser(); navigateTo("/"); } else { toast.error(t("message.signup-failed")); diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index e94b1cf75..7ec5a5370 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -1,7 +1,7 @@ import { lazy } from "react"; -import { createBrowserRouter, redirect } from "react-router-dom"; +import { createBrowserRouter } from "react-router-dom"; import App from "@/App"; -import { initialGlobalState, initialUserState } from "@/store/module"; +import { initialGlobalState } from "@/store/module"; const Root = lazy(() => import("@/layouts/Root")); const SignIn = lazy(() => import("@/pages/SignIn")); @@ -28,20 +28,6 @@ const initialGlobalStateLoader = async () => { return null; }; -const initialUserStateLoader = async (redirectWhenNotFound = true) => { - let user = undefined; - try { - user = await initialUserState(); - } catch (error) { - // do nothing. - } - - if (!user && redirectWhenNotFound) { - return redirect("/explore"); - } - return null; -}; - const router = createBrowserRouter([ { path: "/", @@ -67,54 +53,44 @@ const router = createBrowserRouter([ { path: "", element: , - loader: () => initialUserStateLoader(), }, { path: "review", element: , - loader: () => initialUserStateLoader(), }, { path: "resources", element: , - loader: () => initialUserStateLoader(), }, { path: "inbox", element: , - loader: () => initialUserStateLoader(), }, { path: "archived", element: , - loader: () => initialUserStateLoader(), }, { path: "setting", element: , - loader: () => initialUserStateLoader(), }, { path: "explore", element: , - loader: () => initialUserStateLoader(false), }, ], }, { path: "/m/:memoId", element: , - loader: () => initialUserStateLoader(false), }, { path: "/m/:memoId/embed", element: , - loader: () => initialUserStateLoader(false), }, { path: "/u/:username", element: , - loader: () => initialUserStateLoader(false), }, { path: "*", diff --git a/web/src/store/index.ts b/web/src/store/index.ts index ea0286c24..3aa3829ee 100644 --- a/web/src/store/index.ts +++ b/web/src/store/index.ts @@ -6,12 +6,10 @@ import globalReducer from "./reducer/global"; import memoReducer from "./reducer/memo"; import resourceReducer from "./reducer/resource"; import tagReducer from "./reducer/tag"; -import userReducer from "./reducer/user"; const store = configureStore({ reducer: { global: globalReducer, - user: userReducer, memo: memoReducer, tag: tagReducer, filter: filterReducer, diff --git a/web/src/store/module/index.ts b/web/src/store/module/index.ts index c4100aad8..08021e779 100644 --- a/web/src/store/module/index.ts +++ b/web/src/store/module/index.ts @@ -3,5 +3,4 @@ export * from "./filter"; export * from "./memo"; export * from "./tag"; export * from "./resource"; -export * from "./user"; export * from "./dialog"; diff --git a/web/src/store/module/user.ts b/web/src/store/module/user.ts deleted file mode 100644 index e89dc70e3..000000000 --- a/web/src/store/module/user.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { camelCase } from "lodash-es"; -import { authServiceClient, userServiceClient } from "@/grpcweb"; -import * as api from "@/helpers/api"; -import storage from "@/helpers/storage"; -import { getSystemColorScheme } from "@/helpers/utils"; -import { User as UserPb } from "@/types/proto/api/v2/user_service"; -import store, { useAppSelector } from ".."; -import { setAppearance, setLocale } from "../reducer/global"; -import { patchUser, setHost, setUser } from "../reducer/user"; - -const defaultSetting: Setting = { - locale: "en", - appearance: getSystemColorScheme(), - memoVisibility: "PRIVATE", - telegramUserId: "", -}; - -const defaultLocalSetting: LocalSetting = { - enableDoubleClickEditing: false, -}; - -export const convertResponseModelUser = (user: User): User => { - // user default 'Basic Setting' should follow server's setting - // 'Basic Setting' fields: locale, appearance - const { systemStatus } = store.getState().global; - const { locale, appearance } = systemStatus.customizedProfile; - const systemSetting = { locale, appearance }; - - const setting: Setting = { - ...defaultSetting, - ...systemSetting, - }; - const { localSetting: storageLocalSetting } = storage.get(["localSetting"]); - const localSetting: LocalSetting = { - ...defaultLocalSetting, - ...storageLocalSetting, - }; - - if (user.userSettingList) { - for (const userSetting of user.userSettingList) { - (setting as any)[camelCase(userSetting.key)] = JSON.parse(userSetting.value); - } - } - - return { - ...user, - setting, - localSetting, - createdTs: user.createdTs * 1000, - updatedTs: user.updatedTs * 1000, - }; -}; - -export const initialUserState = async () => { - const { systemStatus } = store.getState().global; - - if (systemStatus.host) { - store.dispatch(setHost(convertResponseModelUser(systemStatus.host))); - } - - const user = await fetchCurrentUser(); - if (user) { - if (user.setting.locale) { - store.dispatch(setLocale(user.setting.locale)); - } - if (user.setting.appearance) { - store.dispatch(setAppearance(user.setting.appearance)); - } - return user; - } -}; - -const doSignOut = async () => { - await api.signout(); - localStorage.removeItem("userId"); -}; - -const fetchCurrentUser = async () => { - const userId = localStorage.getItem("userId"); - if (userId) { - const { user } = await authServiceClient.getAuthStatus({}); - if (!user) { - localStorage.removeItem("userId"); - return; - } - const { data } = await api.getUserById(Number(userId)); - const userMessage = convertResponseModelUser(data); - if (userMessage) { - store.dispatch(setUser(userMessage)); - return userMessage; - } - } -}; - -export const useUserStore = () => { - const state = useAppSelector((state) => state.user); - - return { - state, - getState: () => { - return store.getState().user; - }, - doSignOut, - fetchCurrentUser, - setCurrentUser: async (user: User) => { - localStorage.setItem("userId", String(user.id)); - }, - upsertUserSetting: async (key: string, value: any) => { - await api.upsertUserSetting({ - key: key as any, - value: JSON.stringify(value), - }); - await fetchCurrentUser(); - }, - upsertLocalSetting: async (localSetting: LocalSetting) => { - storage.set({ localSetting }); - store.dispatch(patchUser({ localSetting })); - }, - patchUser: async (user: UserPb, updateMask: string[]): Promise => { - await userServiceClient.updateUser({ user, updateMask }); - // If the user is the current user and the username is changed, reload the page. - if (user.id === store.getState().user.user?.id) { - window.location.reload(); - } - }, - deleteUser: async (name: string) => { - await userServiceClient.deleteUser({ - name, - }); - }, - }; -}; diff --git a/web/src/store/reducer/user.ts b/web/src/store/reducer/user.ts deleted file mode 100644 index 57cae6d3c..000000000 --- a/web/src/store/reducer/user.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -interface State { - // host is the user who hist the system - host?: User; - // user is the user who is currently logged in - user?: User; -} - -const userSlice = createSlice({ - name: "user", - initialState: {} as State, - reducers: { - setHost: (state, action: PayloadAction) => { - return { - ...state, - host: action.payload, - }; - }, - setUser: (state, action: PayloadAction) => { - return { - ...state, - user: action.payload, - }; - }, - patchUser: (state, action: PayloadAction>) => { - return { - ...state, - user: { - ...state.user, - ...action.payload, - } as User, - }; - }, - }, -}); - -export const { setHost, setUser, patchUser } = userSlice.actions; - -export default userSlice.reducer; diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts index b55617126..0c03e68d2 100644 --- a/web/src/store/v1/user.ts +++ b/web/src/store/v1/user.ts @@ -1,15 +1,29 @@ import { create } from "zustand"; -import { userServiceClient } from "@/grpcweb"; -import { User } from "@/types/proto/api/v2/user_service"; +import { authServiceClient, userServiceClient } from "@/grpcweb"; +import { User, UserSetting } from "@/types/proto/api/v2/user_service"; import { UserNamePrefix, extractUsernameFromName } from "./resourceName"; interface UserV1Store { userMapByUsername: Record; + currentUser?: User; + userSetting?: UserSetting; getOrFetchUserByUsername: (username: string) => Promise; getUserByUsername: (username: string) => User; updateUser: (user: Partial, updateMask: string[]) => Promise; + deleteUser: (name: string) => Promise; + fetchCurrentUser: () => Promise; + setCurrentUser: (user: User) => void; + updateUserSetting: (userSetting: Partial, updateMark: string[]) => Promise; } +const getDefaultUserSetting = () => { + return UserSetting.fromPartial({ + locale: "en", + appearance: "auto", + memoVisibility: "PRIVATE", + }); +}; + // Request cache is used to prevent multiple requests. const requestCache = new Map>(); @@ -57,4 +71,38 @@ export const useUserV1Store = create()((set, get) => ({ set(userMap); return updatedUser; }, + deleteUser: async (name: string) => { + await userServiceClient.deleteUser({ + name, + }); + }, + fetchCurrentUser: async () => { + const { user } = await authServiceClient.getAuthStatus({}); + if (!user) { + throw new Error("User not found"); + } + set({ currentUser: user }); + const { setting } = await userServiceClient.getUserSetting({}); + set({ + userSetting: UserSetting.fromPartial({ + ...getDefaultUserSetting(), + ...setting, + }), + }); + return user; + }, + setCurrentUser: (user: User) => { + set({ currentUser: user }); + }, + updateUserSetting: async (userSetting: Partial, updateMask: string[]) => { + const { setting: updatedUserSetting } = await userServiceClient.updateUserSetting({ + setting: userSetting, + updateMask: updateMask, + }); + if (!updatedUserSetting) { + throw new Error("User setting not found"); + } + set({ userSetting: updatedUserSetting }); + return updatedUserSetting; + }, }));