From b6d1ded6680c890381e8e693fd9a370967c0c254 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 10 Sep 2023 23:44:06 +0800 Subject: [PATCH] chore: adjust initial states --- web/package.json | 1 + web/pnpm-lock.yaml | 7 + web/src/App.tsx | 41 ++++- web/src/main.tsx | 7 +- web/src/pages/Archived.tsx | 8 + web/src/pages/DailyReview.tsx | 8 + web/src/pages/Home.tsx | 29 ++-- web/src/pages/ResourcesDashboard.tsx | 8 + web/src/pages/Setting.tsx | 16 +- web/src/router/index.tsx | 237 ++++++--------------------- web/src/store/module/user.ts | 1 + web/src/store/v1/user.ts | 2 +- 12 files changed, 143 insertions(+), 222 deletions(-) diff --git a/web/package.json b/web/package.json index 04b40e77..2f427a8a 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ }, "packageManager": "pnpm@8.7.0", "dependencies": { + "@bufbuild/protobuf": "^1.3.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/joy": "5.0.0-beta.2", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b5e3a663..06052915 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@bufbuild/protobuf': + specifier: ^1.3.1 + version: 1.3.1 '@emotion/react': specifier: ^11.11.1 version: 11.11.1(@types/react@18.2.21)(react@18.2.0) @@ -407,6 +410,10 @@ packages: '@babel/helper-validator-identifier': 7.22.5 to-fast-properties: 2.0.0 + /@bufbuild/protobuf@1.3.1: + resolution: {integrity: sha512-BUyJWutgP2S8K/1NphOJokuwDckXS4qI2T1pGZAlkFdZchWae3jm6fCdkcGbLlM1QLOcNFFePd+7Feo4BYGrJQ==} + dev: false + /@emotion/babel-plugin@11.11.0: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: diff --git a/web/src/App.tsx b/web/src/App.tsx index 3debb429..c0b04efa 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,20 +1,44 @@ import { useColorScheme } from "@mui/joy"; -import { Suspense, useEffect } from "react"; -import { Toaster } from "react-hot-toast"; +import { Suspense, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { RouterProvider } from "react-router-dom"; +import { Outlet } from "react-router-dom"; import storage from "./helpers/storage"; import { getSystemColorScheme } from "./helpers/utils"; import Loading from "./pages/Loading"; -import router from "./router"; -import { useGlobalStore } from "./store/module"; +import { initialGlobalState, initialUserState, useGlobalStore } from "./store/module"; +import { useUserV1Store } from "./store/v1"; const App = () => { const { i18n } = useTranslation(); const globalStore = useGlobalStore(); const { mode, setMode } = useColorScheme(); + const userV1Store = useUserV1Store(); + const [loading, setLoading] = useState(true); const { appearance, locale, systemStatus } = globalStore.state; + useEffect(() => { + const initialState = async () => { + try { + await initialGlobalState(); + } catch (error) { + // do nothing + } + + try { + const user = await initialUserState(); + if (user) { + await userV1Store.getOrFetchUserByUsername(user.username); + } + } catch (error) { + // do nothing. + } + + setLoading(false); + }; + + initialState(); + }, []); + useEffect(() => { const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleColorSchemeChange = (e: MediaQueryListEvent) => { @@ -85,10 +109,11 @@ const App = () => { } }, [mode]); - return ( + return loading ? ( + + ) : ( }> - - + ); }; diff --git a/web/src/main.tsx b/web/src/main.tsx index 375bea98..782ad070 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,12 +1,14 @@ import { CssVarsProvider } from "@mui/joy"; import { createRoot } from "react-dom/client"; +import { Toaster } from "react-hot-toast"; import { Provider } from "react-redux"; -import App from "./App"; +import { RouterProvider } from "react-router-dom"; import "./css/global.css"; import "./css/tailwind.css"; import "./helpers/polyfill"; import "./i18n"; import "./less/code-highlight.less"; +import router from "./router"; import store from "./store"; import theme from "./theme"; @@ -15,7 +17,8 @@ const root = createRoot(container as HTMLElement); root.render( - + + ); diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index 6c5548c5..2e7c358f 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -4,6 +4,7 @@ import ArchivedMemo from "@/components/ArchivedMemo"; import Empty from "@/components/Empty"; import MemoFilter from "@/components/MemoFilter"; import MobileHeader from "@/components/MobileHeader"; +import useCurrentUser from "@/hooks/useCurrentUser"; import useLoading from "@/hooks/useLoading"; import { useFilterStore, useMemoStore } from "@/store/module"; import { useTranslate } from "@/utils/i18n"; @@ -11,6 +12,7 @@ import "@/less/archived.less"; const Archived = () => { const t = useTranslate(); + const user = useCurrentUser(); const memoStore = useMemoStore(); const loadingState = useLoading(); const [archivedMemos, setArchivedMemos] = useState([]); @@ -19,6 +21,12 @@ const Archived = () => { const filter = filterStore.state; const { text: textQuery } = filter; + useEffect(() => { + if (!user) { + window.location.href = "/auth"; + } + }, []); + useEffect(() => { memoStore .fetchArchivedMemos() diff --git a/web/src/pages/DailyReview.tsx b/web/src/pages/DailyReview.tsx index 6ade07a9..17c0d46f 100644 --- a/web/src/pages/DailyReview.tsx +++ b/web/src/pages/DailyReview.tsx @@ -11,6 +11,7 @@ import showPreviewImageDialog from "@/components/PreviewImageDialog"; import DatePicker from "@/components/kit/DatePicker"; import { DAILY_TIMESTAMP, DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import { convertToMillis, getDateStampByDate, getNormalizedDateString, getTimeStampByDate, isFutureDate } from "@/helpers/datetime"; +import useCurrentUser from "@/hooks/useCurrentUser"; import i18n from "@/i18n"; import toImage from "@/labs/html2image"; import { useMemoStore, useUserStore } from "@/store/module"; @@ -20,6 +21,7 @@ const DailyReview = () => { const t = useTranslate(); const memoStore = useMemoStore(); const userStore = useUserStore(); + const user = useCurrentUser(); const { localSetting } = userStore.state.user as User; const [currentDateStamp, setCurrentDateStamp] = useState(getDateStampByDate(getNormalizedDateString())); const [showDatePicker, toggleShowDatePicker] = useToggle(false); @@ -37,6 +39,12 @@ const DailyReview = () => { }) .sort((a, b) => getTimeStampByDate(a.displayTs) - getTimeStampByDate(b.displayTs)); + useEffect(() => { + if (!user) { + window.location.href = "/auth"; + } + }, []); + useEffect(() => { let offset = 0; const fetchMoreMemos = async () => { diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index d6fe8815..8bf3b73a 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -1,34 +1,29 @@ import { useEffect } from "react"; -import { toast } from "react-hot-toast"; import HomeSidebar from "@/components/HomeSidebar"; import MemoEditor from "@/components/MemoEditor"; import MemoFilter from "@/components/MemoFilter"; import MemoList from "@/components/MemoList"; import MobileHeader from "@/components/MobileHeader"; +import useCurrentUser from "@/hooks/useCurrentUser"; 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; + const user = useCurrentUser(); useEffect(() => { - const currentUsername = userStore.getCurrentUsername(); - userV1Store.getOrFetchUserByUsername(currentUsername).catch((error) => { - console.error(error); - toast.error(t("message.user-not-found")); - }); - }, [userStore.getCurrentUsername()]); - - useEffect(() => { - if (user?.setting.locale) { - globalStore.setLocale(user.setting.locale); + if (user) { + return; } - }, [user?.setting.locale]); + + const systemStatus = globalStore.state.systemStatus; + if (systemStatus.disablePublicMemos) { + window.location.href = "/auth"; + } else { + window.location.href = "/explore"; + } + }, []); return (
diff --git a/web/src/pages/ResourcesDashboard.tsx b/web/src/pages/ResourcesDashboard.tsx index 1b2785d9..284d194b 100644 --- a/web/src/pages/ResourcesDashboard.tsx +++ b/web/src/pages/ResourcesDashboard.tsx @@ -11,6 +11,7 @@ import ResourceItem from "@/components/ResourceItem"; import ResourceSearchBar from "@/components/ResourceSearchBar"; import Dropdown from "@/components/kit/Dropdown"; import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; +import useCurrentUser from "@/hooks/useCurrentUser"; import useEvent from "@/hooks/useEvent"; import useLoading from "@/hooks/useLoading"; import { useResourceStore } from "@/store/module"; @@ -19,6 +20,7 @@ import { useTranslate } from "@/utils/i18n"; const ResourcesDashboard = () => { const t = useTranslate(); const loadingState = useLoading(); + const user = useCurrentUser(); const resourceStore = useResourceStore(); const resources = resourceStore.state.resources; const [selectedList, setSelectedList] = useState>([]); @@ -27,6 +29,12 @@ const ResourcesDashboard = () => { const [dragActive, setDragActive] = useState(false); const [isComplete, setIsComplete] = useState(false); + useEffect(() => { + if (!user) { + window.location.href = "/auth"; + } + }, []); + useEffect(() => { resourceStore .fetchResourceListWithLimit(DEFAULT_MEMO_LIMIT) diff --git a/web/src/pages/Setting.tsx b/web/src/pages/Setting.tsx index cc8141a8..0866d633 100644 --- a/web/src/pages/Setting.tsx +++ b/web/src/pages/Setting.tsx @@ -1,5 +1,6 @@ import { Option, Select } from "@mui/joy"; -import { useState } from "react"; +import { isEqual } from "lodash-es"; +import { useEffect, useState } from "react"; import BetaBadge from "@/components/BetaBadge"; import Icon from "@/components/Icon"; import MobileHeader from "@/components/MobileHeader"; @@ -9,7 +10,7 @@ import PreferencesSection from "@/components/Settings/PreferencesSection"; import SSOSection from "@/components/Settings/SSOSection"; import StorageSection from "@/components/Settings/StorageSection"; import SystemSection from "@/components/Settings/SystemSection"; -import { useUserStore } from "@/store/module"; +import useCurrentUser from "@/hooks/useCurrentUser"; import { useTranslate } from "@/utils/i18n"; import "@/less/setting.less"; @@ -21,12 +22,17 @@ interface State { const Setting = () => { const t = useTranslate(); - const userStore = useUserStore(); - const user = userStore.state.user; + const user = useCurrentUser(); const [state, setState] = useState({ selectedSection: "my-account", }); - const isHost = user?.role === "HOST"; + const isHost = isEqual(user.role, "HOST"); + + useEffect(() => { + if (!user) { + window.location.href = "/auth"; + } + }, []); const handleSectionSelectorItemClick = (settingSection: SettingSection) => { setState({ diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index 48674e00..0f3abe37 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -1,12 +1,11 @@ import { lazy } from "react"; -import { createBrowserRouter, redirect } from "react-router-dom"; -import { isNullorUndefined } from "@/helpers/utils"; +import { createBrowserRouter } from "react-router-dom"; +import App from "@/App"; import Archived from "@/pages/Archived"; import DailyReview from "@/pages/DailyReview"; import ResourcesDashboard from "@/pages/ResourcesDashboard"; import Setting from "@/pages/Setting"; -import store from "@/store"; -import { initialGlobalState, initialUserState } from "@/store/module"; +import { initialGlobalState } from "@/store/module"; const Root = lazy(() => import("@/layouts/Root")); const Auth = lazy(() => import("@/pages/Auth")); @@ -35,210 +34,70 @@ const initialGlobalStateLoader = (() => { })(); const router = createBrowserRouter([ - { - path: "/auth", - element: , - loader: async () => { - await initialGlobalStateLoader(); - return null; - }, - }, - { - path: "/auth/callback", - element: , - }, { path: "/", - element: , + element: , children: [ { - path: "", - element: , + path: "/auth", + element: , loader: async () => { await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - const { systemStatus } = store.getState().global; - - // if user is authenticated, then show home - if (!isNullorUndefined(user)) { - return null; - } - - // if user is anonymous, then redirect to auth if disabled public memos, else redirect to explore - if (systemStatus.disablePublicMemos) { - return redirect("/auth"); - } - - return redirect("/explore"); - }, - }, - { - path: "explore", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - const { systemStatus } = store.getState().global; - - if (isNullorUndefined(user) && systemStatus.disablePublicMemos) { - return redirect("/auth"); - } return null; }, }, { - path: "review", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - - if (isNullorUndefined(user)) { - return redirect("/auth"); - } - return null; - }, + path: "/auth/callback", + element: , }, { - path: "resources", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - - if (isNullorUndefined(user)) { - return redirect("/auth"); - } - return null; - }, + path: "/", + element: , + children: [ + { + path: "", + element: , + }, + { + path: "explore", + element: , + }, + { + path: "review", + element: , + }, + { + path: "resources", + element: , + }, + { + path: "archived", + element: , + }, + { + path: "setting", + element: , + }, + ], }, { - path: "archived", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - - if (isNullorUndefined(user)) { - return redirect("/auth"); - } - return null; - }, + path: "/m/:memoId", + element: , }, { - path: "setting", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - - if (isNullorUndefined(user)) { - return redirect("/auth"); - } - return null; - }, + path: "/m/:memoId/embed", + element: , + }, + { + path: "/u/:username", + element: , + }, + { + path: "*", + element: , }, ], }, - { - path: "/m/:memoId", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - - const { user } = store.getState().user; - const { systemStatus } = store.getState().global; - - if (isNullorUndefined(user) && systemStatus.disablePublicMemos) { - return redirect("/auth"); - } - return null; - }, - }, - { - path: "/m/:memoId/embed", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - return null; - }, - }, - { - path: "/u/:username", - element: , - loader: async () => { - await initialGlobalStateLoader(); - - try { - await initialUserState(); - } catch (error) { - // do nth - } - return null; - }, - }, - { - path: "*", - element: , - loader: async () => { - await initialGlobalStateLoader(); - return null; - }, - }, ]); export default router; diff --git a/web/src/store/module/user.ts b/web/src/store/module/user.ts index 5662cbdb..471e8d0d 100644 --- a/web/src/store/module/user.ts +++ b/web/src/store/module/user.ts @@ -68,6 +68,7 @@ export const initialUserState = async () => { if (user.setting.appearance) { store.dispatch(setAppearance(user.setting.appearance)); } + return user; } }; diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts index 58fad286..87cc4548 100644 --- a/web/src/store/v1/user.ts +++ b/web/src/store/v1/user.ts @@ -39,7 +39,7 @@ const useUserV1Store = create()((set, get) => ({ }, getUserByUsername: (username: string) => { const userMap = get().userMapByUsername; - return userMap[username] as User; + return userMap[username]; }, updateUser: async (user: Partial, updateMask: string[]) => { const {