mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: use react-router
This commit is contained in:
@ -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 useI18n from "./hooks/useI18n";
|
||||||
import { appRouterSwitch } from "./routers";
|
|
||||||
import { globalService, locationService } from "./services";
|
import { globalService, locationService } from "./services";
|
||||||
import { useAppSelector } from "./store";
|
import { useAppSelector } from "./store";
|
||||||
|
import router from "./router";
|
||||||
import * as storage from "./helpers/storage";
|
import * as storage from "./helpers/storage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { setLocale } = useI18n();
|
const { setLocale } = useI18n();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
|
||||||
const global = useAppSelector((state) => state.global);
|
const global = useAppSelector((state) => state.global);
|
||||||
const pathname = useAppSelector((state) => state.location.pathname);
|
|
||||||
const [isLoading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
locationService.updateStateWithLocation();
|
locationService.updateStateWithLocation();
|
||||||
window.onpopstate = () => {
|
window.onpopstate = () => {
|
||||||
locationService.updateStateWithLocation();
|
locationService.updateStateWithLocation();
|
||||||
};
|
};
|
||||||
globalService.initialState().then(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
globalService.initialState();
|
||||||
if (user?.setting.locale) {
|
}, []);
|
||||||
globalService.setLocale(user.setting.locale);
|
|
||||||
}
|
|
||||||
}, [user?.setting.locale]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocale(global.locale);
|
setLocale(global.locale);
|
||||||
@ -35,7 +26,7 @@ function App() {
|
|||||||
});
|
});
|
||||||
}, [global.locale]);
|
}, [global.locale]);
|
||||||
|
|
||||||
return <>{isLoading ? null : appRouterSwitch(pathname)}</>;
|
return <RouterProvider router={router} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef } from "react";
|
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 useI18n from "../hooks/useI18n";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||||
@ -15,6 +16,7 @@ interface Props {
|
|||||||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||||
const { shownStatus, setShownStatus } = props;
|
const { shownStatus, setShownStatus } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const navigate = useNavigate();
|
||||||
const popupElRef = useRef<HTMLDivElement>(null);
|
const popupElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -48,8 +50,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
|||||||
userService
|
userService
|
||||||
.doSignOut()
|
.doSignOut()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
locationService.replaceHistory("/auth");
|
navigate("/auth");
|
||||||
window.location.reload();
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// do nth
|
// 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 useI18n from "../hooks/useI18n";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
@ -17,10 +18,6 @@ const Sidebar = () => {
|
|||||||
showSettingDialog();
|
showSettingDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExploreBtnClick = () => {
|
|
||||||
locationService.pushHistory("/explore");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar-wrapper">
|
<aside className="sidebar-wrapper">
|
||||||
<div className="close-container">
|
<div className="close-container">
|
||||||
@ -35,9 +32,9 @@ const Sidebar = () => {
|
|||||||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||||
</button>
|
</button>
|
||||||
<Only when={!userService.isVisitorMode()}>
|
<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")}
|
<span className="icon">🏂</span> {t("common.explore")}
|
||||||
</button>
|
</Link>
|
||||||
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
||||||
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
||||||
</button>
|
</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 {
|
export function getNowTimeStamp(): number {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
> .action-button-container {
|
> .action-button-container {
|
||||||
> .btn {
|
> .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 {
|
> .icon {
|
||||||
@apply text-lg;
|
@apply text-lg;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
import I18nProvider from "./labs/i18n/I18nProvider";
|
import I18nProvider from "./labs/i18n/I18nProvider";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
@ -11,11 +10,9 @@ import "./css/index.css";
|
|||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
const root = createRoot(container as HTMLElement);
|
const root = createRoot(container as HTMLElement);
|
||||||
root.render(
|
root.render(
|
||||||
<BrowserRouter>
|
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</BrowserRouter>
|
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import useI18n from "../hooks/useI18n";
|
import useI18n from "../hooks/useI18n";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { globalService, locationService, userService } from "../services";
|
import { globalService, userService } from "../services";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import Only from "../components/common/OnlyWhen";
|
import Only from "../components/common/OnlyWhen";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
@ -18,6 +19,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
const navigate = useNavigate();
|
||||||
const pageLoadingState = useLoading(true);
|
const pageLoadingState = useLoading(true);
|
||||||
const [siteHost, setSiteHost] = useState<User>();
|
const [siteHost, setSiteHost] = useState<User>();
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@ -68,7 +70,7 @@ const Auth = () => {
|
|||||||
await api.signin(email, password);
|
await api.signin(email, password);
|
||||||
const user = await userService.doSignIn();
|
const user = await userService.doSignIn();
|
||||||
if (user) {
|
if (user) {
|
||||||
locationService.replaceHistory("/");
|
navigate("/");
|
||||||
} else {
|
} else {
|
||||||
toastHelper.error(t("message.login-failed"));
|
toastHelper.error(t("message.login-failed"));
|
||||||
}
|
}
|
||||||
@ -101,7 +103,7 @@ const Auth = () => {
|
|||||||
await api.signup(email, password, "HOST");
|
await api.signup(email, password, "HOST");
|
||||||
const user = await userService.doSignIn();
|
const user = await userService.doSignIn();
|
||||||
if (user) {
|
if (user) {
|
||||||
locationService.replaceHistory("/");
|
navigate("/");
|
||||||
} else {
|
} else {
|
||||||
toastHelper.error(t("common.singup-failed"));
|
toastHelper.error(t("common.singup-failed"));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useEffect, useState } from "react";
|
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 { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
import useI18n from "../hooks/useI18n";
|
||||||
import useQuery from "../hooks/useQuery";
|
import useQuery from "../hooks/useQuery";
|
||||||
@ -16,6 +18,7 @@ interface State {
|
|||||||
|
|
||||||
const Explore = () => {
|
const Explore = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
const navigate = useNavigate();
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const location = useAppSelector((state) => state.location);
|
const location = useAppSelector((state) => state.location);
|
||||||
@ -25,13 +28,9 @@ const Explore = () => {
|
|||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
userService
|
|
||||||
.initialState()
|
|
||||||
.catch()
|
|
||||||
.finally(async () => {
|
|
||||||
const { host } = userService.getState();
|
const { host } = userService.getState();
|
||||||
if (!host) {
|
if (isNullorUndefined(host)) {
|
||||||
locationService.replaceHistory("/auth");
|
navigate("/auth");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +48,6 @@ const Explore = () => {
|
|||||||
...state,
|
...state,
|
||||||
memos: filteredMemos,
|
memos: filteredMemos,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
loadingState.setFinish();
|
loadingState.setFinish();
|
||||||
});
|
});
|
||||||
}, [location]);
|
}, [location]);
|
||||||
@ -65,13 +63,13 @@ const Explore = () => {
|
|||||||
<div className="action-button-container">
|
<div className="action-button-container">
|
||||||
<Only when={!loadingState.isLoading}>
|
<Only when={!loadingState.isLoading}>
|
||||||
{user ? (
|
{user ? (
|
||||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
<Link to="/" className="btn">
|
||||||
<span className="icon">🏠</span> {t("common.back-to-home")}
|
<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")}
|
<span className="icon">👉</span> {t("common.sign-in")}
|
||||||
</button>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,31 +1,29 @@
|
|||||||
import { useEffect } from "react";
|
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 { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
import useI18n from "../hooks/useI18n";
|
||||||
import useLoading from "../hooks/useLoading";
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import Only from "../components/common/OnlyWhen";
|
import Only from "../components/common/OnlyWhen";
|
||||||
|
import toastHelper from "../components/Toast";
|
||||||
import Sidebar from "../components/Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
import MemosHeader from "../components/MemosHeader";
|
import MemosHeader from "../components/MemosHeader";
|
||||||
import MemoEditor from "../components/MemoEditor";
|
import MemoEditor from "../components/MemoEditor";
|
||||||
import MemoFilter from "../components/MemoFilter";
|
import MemoFilter from "../components/MemoFilter";
|
||||||
import MemoList from "../components/MemoList";
|
import MemoList from "../components/MemoList";
|
||||||
import toastHelper from "../components/Toast";
|
|
||||||
import "../less/home.less";
|
import "../less/home.less";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const location = useAppSelector((state) => state.location);
|
|
||||||
const loadingState = useLoading();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
userService
|
|
||||||
.initialState()
|
|
||||||
.catch()
|
|
||||||
.finally(async () => {
|
|
||||||
const { host, owner, user } = userService.getState();
|
const { host, owner, user } = userService.getState();
|
||||||
if (!host) {
|
|
||||||
locationService.replaceHistory("/auth");
|
if (isNullorUndefined(host)) {
|
||||||
|
navigate("/auth");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,17 +32,20 @@ function Home() {
|
|||||||
toastHelper.error(t("message.user-not-found"));
|
toastHelper.error(t("message.user-not-found"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!user) {
|
if (isNullorUndefined(user)) {
|
||||||
locationService.replaceHistory(`/explore`);
|
navigate("/explore");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadingState.setFinish();
|
|
||||||
});
|
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user?.setting.locale) {
|
||||||
|
globalService.setLocale(user.setting.locale);
|
||||||
|
}
|
||||||
|
}, [user?.setting.locale]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="page-wrapper home">
|
<section className="page-wrapper home">
|
||||||
{loadingState.isLoading ? null : (
|
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<main className="memos-wrapper">
|
<main className="memos-wrapper">
|
||||||
@ -71,7 +72,6 @@ function Home() {
|
|||||||
</Only>
|
</Only>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</section>
|
</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);
|
|
Reference in New Issue
Block a user