mirror of
https://github.com/usememos/memos.git
synced 2025-10-29 07:48:14 +08:00
refactor: user store
This commit is contained in:
parent
11b9c240e9
commit
7a57b5c6e7
11 changed files with 52 additions and 166 deletions
|
|
@ -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<Map<string, User[]>>(new Map());
|
||||
const readonly = memo.state === State.ARCHIVED;
|
||||
|
||||
|
|
|
|||
|
|
@ -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: Props) => {
|
|||
const location = useLocation();
|
||||
const navigateTo = useNavigateTo();
|
||||
const currentUser = useCurrentUser();
|
||||
const userStore = useUserStore();
|
||||
const user = useCurrentUser();
|
||||
const memoStore = useMemoStore();
|
||||
const userStatsStore = useUserStatsStore();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<LocalState>({
|
||||
creatingUser: User.fromPartial({
|
||||
username: "",
|
||||
|
|
|
|||
|
|
@ -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<Props> = ({ destroy }: Props) => {
|
||||
const t = useTranslate();
|
||||
const currentUser = useCurrentUser();
|
||||
const userStore = useUserStore();
|
||||
const [state, setState] = useState<State>({
|
||||
avatarUrl: currentUser.avatarUrl,
|
||||
username: currentUser.username,
|
||||
|
|
|
|||
|
|
@ -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<State>({
|
||||
loading: true,
|
||||
errorMessage: "",
|
||||
|
|
@ -55,7 +54,7 @@ const AuthCallback = () => {
|
|||
loading: false,
|
||||
errorMessage: "",
|
||||
});
|
||||
await userStore.fetchCurrentUser();
|
||||
await initialUserStore();
|
||||
navigateTo("/");
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<User>();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
|
|
@ -32,7 +32,7 @@ const UserProfile = () => {
|
|||
}
|
||||
|
||||
userStore
|
||||
.fetchUserByUsername(username)
|
||||
.getOrFetchUserByName(username)
|
||||
.then((user) => {
|
||||
setUser(user);
|
||||
loadingState.setFinish();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export * from "./user";
|
||||
export * from "./memo";
|
||||
export * from "./resourceName";
|
||||
export * from "./resource";
|
||||
|
|
|
|||
|
|
@ -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<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 = (() => {
|
|||
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 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<UserSetting>, 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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue