Commit 7a57b5c6 authored by Johnny's avatar Johnny

refactor: user store

parent 11b9c240
import { uniq } from "lodash-es"; import { uniq } from "lodash-es";
import { memo, useEffect, useState } from "react"; import { memo, useEffect, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useUserStore } from "@/store/v1"; import { userStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { Reaction } from "@/types/proto/api/v1/reaction_service"; import { Reaction } from "@/types/proto/api/v1/reaction_service";
...@@ -17,7 +17,6 @@ interface Props { ...@@ -17,7 +17,6 @@ interface Props {
const MemoReactionListView = (props: Props) => { const MemoReactionListView = (props: Props) => {
const { memo, reactions } = props; const { memo, reactions } = props;
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const userStore = useUserStore();
const [reactionGroup, setReactionGroup] = useState<Map<string, User[]>>(new Map()); const [reactionGroup, setReactionGroup] = useState<Map<string, User[]>>(new Map());
const readonly = memo.state === State.ARCHIVED; const readonly = memo.state === State.ARCHIVED;
......
...@@ -5,8 +5,8 @@ import { Link, useLocation } from "react-router-dom"; ...@@ -5,8 +5,8 @@ import { Link, useLocation } from "react-router-dom";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserStore, useMemoStore, useUserStatsStore } from "@/store/v1"; import { useMemoStore, useUserStatsStore } from "@/store/v1";
import { workspaceStore } from "@/store/v2"; import { userStore, workspaceStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service"; import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
...@@ -44,7 +44,6 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -44,7 +44,6 @@ const MemoView: React.FC<Props> = (props: Props) => {
const location = useLocation(); const location = useLocation();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const userStore = useUserStore();
const user = useCurrentUser(); const user = useCurrentUser();
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const userStatsStore = useUserStatsStore(); const userStatsStore = useUserStatsStore();
......
...@@ -7,14 +7,13 @@ import { toast } from "react-hot-toast"; ...@@ -7,14 +7,13 @@ import { toast } from "react-hot-toast";
import { authServiceClient } from "@/grpcweb"; import { authServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserStore } from "@/store/v1";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { initialUserStore } from "@/store/v2/user";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const PasswordSignInForm = observer(() => { const PasswordSignInForm = observer(() => {
const t = useTranslate(); const t = useTranslate();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const userStore = useUserStore();
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
...@@ -54,7 +53,7 @@ const PasswordSignInForm = observer(() => { ...@@ -54,7 +53,7 @@ const PasswordSignInForm = observer(() => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await authServiceClient.signIn({ username, password, neverExpire: remember }); await authServiceClient.signIn({ username, password, neverExpire: remember });
await userStore.fetchCurrentUser(); await initialUserStore();
navigateTo("/"); navigateTo("/");
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
......
...@@ -6,12 +6,22 @@ import React, { useEffect, useState } from "react"; ...@@ -6,12 +6,22 @@ import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { userServiceClient } from "@/grpcweb"; import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; 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 { State } from "@/types/proto/api/v1/common";
import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { User, User_Role } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; 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 { interface LocalState {
creatingUser: User; creatingUser: User;
} }
...@@ -19,7 +29,6 @@ interface LocalState { ...@@ -19,7 +29,6 @@ interface LocalState {
const MemberSection = () => { const MemberSection = () => {
const t = useTranslate(); const t = useTranslate();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const userStore = useUserStore();
const [state, setState] = useState<LocalState>({ const [state, setState] = useState<LocalState>({
creatingUser: User.fromPartial({ creatingUser: User.fromPartial({
username: "", username: "",
......
...@@ -6,8 +6,7 @@ import { useState } from "react"; ...@@ -6,8 +6,7 @@ import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { convertFileToBase64 } from "@/helpers/utils"; import { convertFileToBase64 } from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useUserStore } from "@/store/v1"; import { userStore, workspaceStore } from "@/store/v2";
import { workspaceStore } from "@/store/v2";
import { User as UserPb } from "@/types/proto/api/v1/user_service"; import { User as UserPb } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
...@@ -26,7 +25,6 @@ interface State { ...@@ -26,7 +25,6 @@ interface State {
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => { const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
const t = useTranslate(); const t = useTranslate();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const userStore = useUserStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
avatarUrl: currentUser.avatarUrl, avatarUrl: currentUser.avatarUrl,
username: currentUser.username, username: currentUser.username,
......
...@@ -6,7 +6,7 @@ import { useSearchParams } from "react-router-dom"; ...@@ -6,7 +6,7 @@ import { useSearchParams } from "react-router-dom";
import { authServiceClient } from "@/grpcweb"; import { authServiceClient } from "@/grpcweb";
import { absolutifyLink } from "@/helpers/utils"; import { absolutifyLink } from "@/helpers/utils";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserStore } from "@/store/v1"; import { initialUserStore } from "@/store/v2/user";
interface State { interface State {
loading: boolean; loading: boolean;
...@@ -16,7 +16,6 @@ interface State { ...@@ -16,7 +16,6 @@ interface State {
const AuthCallback = () => { const AuthCallback = () => {
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const userStore = useUserStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
loading: true, loading: true,
errorMessage: "", errorMessage: "",
...@@ -55,7 +54,7 @@ const AuthCallback = () => { ...@@ -55,7 +54,7 @@ const AuthCallback = () => {
loading: false, loading: false,
errorMessage: "", errorMessage: "",
}); });
await userStore.fetchCurrentUser(); await initialUserStore();
navigateTo("/"); navigateTo("/");
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
......
...@@ -10,14 +10,13 @@ import LocaleSelect from "@/components/LocaleSelect"; ...@@ -10,14 +10,13 @@ import LocaleSelect from "@/components/LocaleSelect";
import { authServiceClient } from "@/grpcweb"; import { authServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserStore } from "@/store/v1";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { initialUserStore } from "@/store/v2/user";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const SignUp = observer(() => { const SignUp = observer(() => {
const t = useTranslate(); const t = useTranslate();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const userStore = useUserStore();
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
...@@ -58,7 +57,7 @@ const SignUp = observer(() => { ...@@ -58,7 +57,7 @@ const SignUp = observer(() => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await authServiceClient.signUp({ username, password }); await authServiceClient.signUp({ username, password });
await userStore.fetchCurrentUser(); await initialUserStore();
navigateTo("/"); navigateTo("/");
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
......
...@@ -11,7 +11,8 @@ import MobileHeader from "@/components/MobileHeader"; ...@@ -11,7 +11,8 @@ import MobileHeader from "@/components/MobileHeader";
import PagedMemoList from "@/components/PagedMemoList"; import PagedMemoList from "@/components/PagedMemoList";
import UserAvatar from "@/components/UserAvatar"; import UserAvatar from "@/components/UserAvatar";
import useLoading from "@/hooks/useLoading"; 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 { Direction, State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service"; import { User } from "@/types/proto/api/v1/user_service";
...@@ -20,7 +21,6 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -20,7 +21,6 @@ import { useTranslate } from "@/utils/i18n";
const UserProfile = () => { const UserProfile = () => {
const t = useTranslate(); const t = useTranslate();
const params = useParams(); const params = useParams();
const userStore = useUserStore();
const loadingState = useLoading(); const loadingState = useLoading();
const [user, setUser] = useState<User>(); const [user, setUser] = useState<User>();
const memoFilterStore = useMemoFilterStore(); const memoFilterStore = useMemoFilterStore();
...@@ -32,7 +32,7 @@ const UserProfile = () => { ...@@ -32,7 +32,7 @@ const UserProfile = () => {
} }
userStore userStore
.fetchUserByUsername(username) .getOrFetchUserByName(username)
.then((user) => { .then((user) => {
setUser(user); setUser(user);
loadingState.setFinish(); loadingState.setFinish();
......
export * from "./user";
export * from "./memo"; export * from "./memo";
export * from "./resourceName"; export * from "./resourceName";
export * from "./resource"; export * from "./resource";
......
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<string, User>;
// 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<string, Promise<any>>();
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<User>, 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<UserSetting>, 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";
}
};
...@@ -40,6 +40,22 @@ const userStore = (() => { ...@@ -40,6 +40,22 @@ const userStore = (() => {
return user; 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<User>, updateMask: string[]) => { const updateUser = async (user: Partial<User>, updateMask: string[]) => {
const updatedUser = await userServiceClient.updateUser({ const updatedUser = await userServiceClient.updateUser({
user, user,
...@@ -53,6 +69,15 @@ const userStore = (() => { ...@@ -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<UserSetting>, updateMask: string[]) => { const updateUserSetting = async (userSetting: Partial<UserSetting>, updateMask: string[]) => {
const updatedUserSetting = await userServiceClient.updateUserSetting({ const updatedUserSetting = await userServiceClient.updateUserSetting({
setting: userSetting, setting: userSetting,
...@@ -103,7 +128,10 @@ const userStore = (() => { ...@@ -103,7 +128,10 @@ const userStore = (() => {
return { return {
state, state,
getOrFetchUserByName, getOrFetchUserByName,
getUserByName,
fetchUsers,
updateUser, updateUser,
deleteUser,
updateUserSetting, updateUserSetting,
fetchShortcuts, fetchShortcuts,
fetchInboxes, fetchInboxes,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment