chore: clean duplicated requests

pull/2209/head
Steven 2 years ago
parent ca336af4fa
commit 866937787c

@ -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) => {

@ -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" />
</>

@ -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);

@ -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>

@ -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);

@ -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>
);
};

@ -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 {

@ -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}

@ -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"));
});

@ -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();
})

@ -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();

@ -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);

@ -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,

@ -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;

@ -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;

@ -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);

Loading…
Cancel
Save