mirror of
https://github.com/usememos/memos.git
synced 2025-01-01 10:01:54 +08:00
feat: use react-router
This commit is contained in:
parent
4608894e56
commit
307483e499
12 changed files with 155 additions and 149 deletions
|
@ -1,32 +1,23 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import useI18n from "./hooks/useI18n";
|
||||
import { appRouterSwitch } from "./routers";
|
||||
import { globalService, locationService } from "./services";
|
||||
import { useAppSelector } from "./store";
|
||||
import router from "./router";
|
||||
import * as storage from "./helpers/storage";
|
||||
|
||||
function App() {
|
||||
const { setLocale } = useI18n();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const global = useAppSelector((state) => state.global);
|
||||
const pathname = useAppSelector((state) => state.location.pathname);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
locationService.updateStateWithLocation();
|
||||
window.onpopstate = () => {
|
||||
locationService.updateStateWithLocation();
|
||||
};
|
||||
globalService.initialState().then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.setting.locale) {
|
||||
globalService.setLocale(user.setting.locale);
|
||||
}
|
||||
}, [user?.setting.locale]);
|
||||
globalService.initialState();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLocale(global.locale);
|
||||
|
@ -35,7 +26,7 @@ function App() {
|
|||
});
|
||||
}, [global.locale]);
|
||||
|
||||
return <>{isLoading ? null : appRouterSwitch(pathname)}</>;
|
||||
return <RouterProvider router={router} />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { locationService, userService } from "../services";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||
|
@ -15,6 +16,7 @@ interface Props {
|
|||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
const { shownStatus, setShownStatus } = props;
|
||||
const { t } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const popupElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -48,8 +50,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
|||
userService
|
||||
.doSignOut()
|
||||
.then(() => {
|
||||
locationService.replaceHistory("/auth");
|
||||
window.location.reload();
|
||||
navigate("/auth");
|
||||
})
|
||||
.catch(() => {
|
||||
// do nth
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { locationService, userService } from "../services";
|
||||
import { Link } from "react-router-dom";
|
||||
import { userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Icon from "./Icon";
|
||||
import Only from "./common/OnlyWhen";
|
||||
|
@ -17,10 +18,6 @@ const Sidebar = () => {
|
|||
showSettingDialog();
|
||||
};
|
||||
|
||||
const handleExploreBtnClick = () => {
|
||||
locationService.pushHistory("/explore");
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="sidebar-wrapper">
|
||||
<div className="close-container">
|
||||
|
@ -35,9 +32,9 @@ const Sidebar = () => {
|
|||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||
</button>
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<button className="btn action-btn" onClick={() => handleExploreBtnClick()}>
|
||||
<Link to="/explore" className="btn action-btn">
|
||||
<span className="icon">🏂</span> {t("common.explore")}
|
||||
</button>
|
||||
</Link>
|
||||
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
||||
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
||||
</button>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { assign } from "lodash-es";
|
||||
import { assign, isNull, isUndefined } from "lodash-es";
|
||||
|
||||
export const isNullorUndefined = (value: any) => {
|
||||
return isNull(value) || isUndefined(value);
|
||||
};
|
||||
|
||||
export function getNowTimeStamp(): number {
|
||||
return Date.now();
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
> .action-button-container {
|
||||
> .btn {
|
||||
@apply text-gray-600 font-mono text-base py-1 border px-3 rounded-xl hover:opacity-80 hover:underline;
|
||||
@apply block text-gray-600 font-mono text-base py-1 border px-3 leading-8 rounded-xl hover:opacity-80 hover:underline;
|
||||
|
||||
> .icon {
|
||||
@apply text-lg;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { createRoot } from "react-dom/client";
|
||||
import { Provider } from "react-redux";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import I18nProvider from "./labs/i18n/I18nProvider";
|
||||
import store from "./store";
|
||||
import App from "./App";
|
||||
|
@ -11,11 +10,9 @@ import "./css/index.css";
|
|||
const container = document.getElementById("root");
|
||||
const root = createRoot(container as HTMLElement);
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<I18nProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</I18nProvider>
|
||||
</BrowserRouter>
|
||||
<I18nProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as api from "../helpers/api";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { globalService, locationService, userService } from "../services";
|
||||
import { globalService, userService } from "../services";
|
||||
import Icon from "../components/Icon";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
import toastHelper from "../components/Toast";
|
||||
|
@ -18,6 +19,7 @@ const validateConfig: ValidatorConfig = {
|
|||
|
||||
const Auth = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const pageLoadingState = useLoading(true);
|
||||
const [siteHost, setSiteHost] = useState<User>();
|
||||
const [email, setEmail] = useState("");
|
||||
|
@ -68,7 +70,7 @@ const Auth = () => {
|
|||
await api.signin(email, password);
|
||||
const user = await userService.doSignIn();
|
||||
if (user) {
|
||||
locationService.replaceHistory("/");
|
||||
navigate("/");
|
||||
} else {
|
||||
toastHelper.error(t("message.login-failed"));
|
||||
}
|
||||
|
@ -101,7 +103,7 @@ const Auth = () => {
|
|||
await api.signup(email, password, "HOST");
|
||||
const user = await userService.doSignIn();
|
||||
if (user) {
|
||||
locationService.replaceHistory("/");
|
||||
navigate("/");
|
||||
} else {
|
||||
toastHelper.error(t("common.singup-failed"));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { locationService, memoService, userService } from "../services";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { memoService, userService } from "../services";
|
||||
import { isNullorUndefined } from "../helpers/utils";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useQuery from "../hooks/useQuery";
|
||||
|
@ -16,6 +18,7 @@ interface State {
|
|||
|
||||
const Explore = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const location = useAppSelector((state) => state.location);
|
||||
|
@ -25,33 +28,28 @@ const Explore = () => {
|
|||
const loadingState = useLoading();
|
||||
|
||||
useEffect(() => {
|
||||
userService
|
||||
.initialState()
|
||||
.catch()
|
||||
.finally(async () => {
|
||||
const { host } = userService.getState();
|
||||
if (!host) {
|
||||
locationService.replaceHistory("/auth");
|
||||
return;
|
||||
}
|
||||
const { host } = userService.getState();
|
||||
if (isNullorUndefined(host)) {
|
||||
navigate("/auth");
|
||||
return;
|
||||
}
|
||||
|
||||
memoService.fetchAllMemos().then((memos) => {
|
||||
let filteredMemos = memos;
|
||||
memoService.fetchAllMemos().then((memos) => {
|
||||
let filteredMemos = memos;
|
||||
|
||||
const memoId = Number(query.get("memoId"));
|
||||
if (memoId && !isNaN(memoId)) {
|
||||
filteredMemos = filteredMemos.filter((memo) => {
|
||||
return memo.id === memoId;
|
||||
});
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
memos: filteredMemos,
|
||||
});
|
||||
const memoId = Number(query.get("memoId"));
|
||||
if (memoId && !isNaN(memoId)) {
|
||||
filteredMemos = filteredMemos.filter((memo) => {
|
||||
return memo.id === memoId;
|
||||
});
|
||||
loadingState.setFinish();
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
memos: filteredMemos,
|
||||
});
|
||||
loadingState.setFinish();
|
||||
});
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
|
@ -65,13 +63,13 @@ const Explore = () => {
|
|||
<div className="action-button-container">
|
||||
<Only when={!loadingState.isLoading}>
|
||||
{user ? (
|
||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||
<Link to="/" className="btn">
|
||||
<span className="icon">🏠</span> {t("common.back-to-home")}
|
||||
</button>
|
||||
</Link>
|
||||
) : (
|
||||
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||
<Link to="/auth" className="btn">
|
||||
<span className="icon">👉</span> {t("common.sign-in")}
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
</Only>
|
||||
</div>
|
||||
|
|
|
@ -1,77 +1,77 @@
|
|||
import { useEffect } from "react";
|
||||
import { locationService, userService } from "../services";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { globalService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { isNullorUndefined } from "../helpers/utils";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
import toastHelper from "../components/Toast";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import MemosHeader from "../components/MemosHeader";
|
||||
import MemoEditor from "../components/MemoEditor";
|
||||
import MemoFilter from "../components/MemoFilter";
|
||||
import MemoList from "../components/MemoList";
|
||||
import toastHelper from "../components/Toast";
|
||||
import "../less/home.less";
|
||||
|
||||
function Home() {
|
||||
const { t } = useI18n();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const location = useAppSelector((state) => state.location);
|
||||
const loadingState = useLoading();
|
||||
|
||||
useEffect(() => {
|
||||
userService
|
||||
.initialState()
|
||||
.catch()
|
||||
.finally(async () => {
|
||||
const { host, owner, user } = userService.getState();
|
||||
if (!host) {
|
||||
locationService.replaceHistory("/auth");
|
||||
return;
|
||||
}
|
||||
const { host, owner, user } = userService.getState();
|
||||
|
||||
if (userService.isVisitorMode()) {
|
||||
if (!owner) {
|
||||
toastHelper.error(t("message.user-not-found"));
|
||||
}
|
||||
} else {
|
||||
if (!user) {
|
||||
locationService.replaceHistory(`/explore`);
|
||||
}
|
||||
}
|
||||
loadingState.setFinish();
|
||||
});
|
||||
if (isNullorUndefined(host)) {
|
||||
navigate("/auth");
|
||||
return;
|
||||
}
|
||||
|
||||
if (userService.isVisitorMode()) {
|
||||
if (!owner) {
|
||||
toastHelper.error(t("message.user-not-found"));
|
||||
}
|
||||
} else {
|
||||
if (isNullorUndefined(user)) {
|
||||
navigate("/explore");
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.setting.locale) {
|
||||
globalService.setLocale(user.setting.locale);
|
||||
}
|
||||
}, [user?.setting.locale]);
|
||||
|
||||
return (
|
||||
<section className="page-wrapper home">
|
||||
{loadingState.isLoading ? null : (
|
||||
<div className="page-container">
|
||||
<Sidebar />
|
||||
<main className="memos-wrapper">
|
||||
<div className="memos-editor-wrapper">
|
||||
<MemosHeader />
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<MemoEditor />
|
||||
</Only>
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
<Only when={userService.isVisitorMode()}>
|
||||
<div className="addtion-btn-container">
|
||||
{user ? (
|
||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||
<span className="icon">🏠</span> {t("common.back-to-home")}
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||
<span className="icon">👉</span> {t("common.sign-in")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="page-container">
|
||||
<Sidebar />
|
||||
<main className="memos-wrapper">
|
||||
<div className="memos-editor-wrapper">
|
||||
<MemosHeader />
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<MemoEditor />
|
||||
</Only>
|
||||
</main>
|
||||
</div>
|
||||
)}
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
<Only when={userService.isVisitorMode()}>
|
||||
<div className="addtion-btn-container">
|
||||
{user ? (
|
||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||
<span className="icon">🏠</span> {t("common.back-to-home")}
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||
<span className="icon">👉</span> {t("common.sign-in")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Only>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
47
web/src/router/index.tsx
Normal file
47
web/src/router/index.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { createBrowserRouter } from "react-router-dom";
|
||||
import { userService } from "../services";
|
||||
import Auth from "../pages/Auth";
|
||||
import Explore from "../pages/Explore";
|
||||
import Home from "../pages/Home";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Home />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await userService.initialState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/auth",
|
||||
element: <Auth />,
|
||||
},
|
||||
{
|
||||
path: "/u/:userId",
|
||||
element: <Home />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await userService.initialState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/explore",
|
||||
element: <Explore />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await userService.initialState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export default router;
|
|
@ -1,11 +0,0 @@
|
|||
import Home from "../pages/Home";
|
||||
import Auth from "../pages/Auth";
|
||||
import Explore from "../pages/Explore";
|
||||
|
||||
const appRouter = {
|
||||
"/auth": <Auth />,
|
||||
"/explore": <Explore />,
|
||||
"*": <Home />,
|
||||
};
|
||||
|
||||
export default appRouter;
|
|
@ -1,20 +0,0 @@
|
|||
import appRouter from "./appRouter";
|
||||
|
||||
// just like React-Router
|
||||
interface Router {
|
||||
[key: string]: JSX.Element | null;
|
||||
"*": JSX.Element | null;
|
||||
}
|
||||
|
||||
const routerSwitch = (router: Router) => {
|
||||
return (pathname: string) => {
|
||||
for (const key of Object.keys(router)) {
|
||||
if (key === pathname) {
|
||||
return router[key];
|
||||
}
|
||||
}
|
||||
return router["*"];
|
||||
};
|
||||
};
|
||||
|
||||
export const appRouterSwitch = routerSwitch(appRouter);
|
Loading…
Reference in a new issue