chore: clean duplicated requests

This commit is contained in:
Steven 2023-09-10 11:43:38 +08:00
parent ca336af4fa
commit 866937787c
16 changed files with 56 additions and 69 deletions

View File

@ -5,8 +5,8 @@ import Icon from "./Icon";
interface Props {
value: Locale;
onChange: (locale: Locale) => void;
className?: string;
onChange: (locale: Locale) => void;
}
const LocaleSelect: FC<Props> = (props: Props) => {

View File

@ -229,7 +229,7 @@ const Memo: React.FC<Props> = (props: Props) => {
<>
<Link className="flex flex-row justify-start items-center" to={`/u/${memo.creatorUsername}`}>
<UserAvatar className="!w-5 !h-auto mr-1" avatarUrl={creator.avatarUrl} />
<span className="text-sm text-gray-600 max-w-[8em] truncate dark:text-zinc-300">{creator.nickname}</span>
<span className="text-sm text-gray-600 max-w-[8em] truncate dark:text-gray-400">{creator.nickname}</span>
</Link>
<Icon.Dot className="w-4 h-auto text-gray-400 dark:text-zinc-400" />
</>

View File

@ -15,7 +15,8 @@ const MemoList: React.FC = () => {
const userStore = useUserStore();
const filterStore = useFilterStore();
const filter = filterStore.state;
const { memos, isFetching } = memoStore.state;
const { memos } = memoStore.state;
const [isFetching, setIsFetching] = useState<boolean>(true);
const [isComplete, setIsComplete] = useState<boolean>(false);
const currentUsername = userStore.getCurrentUsername();
@ -82,6 +83,7 @@ const MemoList: React.FC = () => {
} else {
setIsComplete(false);
}
setIsFetching(false);
})
.catch((error) => {
console.error(error);
@ -122,12 +124,14 @@ const MemoList: React.FC = () => {
const handleFetchMoreClick = async () => {
try {
setIsFetching(true);
const fetchedMemos = await memoStore.fetchMemos(DEFAULT_MEMO_LIMIT, memos.length);
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
setIsComplete(true);
} else {
setIsComplete(false);
}
setIsFetching(false);
} catch (error: any) {
console.error(error);
toast.error(error.response.data.message);

View File

@ -136,17 +136,17 @@ const PreferencesSection = () => {
<div className="inline-block min-w-full align-middle">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" className="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
<tr className="text-sm font-semibold text-left text-gray-900 dark:text-gray-300">
<th scope="col" className="py-2 pl-4 pr-3">
ID
</th>
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
<th scope="col" className="px-3 py-2">
{t("common.username")}
</th>
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
<th scope="col" className="px-3 py-2">
{t("common.nickname")}
</th>
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
<th scope="col" className="px-3 py-2">
{t("common.email")}
</th>
<th scope="col" className="relative py-2 pl-3 pr-4"></th>
@ -155,13 +155,13 @@ const PreferencesSection = () => {
<tbody className="divide-y divide-gray-200">
{userList.map((user) => (
<tr key={user.id}>
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900">{user.id}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-300">{user.id}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">
{user.username}
<span className="ml-1 italic">{user.rowStatus === "ARCHIVED" && "(Archived)"}</span>
</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.nickname}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.email}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.nickname}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.email}</td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end">
{currentUser?.id === user.id ? (
<span>{t("common.yourself")}</span>

View File

@ -3,6 +3,7 @@ import { getMemoStats } from "@/helpers/api";
import { DAILY_TIMESTAMP } from "@/helpers/consts";
import { getDateStampByDate, getDateString, getTimeStampByDate } from "@/helpers/datetime";
import * as utils from "@/helpers/utils";
import { useUserV1Store } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
import { useFilterStore, useMemoStore, useUserStore } from "../store/module";
import "@/less/usage-heat-map.less";
@ -32,6 +33,7 @@ const UsageHeatMap = () => {
const t = useTranslate();
const filterStore = useFilterStore();
const userStore = useUserStore();
const userV1Store = useUserV1Store();
const memoStore = useMemoStore();
const todayTimeStamp = getDateStampByDate(Date.now());
const todayDay = new Date(todayTimeStamp).getDay() + 1;
@ -47,7 +49,7 @@ const UsageHeatMap = () => {
const currentUsername = userStore.getCurrentUsername();
useEffect(() => {
userStore.getUserByUsername(currentUsername).then((user) => {
userV1Store.getOrFetchUserByUsername(currentUsername).then((user) => {
if (!user) {
return;
}
@ -56,6 +58,10 @@ const UsageHeatMap = () => {
}, [currentUsername]);
useEffect(() => {
if (memos.length === 0) {
return;
}
getMemoStats(currentUsername)
.then(({ data }) => {
setMemoAmount(data.length);

View File

@ -9,7 +9,11 @@ const UserAvatar = (props: Props) => {
const { avatarUrl, className } = props;
return (
<div className={classNames(`w-8 h-auto overflow-clip rounded-full`, className)}>
<img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.webp"} alt="" />
<img
className="w-full h-auto rounded-full min-w-full min-h-full object-cover dark:opacity-80"
src={avatarUrl || "/logo.webp"}
alt=""
/>
</div>
);
};

View File

@ -1,9 +1,6 @@
html,
body {
@apply text-base w-full h-full overflow-hidden dark:bg-zinc-800;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Noto Sans",
"Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji", sans-serif;
}
#root {

View File

@ -143,7 +143,7 @@ const Auth = () => {
className="w-full"
size="lg"
type="text"
disabled={actionBtnLoadingState.isLoading}
readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.username")}
value={username}
onChange={handleUsernameInputChanged}
@ -153,7 +153,7 @@ const Auth = () => {
className="w-full"
size="lg"
type="password"
disabled={actionBtnLoadingState.isLoading}
readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.password")}
value={password}
onChange={handlePasswordInputChanged}

View File

@ -6,17 +6,19 @@ import MemoFilter from "@/components/MemoFilter";
import MemoList from "@/components/MemoList";
import MobileHeader from "@/components/MobileHeader";
import { useGlobalStore, useUserStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
const Home = () => {
const t = useTranslate();
const globalStore = useGlobalStore();
const userStore = useUserStore();
const userV1Store = useUserV1Store();
const user = userStore.state.user;
useEffect(() => {
const currentUsername = userStore.getCurrentUsername();
userStore.getUserByUsername(currentUsername).catch((error) => {
userV1Store.getOrFetchUserByUsername(currentUsername).catch((error) => {
console.error(error);
toast.error(t("message.user-not-found"));
});

View File

@ -5,12 +5,13 @@ import FloatingNavButton from "@/components/FloatingNavButton";
import Memo from "@/components/Memo";
import UserAvatar from "@/components/UserAvatar";
import useLoading from "@/hooks/useLoading";
import { useMemoStore, useUserStore } from "@/store/module";
import { useMemoStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
const MemoDetail = () => {
const params = useParams();
const memoStore = useMemoStore();
const userStore = useUserStore();
const userV1Store = useUserV1Store();
const loadingState = useLoading();
const [user, setUser] = useState<User>();
const memoId = Number(params.memoId);
@ -21,7 +22,7 @@ const MemoDetail = () => {
memoStore
.fetchMemoById(memoId)
.then(async (memo) => {
const user = await userStore.getUserByUsername(memo.creatorUsername);
const user = await userV1Store.getOrFetchUserByUsername(memo.creatorUsername);
setUser(user);
loadingState.setFinish();
})

View File

@ -6,18 +6,20 @@ import MemoList from "@/components/MemoList";
import UserAvatar from "@/components/UserAvatar";
import useLoading from "@/hooks/useLoading";
import { useUserStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
const UserProfile = () => {
const t = useTranslate();
const userStore = useUserStore();
const userV1Store = useUserV1Store();
const loadingState = useLoading();
const [user, setUser] = useState<User>();
useEffect(() => {
const currentUsername = userStore.getCurrentUsername();
userStore
.getUserByUsername(currentUsername)
userV1Store
.getOrFetchUserByUsername(currentUsername)
.then((user) => {
setUser(user);
loadingState.setFinish();

View File

@ -2,7 +2,7 @@ import { omit } from "lodash-es";
import * as api from "@/helpers/api";
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import store, { useAppSelector } from "../";
import { createMemo, deleteMemo, patchMemo, setIsFetching, upsertMemos } from "../reducer/memo";
import { createMemo, deleteMemo, patchMemo, upsertMemos } from "../reducer/memo";
import { useMemoCacheStore } from "../v1";
import { useUserStore } from "./";
@ -34,7 +34,6 @@ export const useMemoStore = () => {
return store.getState().memo;
},
fetchMemos: async (limit = DEFAULT_MEMO_LIMIT, offset = 0) => {
store.dispatch(setIsFetching(true));
const memoFind: MemoFind = {
rowStatus: "NORMAL",
limit,
@ -46,26 +45,20 @@ export const useMemoStore = () => {
const { data } = await api.getMemoList(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
store.dispatch(upsertMemos(fetchedMemos));
store.dispatch(setIsFetching(false));
for (const m of fetchedMemos) {
memoCacheStore.setMemoCache(m);
}
return fetchedMemos;
},
fetchAllMemos: async (limit = DEFAULT_MEMO_LIMIT, offset?: number) => {
store.dispatch(setIsFetching(true));
const memoFind: MemoFind = {
rowStatus: "NORMAL",
limit,
offset,
};
const { data } = await api.getAllMemos(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
store.dispatch(upsertMemos(fetchedMemos));
store.dispatch(setIsFetching(false));
for (const m of fetchedMemos) {
memoCacheStore.setMemoCache(m);

View File

@ -5,7 +5,7 @@ import storage from "@/helpers/storage";
import { getSystemColorScheme } from "@/helpers/utils";
import store, { useAppSelector } from "..";
import { setAppearance, setLocale } from "../reducer/global";
import { patchUser, setHost, setUser, setUserById } from "../reducer/user";
import { patchUser, setHost, setUser } from "../reducer/user";
const defaultSetting: Setting = {
locale: "en",
@ -118,16 +118,6 @@ export const useUserStore = () => {
return state.user?.username || UNKNOWN_USERNAME;
}
},
getUserByUsername: async (username: string) => {
const { data } = await api.getUserByUsername(username);
if (data) {
const user = convertResponseModelUser(data);
store.dispatch(setUserById(user));
return user;
} else {
return undefined;
}
},
upsertUserSetting: async (key: string, value: any) => {
await api.upsertUserSetting({
key: key as any,

View File

@ -3,15 +3,12 @@ import { uniqBy } from "lodash-es";
interface State {
memos: Memo[];
isFetching: boolean;
}
const memoSlice = createSlice({
name: "memo",
initialState: {
memos: [],
// isFetching flag should starts with true.
isFetching: true,
} as State,
reducers: {
upsertMemos: (state, action: PayloadAction<Memo[]>) => {
@ -51,15 +48,9 @@ const memoSlice = createSlice({
}),
};
},
setIsFetching: (state, action: PayloadAction<boolean>) => {
return {
...state,
isFetching: action.payload,
};
},
},
});
export const { upsertMemos, createMemo, patchMemo, deleteMemo, setIsFetching } = memoSlice.actions;
export const { upsertMemos, createMemo, patchMemo, deleteMemo } = memoSlice.actions;
export default memoSlice.reducer;

View File

@ -1,19 +1,15 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { cloneDeep } from "lodash-es";
interface State {
// host is the user who hist the system
host?: User;
// user is the user who is currently logged in
user?: User;
userById: { [key: UserId]: User };
}
const userSlice = createSlice({
name: "user",
initialState: {
userById: {},
} as State,
initialState: {} as State,
reducers: {
setHost: (state, action: PayloadAction<User | undefined>) => {
return {
@ -27,14 +23,6 @@ const userSlice = createSlice({
user: action.payload,
};
},
setUserById: (state, action: PayloadAction<User>) => {
const userById = cloneDeep(state.userById);
userById[action.payload.id] = action.payload;
return {
...state,
userById: userById,
};
},
patchUser: (state, action: PayloadAction<Partial<User>>) => {
return {
...state,
@ -47,6 +35,6 @@ const userSlice = createSlice({
},
});
export const { setHost, setUser, setUserById, patchUser } = userSlice.actions;
export const { setHost, setUser, patchUser } = userSlice.actions;
export default userSlice.reducer;

View File

@ -8,6 +8,9 @@ interface UserV1Store {
getUserByUsername: (username: string) => User;
}
// Request cache is used to prevent multiple requests.
const requestCache = new Map<string, Promise<any>>();
const useUserV1Store = create<UserV1Store>()((set, get) => ({
userMapByUsername: {},
getOrFetchUserByUsername: async (username: string) => {
@ -15,8 +18,14 @@ const useUserV1Store = create<UserV1Store>()((set, get) => ({
if (userMap[username]) {
return userMap[username] as User;
}
if (requestCache.has(username)) {
return await requestCache.get(username);
}
const { data } = await api.getUserByUsername(username);
const promise = api.getUserByUsername(username);
requestCache.set(username, promise);
const { data } = await promise;
requestCache.delete(username);
const user = convertResponseModelUser(data);
userMap[username] = user;
set(userMap);