diff --git a/web/src/components/MemoReactionListView.tsx b/web/src/components/MemoReactionListView.tsx index 359c8f23..5ec1189c 100644 --- a/web/src/components/MemoReactionListView.tsx +++ b/web/src/components/MemoReactionListView.tsx @@ -1,7 +1,7 @@ import { uniq } from "lodash-es"; import { memo, useEffect, useState } from "react"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { useUserStore } from "@/store/v1"; +import { userStore } from "@/store/v2"; import { State } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; import { Reaction } from "@/types/proto/api/v1/reaction_service"; @@ -17,7 +17,6 @@ interface Props { const MemoReactionListView = (props: Props) => { const { memo, reactions } = props; const currentUser = useCurrentUser(); - const userStore = useUserStore(); const [reactionGroup, setReactionGroup] = useState>(new Map()); const readonly = memo.state === State.ARCHIVED; diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 14d3a3b2..262fdd1d 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -5,8 +5,8 @@ import { Link, useLocation } from "react-router-dom"; import useAsyncEffect from "@/hooks/useAsyncEffect"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore, useMemoStore, useUserStatsStore } from "@/store/v1"; -import { workspaceStore } from "@/store/v2"; +import { useMemoStore, useUserStatsStore } from "@/store/v1"; +import { userStore, workspaceStore } from "@/store/v2"; import { State } from "@/types/proto/api/v1/common"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { Memo, Visibility } from "@/types/proto/api/v1/memo_service"; @@ -44,7 +44,6 @@ const MemoView: React.FC = (props: Props) => { const location = useLocation(); const navigateTo = useNavigateTo(); const currentUser = useCurrentUser(); - const userStore = useUserStore(); const user = useCurrentUser(); const memoStore = useMemoStore(); const userStatsStore = useUserStatsStore(); diff --git a/web/src/components/PasswordSignInForm.tsx b/web/src/components/PasswordSignInForm.tsx index 9b4d956d..99a1296d 100644 --- a/web/src/components/PasswordSignInForm.tsx +++ b/web/src/components/PasswordSignInForm.tsx @@ -7,14 +7,13 @@ import { toast } from "react-hot-toast"; import { authServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore } from "@/store/v1"; import { workspaceStore } from "@/store/v2"; +import { initialUserStore } from "@/store/v2/user"; import { useTranslate } from "@/utils/i18n"; const PasswordSignInForm = observer(() => { const t = useTranslate(); const navigateTo = useNavigateTo(); - const userStore = useUserStore(); const actionBtnLoadingState = useLoading(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -54,7 +53,7 @@ const PasswordSignInForm = observer(() => { try { actionBtnLoadingState.setLoading(); await authServiceClient.signIn({ username, password, neverExpire: remember }); - await userStore.fetchCurrentUser(); + await initialUserStore(); navigateTo("/"); } catch (error: any) { console.error(error); diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index 1bf4750c..acdb7a17 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -6,12 +6,22 @@ import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { userServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { stringifyUserRole, useUserStore } from "@/store/v1"; +import { userStore } from "@/store/v2"; import { State } from "@/types/proto/api/v1/common"; import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; +const stringifyUserRole = (role: User_Role) => { + if (role === User_Role.HOST) { + return "Host"; + } else if (role === User_Role.ADMIN) { + return "Admin"; + } else { + return "User"; + } +}; + interface LocalState { creatingUser: User; } @@ -19,7 +29,6 @@ interface LocalState { const MemberSection = () => { const t = useTranslate(); const currentUser = useCurrentUser(); - const userStore = useUserStore(); const [state, setState] = useState({ creatingUser: User.fromPartial({ username: "", diff --git a/web/src/components/UpdateAccountDialog.tsx b/web/src/components/UpdateAccountDialog.tsx index 62b3904f..818a03d1 100644 --- a/web/src/components/UpdateAccountDialog.tsx +++ b/web/src/components/UpdateAccountDialog.tsx @@ -6,8 +6,7 @@ import { useState } from "react"; import { toast } from "react-hot-toast"; import { convertFileToBase64 } from "@/helpers/utils"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { useUserStore } from "@/store/v1"; -import { workspaceStore } from "@/store/v2"; +import { userStore, workspaceStore } from "@/store/v2"; import { User as UserPb } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import { generateDialog } from "./Dialog"; @@ -26,7 +25,6 @@ interface State { const UpdateAccountDialog: React.FC = ({ destroy }: Props) => { const t = useTranslate(); const currentUser = useCurrentUser(); - const userStore = useUserStore(); const [state, setState] = useState({ avatarUrl: currentUser.avatarUrl, username: currentUser.username, diff --git a/web/src/pages/AuthCallback.tsx b/web/src/pages/AuthCallback.tsx index a333f70d..8488066d 100644 --- a/web/src/pages/AuthCallback.tsx +++ b/web/src/pages/AuthCallback.tsx @@ -6,7 +6,7 @@ import { useSearchParams } from "react-router-dom"; import { authServiceClient } from "@/grpcweb"; import { absolutifyLink } from "@/helpers/utils"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore } from "@/store/v1"; +import { initialUserStore } from "@/store/v2/user"; interface State { loading: boolean; @@ -16,7 +16,6 @@ interface State { const AuthCallback = () => { const navigateTo = useNavigateTo(); const [searchParams] = useSearchParams(); - const userStore = useUserStore(); const [state, setState] = useState({ loading: true, errorMessage: "", @@ -55,7 +54,7 @@ const AuthCallback = () => { loading: false, errorMessage: "", }); - await userStore.fetchCurrentUser(); + await initialUserStore(); navigateTo("/"); } catch (error: any) { console.error(error); diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx index 5ae6ea0b..19c05016 100644 --- a/web/src/pages/SignUp.tsx +++ b/web/src/pages/SignUp.tsx @@ -10,14 +10,13 @@ import LocaleSelect from "@/components/LocaleSelect"; import { authServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore } from "@/store/v1"; import { workspaceStore } from "@/store/v2"; +import { initialUserStore } from "@/store/v2/user"; import { useTranslate } from "@/utils/i18n"; const SignUp = observer(() => { const t = useTranslate(); const navigateTo = useNavigateTo(); - const userStore = useUserStore(); const actionBtnLoadingState = useLoading(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -58,7 +57,7 @@ const SignUp = observer(() => { try { actionBtnLoadingState.setLoading(); await authServiceClient.signUp({ username, password }); - await userStore.fetchCurrentUser(); + await initialUserStore(); navigateTo("/"); } catch (error: any) { console.error(error); diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index 9383c629..10c9294c 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -11,7 +11,8 @@ import MobileHeader from "@/components/MobileHeader"; import PagedMemoList from "@/components/PagedMemoList"; import UserAvatar from "@/components/UserAvatar"; import useLoading from "@/hooks/useLoading"; -import { useMemoFilterStore, useUserStore } from "@/store/v1"; +import { useMemoFilterStore } from "@/store/v1"; +import { userStore } from "@/store/v2"; import { Direction, State } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; import { User } from "@/types/proto/api/v1/user_service"; @@ -20,7 +21,6 @@ import { useTranslate } from "@/utils/i18n"; const UserProfile = () => { const t = useTranslate(); const params = useParams(); - const userStore = useUserStore(); const loadingState = useLoading(); const [user, setUser] = useState(); const memoFilterStore = useMemoFilterStore(); @@ -32,7 +32,7 @@ const UserProfile = () => { } userStore - .fetchUserByUsername(username) + .getOrFetchUserByName(username) .then((user) => { setUser(user); loadingState.setFinish(); diff --git a/web/src/store/v1/index.ts b/web/src/store/v1/index.ts index 2a0289fb..abdb5a64 100644 --- a/web/src/store/v1/index.ts +++ b/web/src/store/v1/index.ts @@ -1,4 +1,3 @@ -export * from "./user"; export * from "./memo"; export * from "./resourceName"; export * from "./resource"; diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts deleted file mode 100644 index 483a05c5..00000000 --- a/web/src/store/v1/user.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { create } from "zustand"; -import { combine } from "zustand/middleware"; -import { authServiceClient, userServiceClient } from "@/grpcweb"; -import { User, UserSetting, User_Role } from "@/types/proto/api/v1/user_service"; - -interface State { - userMapByName: Record; - // The name of current user. Format: `users/${uid}` - currentUser?: string; - userSetting?: UserSetting; -} - -const getDefaultState = (): State => ({ - userMapByName: {}, - currentUser: undefined, - userSetting: undefined, -}); - -const getDefaultUserSetting = () => { - return UserSetting.fromPartial({ - locale: "en", - appearance: "auto", - memoVisibility: "PRIVATE", - }); -}; - -// Request cache is used to prevent multiple requests. -const requestCache = new Map>(); - -export const useUserStore = create( - combine(getDefaultState(), (set, get) => ({ - getState: () => get(), - fetchUsers: async () => { - const { users } = await userServiceClient.listUsers({}); - const userMap = get().userMapByName; - for (const user of users) { - userMap[user.name] = user; - } - set({ userMapByName: userMap }); - return users; - }, - getOrFetchUserByName: async (name: string) => { - const userMap = get().userMapByName; - if (userMap[name]) { - return userMap[name] as User; - } - if (requestCache.has(name)) { - return await requestCache.get(name); - } - - const promisedUser = userServiceClient - .getUser({ - name: name, - }) - .then((user) => user); - requestCache.set(name, promisedUser); - const user = await promisedUser; - if (!user) { - throw new Error("User not found"); - } - requestCache.delete(name); - userMap[name] = user; - set({ userMapByName: userMap }); - return user; - }, - fetchUserByUsername: async (username: string) => { - const user = await userServiceClient.getUserByUsername({ username }); - const userMap = get().userMapByName; - userMap[user.name] = user; - set({ userMapByName: userMap }); - return user; - }, - listUsers: async () => { - const { users } = await userServiceClient.listUsers({}); - const userMap = get().userMapByName; - for (const user of users) { - userMap[user.name] = user; - } - set({ userMapByName: userMap }); - return users; - }, - getUserByName: (name: string) => { - const userMap = get().userMapByName; - return userMap[name]; - }, - updateUser: async (user: Partial, updateMask: string[]) => { - const updatedUser = await userServiceClient.updateUser({ - user: user, - updateMask: updateMask, - }); - const userMap = get().userMapByName; - if (user.name && user.name !== updatedUser.name) { - delete userMap[user.name]; - } - userMap[updatedUser.name] = updatedUser; - set({ userMapByName: userMap }); - if (user.name === get().currentUser) { - set({ currentUser: updatedUser.name }); - } - return updatedUser; - }, - deleteUser: async (name: string) => { - await userServiceClient.deleteUser({ - name, - }); - const userMap = get().userMapByName; - delete userMap[name]; - set({ userMapByName: userMap }); - }, - fetchCurrentUser: async () => { - const user = await authServiceClient.getAuthStatus({}); - const userMap = get().userMapByName; - userMap[user.name] = user; - set({ currentUser: user.name, userMapByName: userMap }); - const setting = await userServiceClient.getUserSetting({}); - set({ - userSetting: UserSetting.fromPartial({ - ...getDefaultUserSetting(), - ...setting, - }), - }); - return user; - }, - updateUserSetting: async (userSetting: Partial, updateMask: string[]) => { - const updatedUserSetting = await userServiceClient.updateUserSetting({ - setting: userSetting, - updateMask: updateMask, - }); - set({ userSetting: updatedUserSetting }); - return updatedUserSetting; - }, - })), -); - -export const stringifyUserRole = (role: User_Role) => { - if (role === User_Role.HOST) { - return "Host"; - } else if (role === User_Role.ADMIN) { - return "Admin"; - } else { - return "User"; - } -}; diff --git a/web/src/store/v2/user.ts b/web/src/store/v2/user.ts index f04ff01d..d7aa72d9 100644 --- a/web/src/store/v2/user.ts +++ b/web/src/store/v2/user.ts @@ -40,6 +40,22 @@ const userStore = (() => { return user; }; + const getUserByName = (name: string) => { + return state.userMapByName[name]; + }; + + const fetchUsers = async () => { + const { users } = await userServiceClient.listUsers({}); + const userMap = state.userMapByName; + for (const user of users) { + userMap[user.name] = user; + } + state.setPartial({ + userMapByName: userMap, + }); + return users; + }; + const updateUser = async (user: Partial, updateMask: string[]) => { const updatedUser = await userServiceClient.updateUser({ user, @@ -53,6 +69,15 @@ const userStore = (() => { }); }; + const deleteUser = async (name: string) => { + await userServiceClient.deleteUser({ name }); + const userMap = state.userMapByName; + delete userMap[name]; + state.setPartial({ + userMapByName: userMap, + }); + }; + const updateUserSetting = async (userSetting: Partial, updateMask: string[]) => { const updatedUserSetting = await userServiceClient.updateUserSetting({ setting: userSetting, @@ -103,7 +128,10 @@ const userStore = (() => { return { state, getOrFetchUserByName, + getUserByName, + fetchUsers, updateUser, + deleteUser, updateUserSetting, fetchShortcuts, fetchInboxes,