feat: use `react-router`

pull/233/head
Steven 3 years ago
parent 4608894e56
commit 307483e499

@ -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,33 +28,28 @@ const Explore = () => {
const loadingState = useLoading(); const loadingState = useLoading();
useEffect(() => { useEffect(() => {
userService const { host } = userService.getState();
.initialState() if (isNullorUndefined(host)) {
.catch() navigate("/auth");
.finally(async () => { return;
const { host } = userService.getState(); }
if (!host) {
locationService.replaceHistory("/auth");
return;
}
memoService.fetchAllMemos().then((memos) => { memoService.fetchAllMemos().then((memos) => {
let filteredMemos = memos; let filteredMemos = memos;
const memoId = Number(query.get("memoId")); const memoId = Number(query.get("memoId"));
if (memoId && !isNaN(memoId)) { if (memoId && !isNaN(memoId)) {
filteredMemos = filteredMemos.filter((memo) => { filteredMemos = filteredMemos.filter((memo) => {
return memo.id === memoId; return memo.id === memoId;
});
}
setState({
...state,
memos: filteredMemos,
});
}); });
loadingState.setFinish(); }
setState({
...state,
memos: filteredMemos,
}); });
loadingState.setFinish();
});
}, [location]); }, [location]);
return ( return (
@ -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,77 +1,77 @@
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 const { host, owner, user } = userService.getState();
.initialState()
.catch()
.finally(async () => {
const { host, owner, user } = userService.getState();
if (!host) {
locationService.replaceHistory("/auth");
return;
}
if (userService.isVisitorMode()) { if (isNullorUndefined(host)) {
if (!owner) { navigate("/auth");
toastHelper.error(t("message.user-not-found")); return;
} }
} else {
if (!user) { if (userService.isVisitorMode()) {
locationService.replaceHistory(`/explore`); if (!owner) {
} toastHelper.error(t("message.user-not-found"));
} }
loadingState.setFinish(); } else {
}); if (isNullorUndefined(user)) {
navigate("/explore");
}
}
}, [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"> <div className="memos-editor-wrapper">
<div className="memos-editor-wrapper"> <MemosHeader />
<MemosHeader /> <Only when={!userService.isVisitorMode()}>
<Only when={!userService.isVisitorMode()}> <MemoEditor />
<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>
</Only> </Only>
</main> <MemoFilter />
</div> </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> </section>
); );
} }

@ -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…
Cancel
Save