From a801ff6cda4aca2221ca26717b81a7add5042d76 Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Sat, 29 Oct 2022 20:43:52 +0800 Subject: [PATCH 01/22] feat(navigation): guard route done --- ui/commitlint.config.js | 2 +- ui/src/App.tsx | 3 +- ui/src/common/constants.ts | 12 +- ui/src/common/interface.ts | 2 +- ui/src/components/Actions/index.tsx | 13 +- ui/src/components/Comment/index.tsx | 18 +- ui/src/components/Editor/ToolBars/image.tsx | 3 +- ui/src/components/FollowingTags/index.tsx | 7 +- ui/src/components/Header/index.tsx | 16 +- ui/src/components/HotQuestions/index.tsx | 3 +- ui/src/components/Modal/PicAuthCodeModal.tsx | 5 +- ui/src/components/Operate/index.tsx | 7 +- ui/src/components/QuestionList/index.tsx | 2 +- ui/src/components/Share/index.tsx | 4 +- ui/src/components/TagSelector/index.tsx | 2 +- ui/src/components/Unactivate/index.tsx | 11 +- ui/src/hooks/useChangeModal/index.tsx | 2 +- ui/src/hooks/useReportModal/index.tsx | 3 +- ui/src/i18n/init.ts | 4 +- ui/src/index.tsx | 22 ++- ui/src/pages/Admin/Answers/index.tsx | 2 +- ui/src/pages/Admin/Flags/index.tsx | 2 +- ui/src/pages/Admin/General/index.tsx | 2 +- ui/src/pages/Admin/Interface/index.tsx | 2 +- ui/src/pages/Admin/Questions/index.tsx | 2 +- ui/src/pages/Admin/Users/index.tsx | 2 +- ui/src/pages/Layout/index.tsx | 43 ++--- ui/src/pages/Questions/Ask/index.tsx | 2 +- .../Detail/components/Answer/index.tsx | 2 +- .../Detail/components/Question/index.tsx | 2 +- .../components/RelatedQuestions/index.tsx | 6 +- .../Detail/components/WriteAnswer/index.tsx | 2 +- ui/src/pages/Questions/Detail/index.tsx | 6 +- ui/src/pages/Questions/EditAnswer/index.tsx | 2 +- ui/src/pages/Search/components/Head/index.tsx | 6 +- ui/src/pages/Search/index.tsx | 2 +- ui/src/pages/Tags/Detail/index.tsx | 2 +- ui/src/pages/Tags/Edit/index.tsx | 6 +- ui/src/pages/Tags/Info/index.tsx | 2 +- ui/src/pages/Tags/index.tsx | 2 +- .../AccountForgot/components/sendEmail.tsx | 2 +- ui/src/pages/Users/AccountForgot/index.tsx | 5 +- ui/src/pages/Users/ActiveEmail/index.tsx | 6 +- ui/src/pages/Users/ConfirmNewEmail/index.tsx | 2 +- ui/src/pages/Users/Login/index.tsx | 31 +-- ui/src/pages/Users/Notifications/index.tsx | 2 +- ui/src/pages/Users/PasswordReset/index.tsx | 13 +- ui/src/pages/Users/Personal/index.tsx | 6 +- .../Register/components/SignUpForm/index.tsx | 2 +- ui/src/pages/Users/Register/index.tsx | 5 +- .../Account/components/ModifyEmail/index.tsx | 4 +- .../Account/components/ModifyPass/index.tsx | 2 +- .../pages/Users/Settings/Interface/index.tsx | 10 +- .../Users/Settings/Notification/index.tsx | 4 +- ui/src/pages/Users/Settings/Profile/index.tsx | 8 +- ui/src/pages/Users/Settings/index.tsx | 4 +- ui/src/pages/Users/Suspended/index.tsx | 4 +- ui/src/router/alias.ts | 8 + ui/src/router/guarder.ts | 42 ++++ ui/src/router/index.tsx | 48 +++-- ui/src/router/route-rules.ts | 9 - ui/src/router/{route-config.ts => routes.ts} | 19 +- ui/src/services/client/index.ts | 1 - ui/src/services/client/notification.ts | 5 +- ui/src/services/client/tag.ts | 5 +- ui/src/services/client/user.ts | 17 -- ui/src/services/common.ts | 2 +- ui/src/services/{api.ts => index.ts} | 0 ui/src/stores/index.ts | 4 +- ui/src/stores/userInfo.ts | 19 +- ui/src/utils/common.ts | 80 ++++++++ ui/src/utils/floppyNavigation.ts | 40 ++++ ui/src/utils/guards.ts | 182 ++++++++++++++++++ ui/src/utils/index.ts | 118 +----------- ui/src/utils/request.ts | 114 ++++++----- ui/src/utils/storage.ts | 5 +- ui/tsconfig.json | 1 - 77 files changed, 656 insertions(+), 411 deletions(-) create mode 100644 ui/src/router/alias.ts create mode 100644 ui/src/router/guarder.ts delete mode 100644 ui/src/router/route-rules.ts rename ui/src/router/{route-config.ts => routes.ts} (90%) delete mode 100644 ui/src/services/client/user.ts rename ui/src/services/{api.ts => index.ts} (100%) create mode 100644 ui/src/utils/common.ts create mode 100644 ui/src/utils/floppyNavigation.ts create mode 100644 ui/src/utils/guards.ts diff --git a/ui/commitlint.config.js b/ui/commitlint.config.js index 84dcb122..4944db0e 100644 --- a/ui/commitlint.config.js +++ b/ui/commitlint.config.js @@ -1,3 +1,3 @@ module.exports = { - extends: ['@commitlint/config-conventional'], + extends: ['@commitlint/routes-conventional'], }; diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 5d4f6925..878ca1ab 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,8 +1,9 @@ import { RouterProvider } from 'react-router-dom'; -import router from '@/router'; +import { routes, createBrowserRouter } from '@/router'; function App() { + const router = createBrowserRouter(routes); return ; } diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts index 9f7f6f5e..364c6bfe 100644 --- a/ui/src/common/constants.ts +++ b/ui/src/common/constants.ts @@ -1,9 +1,9 @@ -export const LOGIN_NEED_BACK = [ - '/users/login', - '/users/register', - '/users/account-recovery', - '/users/password-reset', -]; +export const DEFAULT_LANG = 'en_US'; +export const CURRENT_LANG_STORAGE_KEY = '_a_lang__'; +export const LOGGED_USER_STORAGE_KEY = '_a_lui_'; +export const LOGGED_TOKEN_STORAGE_KEY = '_a_ltk_'; +export const REDIRECT_PATH_STORAGE_KEY = '_a_rp_'; +export const CAPTCHA_CODE_STORAGE_KEY = '_a_captcha_'; export const ADMIN_LIST_STATUS = { // normal; diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 73eac5d4..0b0a733e 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -108,7 +108,7 @@ export interface UserInfoBase { */ status?: string; /** roles */ - is_admin?: true; + is_admin?: boolean; } export interface UserInfoRes extends UserInfoBase { diff --git a/ui/src/components/Actions/index.tsx b/ui/src/components/Actions/index.tsx index 87c35cef..25402ab2 100644 --- a/ui/src/components/Actions/index.tsx +++ b/ui/src/components/Actions/index.tsx @@ -5,11 +5,12 @@ import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; import { Icon } from '@answer/components'; -import { bookmark, postVote } from '@answer/api'; -import { isLogin } from '@answer/utils'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; import { useToast } from '@answer/hooks'; +import { tryNormalLogged } from '@/utils/guards'; +import { bookmark, postVote } from '@/services'; + interface Props { className?: string; data: { @@ -32,7 +33,7 @@ const Index: FC = ({ className, data }) => { state: data?.collected, count: data?.collectCount, }); - const { username = '' } = userInfoStore((state) => state.user); + const { username = '' } = loggedUserInfoStore((state) => state.user); const toast = useToast(); const { t } = useTranslation(); useEffect(() => { @@ -48,7 +49,7 @@ const Index: FC = ({ className, data }) => { }, []); const handleVote = (type: 'up' | 'down') => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } @@ -84,7 +85,7 @@ const Index: FC = ({ className, data }) => { }; const handleBookmark = () => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } bookmark({ diff --git a/ui/src/components/Comment/index.tsx b/ui/src/components/Comment/index.tsx index e7fd3bff..a6dcdc98 100644 --- a/ui/src/components/Comment/index.tsx +++ b/ui/src/components/Comment/index.tsx @@ -8,18 +8,20 @@ import { unionBy } from 'lodash'; import { marked } from 'marked'; import * as Types from '@answer/common/interface'; +import { Modal } from '@answer/components'; +import { usePageUsers, useReportModal } from '@answer/hooks'; +import { matchedUsers, parseUserInfo } from '@answer/utils'; + +import { Form, ActionBar, Reply } from './components'; + +import { tryNormalLogged } from '@/utils/guards'; import { useQueryComments, addComment, deleteComment, updateComment, postVote, -} from '@answer/api'; -import { Modal } from '@answer/components'; -import { usePageUsers, useReportModal } from '@answer/hooks'; -import { matchedUsers, parseUserInfo, isLogin } from '@answer/utils'; - -import { Form, ActionBar, Reply } from './components'; +} from '@/services'; import './index.scss'; @@ -163,7 +165,7 @@ const Comment = ({ objectId, mode }) => { }; const handleVote = (id, is_cancel) => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } @@ -189,7 +191,7 @@ const Comment = ({ objectId, mode }) => { }; const handleAction = ({ action }, item) => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } if (action === 'report') { diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx index 5f4475a2..ead20eee 100644 --- a/ui/src/components/Editor/ToolBars/image.tsx +++ b/ui/src/components/Editor/ToolBars/image.tsx @@ -3,10 +3,11 @@ import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Modal as AnswerModal } from '@answer/components'; -import { uploadImage } from '@answer/api'; import ToolItem from '../toolItem'; import { IEditorContext } from '../types'; +import { uploadImage } from '@/services'; + const Image: FC = ({ editor }) => { const { t } = useTranslation('translation', { keyPrefix: 'editor' }); diff --git a/ui/src/components/FollowingTags/index.tsx b/ui/src/components/FollowingTags/index.tsx index 7f93a205..55e9bc76 100644 --- a/ui/src/components/FollowingTags/index.tsx +++ b/ui/src/components/FollowingTags/index.tsx @@ -4,8 +4,9 @@ import { useTranslation } from 'react-i18next'; import { NavLink } from 'react-router-dom'; import { TagSelector, Tag } from '@answer/components'; -import { isLogin } from '@answer/utils'; -import { useFollowingTags, followTags } from '@answer/api'; + +import { tryNormalLogged } from '@/utils/guards'; +import { useFollowingTags, followTags } from '@/services'; const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'question' }); @@ -32,7 +33,7 @@ const Index: FC = () => { }); }; - if (!isLogin()) { + if (!tryNormalLogged()) { return null; } diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 54d6cee7..b4cd4e16 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -17,17 +17,22 @@ import { useLocation, } from 'react-router-dom'; -import { userInfoStore, siteInfoStore, interfaceStore } from '@answer/stores'; -import { logout, useQueryNotificationStatus } from '@answer/api'; -import Storage from '@answer/utils/storage'; +import { + loggedUserInfoStore, + siteInfoStore, + interfaceStore, +} from '@answer/stores'; import NavItems from './components/NavItems'; +import { logout, useQueryNotificationStatus } from '@/services'; +import { RouteAlias } from '@/router/alias'; + import './index.scss'; const Header: FC = () => { const navigate = useNavigate(); - const { user, clear } = userInfoStore(); + const { user, clear } = loggedUserInfoStore(); const { t } = useTranslation(); const [urlSearch] = useSearchParams(); const q = urlSearch.get('q'); @@ -42,9 +47,8 @@ const Header: FC = () => { const handleLogout = async () => { await logout(); - Storage.remove('token'); clear(); - navigate('/'); + navigate(RouteAlias.home); }; useEffect(() => { diff --git a/ui/src/components/HotQuestions/index.tsx b/ui/src/components/HotQuestions/index.tsx index d398d172..920bdce2 100644 --- a/ui/src/components/HotQuestions/index.tsx +++ b/ui/src/components/HotQuestions/index.tsx @@ -3,9 +3,10 @@ import { Card, ListGroup, ListGroupItem } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useHotQuestions } from '@answer/api'; import { Icon } from '@answer/components'; +import { useHotQuestions } from '@/services'; + const HotQuestions: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'question' }); const [questions, setQuestions] = useState([]); diff --git a/ui/src/components/Modal/PicAuthCodeModal.tsx b/ui/src/components/Modal/PicAuthCodeModal.tsx index f0fe2091..3a081a0c 100644 --- a/ui/src/components/Modal/PicAuthCodeModal.tsx +++ b/ui/src/components/Modal/PicAuthCodeModal.tsx @@ -9,6 +9,9 @@ import type { ImgCodeRes, } from '@answer/common/interface'; +import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants'; +import Storage from '@/utils/storage'; + interface IProps { /** control visible */ visible: boolean; @@ -55,7 +58,7 @@ const Index: React.FC = ({ placeholder={t('placeholder')} isInvalid={captcha.isInvalid} onChange={(e) => { - localStorage.setItem('captchaCode', e.target.value); + Storage.set(CAPTCHA_CODE_STORAGE_KEY, e.target.value); handleCaptcha({ captcha_code: { value: e.target.value, diff --git a/ui/src/components/Operate/index.tsx b/ui/src/components/Operate/index.tsx index 6d76af2a..201774c6 100644 --- a/ui/src/components/Operate/index.tsx +++ b/ui/src/components/Operate/index.tsx @@ -5,10 +5,11 @@ import { useTranslation } from 'react-i18next'; import { Modal } from '@answer/components'; import { useReportModal, useToast } from '@answer/hooks'; -import { deleteQuestion, deleteAnswer } from '@answer/api'; -import { isLogin } from '@answer/utils'; import Share from '../Share'; +import { deleteQuestion, deleteAnswer } from '@/services'; +import { tryNormalLogged } from '@/utils/guards'; + interface IProps { type: 'answer' | 'question'; qid: string; @@ -98,7 +99,7 @@ const Index: FC = ({ }; const handleAction = (action) => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } if (action === 'delete') { diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index 295b9034..69c6ad9d 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -3,7 +3,7 @@ import { Row, Col, ListGroup } from 'react-bootstrap'; import { NavLink, useParams, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQuestionList } from '@answer/api'; +import { useQuestionList } from '@/services'; import type * as Type from '@answer/common/interface'; import { Icon, diff --git a/ui/src/components/Share/index.tsx b/ui/src/components/Share/index.tsx index d5866d98..74b3de34 100644 --- a/ui/src/components/Share/index.tsx +++ b/ui/src/components/Share/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { FacebookShareButton, TwitterShareButton } from 'next-share'; import copy from 'copy-to-clipboard'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; interface IProps { type: 'answer' | 'question'; @@ -15,7 +15,7 @@ interface IProps { } const Index: FC = ({ type, qid, aid, title }) => { - const user = userInfoStore((state) => state.user); + const user = loggedUserInfoStore((state) => state.user); const [show, setShow] = useState(false); const [showTip, setShowTip] = useState(false); const [canSystemShare, setSystemShareState] = useState(false); diff --git a/ui/src/components/TagSelector/index.tsx b/ui/src/components/TagSelector/index.tsx index 790b8242..f939b674 100644 --- a/ui/src/components/TagSelector/index.tsx +++ b/ui/src/components/TagSelector/index.tsx @@ -6,7 +6,7 @@ import { marked } from 'marked'; import classNames from 'classnames'; import { useTagModal } from '@answer/hooks'; -import { queryTags } from '@answer/api'; +import { queryTags } from '@/services'; import type * as Type from '@answer/common/interface'; import './index.scss'; diff --git a/ui/src/components/Unactivate/index.tsx b/ui/src/components/Unactivate/index.tsx index 3fc26200..dd44f68f 100644 --- a/ui/src/components/Unactivate/index.tsx +++ b/ui/src/components/Unactivate/index.tsx @@ -2,14 +2,17 @@ import React, { useState, useEffect } from 'react'; import { Button, Col } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import { resendEmail, checkImgCode } from '@answer/api'; +import { resendEmail, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@answer/components/Modal'; import type { ImgCodeRes, ImgCodeReq, FormDataType, } from '@answer/common/interface'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; + +import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants'; +import Storage from '@/utils/storage'; interface IProps { visible: boolean; @@ -19,7 +22,7 @@ const Index: React.FC = ({ visible = false }) => { const { t } = useTranslation('translation', { keyPrefix: 'inactive' }); const [isSuccess, setSuccess] = useState(false); const [showModal, setModalState] = useState(false); - const { e_mail } = userInfoStore((state) => state.user); + const { e_mail } = loggedUserInfoStore((state) => state.user); const [formData, setFormData] = useState({ captcha_code: { value: '', @@ -47,7 +50,7 @@ const Index: React.FC = ({ visible = false }) => { } let obj: ImgCodeReq = {}; if (imgCode.verify) { - const code = localStorage.getItem('captchaCode') || ''; + const code = Storage.get(CAPTCHA_CODE_STORAGE_KEY) || ''; obj = { captcha_code: code, captcha_id: imgCode.captcha_id, diff --git a/ui/src/hooks/useChangeModal/index.tsx b/ui/src/hooks/useChangeModal/index.tsx index b46aa80d..76b9f2ea 100644 --- a/ui/src/hooks/useChangeModal/index.tsx +++ b/ui/src/hooks/useChangeModal/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import ReactDOM from 'react-dom/client'; -import { changeUserStatus } from '@answer/api'; +import { changeUserStatus } from '@/services'; import { Modal as AnswerModal } from '@answer/components'; const div = document.createElement('div'); diff --git a/ui/src/hooks/useReportModal/index.tsx b/ui/src/hooks/useReportModal/index.tsx index 2566ccd6..2f7c8f18 100644 --- a/ui/src/hooks/useReportModal/index.tsx +++ b/ui/src/hooks/useReportModal/index.tsx @@ -4,10 +4,11 @@ import { useTranslation } from 'react-i18next'; import ReactDOM from 'react-dom/client'; -import { reportList, postReport, closeQuestion, putReport } from '@answer/api'; import { useToast } from '@answer/hooks'; import type * as Type from '@answer/common/interface'; +import { reportList, postReport, closeQuestion, putReport } from '@/services'; + interface Params { isBackend?: boolean; type: Type.ReportType; diff --git a/ui/src/i18n/init.ts b/ui/src/i18n/init.ts index 495ecc1d..deecc0e2 100644 --- a/ui/src/i18n/init.ts +++ b/ui/src/i18n/init.ts @@ -6,6 +6,8 @@ import Backend from 'i18next-http-backend'; import en from './locales/en.json'; import zh from './locales/zh_CN.json'; +import { DEFAULT_LANG } from '@/common/constants'; + i18next // load translation using http .use(Backend) @@ -21,7 +23,7 @@ i18next }, }, // debug: process.env.NODE_ENV === 'development', - fallbackLng: process.env.REACT_APP_LANG || 'en_US', + fallbackLng: process.env.REACT_APP_LANG || DEFAULT_LANG, interpolation: { escapeValue: false, }, diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 8553972a..1943b61a 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -3,14 +3,26 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; + +import { pullLoggedUser } from '@/utils/guards'; + import './i18n/init'; import './index.scss'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, ); -root.render( - - - , -); + +async function bootstrapApp() { + /** + * NOTICE: must pre init logged user info for router + */ + await pullLoggedUser(); + root.render( + + + , + ); +} + +bootstrapApp(); diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx index f9a9de27..ead61b57 100644 --- a/ui/src/pages/Admin/Answers/index.tsx +++ b/ui/src/pages/Admin/Answers/index.tsx @@ -14,7 +14,7 @@ import { } from '@answer/components'; import { ADMIN_LIST_STATUS } from '@answer/common/constants'; import { useEditStatusModal } from '@answer/hooks'; -import { useAnswerSearch, changeAnswerStatus } from '@answer/api'; +import { useAnswerSearch, changeAnswerStatus } from '@/services'; import * as Type from '@answer/common/interface'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Flags/index.tsx b/ui/src/pages/Admin/Flags/index.tsx index 5eb14ec4..ed10eaa5 100644 --- a/ui/src/pages/Admin/Flags/index.tsx +++ b/ui/src/pages/Admin/Flags/index.tsx @@ -12,7 +12,7 @@ import { } from '@answer/components'; import { useReportModal } from '@answer/hooks'; import * as Type from '@answer/common/interface'; -import { useFlagSearch } from '@answer/api'; +import { useFlagSearch } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/General/index.tsx b/ui/src/pages/Admin/General/index.tsx index 87e473d3..9caf81e8 100644 --- a/ui/src/pages/Admin/General/index.tsx +++ b/ui/src/pages/Admin/General/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import type * as Type from '@answer/common/interface'; import { useToast } from '@answer/hooks'; import { siteInfoStore } from '@answer/stores'; -import { useGeneralSetting, updateGeneralSetting } from '@answer/api'; +import { useGeneralSetting, updateGeneralSetting } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx index 66f7ef4c..e22d1046 100644 --- a/ui/src/pages/Admin/Interface/index.tsx +++ b/ui/src/pages/Admin/Interface/index.tsx @@ -14,7 +14,7 @@ import { updateInterfaceSetting, useInterfaceSetting, useThemeOptions, -} from '@answer/api'; +} from '@/services'; import { interfaceStore } from '@answer/stores'; import { UploadImg } from '@answer/components'; diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx index 373faf94..3235a819 100644 --- a/ui/src/pages/Admin/Questions/index.tsx +++ b/ui/src/pages/Admin/Questions/index.tsx @@ -18,7 +18,7 @@ import { useQuestionSearch, changeQuestionStatus, deleteQuestion, -} from '@answer/api'; +} from '@/services'; import * as Type from '@answer/common/interface'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index 6662b11f..b4efe613 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -3,7 +3,7 @@ import { Button, Form, Table, Badge } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQueryUsers } from '@answer/api'; +import { useQueryUsers } from '@/services'; import { Pagination, FormatTime, diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx index 69c5195d..bad408bf 100644 --- a/ui/src/pages/Layout/index.tsx +++ b/ui/src/pages/Layout/index.tsx @@ -1,60 +1,43 @@ -import { FC, useEffect } from 'react'; +import { FC, useEffect, memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; import { Helmet, HelmetProvider } from 'react-helmet-async'; import { SWRConfig } from 'swr'; -import { - userInfoStore, - siteInfoStore, - interfaceStore, - toastStore, -} from '@answer/stores'; +import { siteInfoStore, interfaceStore, toastStore } from '@answer/stores'; import { Header, AdminHeader, Footer, Toast } from '@answer/components'; -import { useSiteSettings, useCheckUserStatus } from '@answer/api'; +import { useSiteSettings } from '@/services'; import Storage from '@/utils/storage'; +import { CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; let isMounted = false; const Layout: FC = () => { const { siteInfo, update: siteStoreUpdate } = siteInfoStore(); const { update: interfaceStoreUpdate } = interfaceStore(); const { data: siteSettings } = useSiteSettings(); - const { data: userStatus } = useCheckUserStatus(); - useEffect(() => { - if (siteSettings) { - siteStoreUpdate(siteSettings.general); - interfaceStoreUpdate(siteSettings.interface); - } - }, [siteSettings]); - const updateUser = userInfoStore((state) => state.update); const { msg: toastMsg, variant, clear: toastClear } = toastStore(); const { i18n } = useTranslation(); const closeToast = () => { toastClear(); }; + + useEffect(() => { + if (siteSettings) { + siteStoreUpdate(siteSettings.general); + interfaceStoreUpdate(siteSettings.interface); + } + }, [siteSettings]); if (!isMounted) { isMounted = true; - const lang = Storage.get('LANG'); - const user = Storage.get('userInfo'); - if (user) { - updateUser(user); - } + const lang = Storage.get(CURRENT_LANG_STORAGE_KEY); if (lang) { i18n.changeLanguage(lang); } } - if (userStatus?.status) { - const user = Storage.get('userInfo'); - if (userStatus.status !== user.status) { - user.status = userStatus?.status; - updateUser(user); - } - } - return ( @@ -76,4 +59,4 @@ const Layout: FC = () => { ); }; -export default Layout; +export default memo(Layout); diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx index abcc86f6..f202e0dc 100644 --- a/ui/src/pages/Questions/Ask/index.tsx +++ b/ui/src/pages/Questions/Ask/index.tsx @@ -14,7 +14,7 @@ import { useQueryRevisions, postAnswer, useQueryQuestionByTitle, -} from '@answer/api'; +} from '@/services'; import type * as Type from '@answer/common/interface'; import SearchQuestion from './components/SearchQuestion'; diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx index b7bd25d8..4ffed114 100644 --- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx @@ -11,7 +11,7 @@ import { FormatTime, htmlRender, } from '@answer/components'; -import { acceptanceAnswer } from '@answer/api'; +import { acceptanceAnswer } from '@/services'; import { scrollTop } from '@answer/utils'; import { AnswerItem } from '@answer/common/interface'; diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx index 8085b172..10c18a47 100644 --- a/ui/src/pages/Questions/Detail/components/Question/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx @@ -13,7 +13,7 @@ import { htmlRender, } from '@answer/components'; import { formatCount } from '@answer/utils'; -import { following } from '@answer/api'; +import { following } from '@/services'; interface Props { data: any; diff --git a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx index bada8207..c6cf29d7 100644 --- a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx @@ -3,16 +3,16 @@ import { Card, ListGroup } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useSimilarQuestion } from '@answer/api'; +import { useSimilarQuestion } from '@/services'; import { Icon } from '@answer/components'; -import { userInfoStore } from '@/stores'; +import { loggedUserInfoStore } from '@/stores'; interface Props { id: string; } const Index: FC = ({ id }) => { - const { user } = userInfoStore(); + const { user } = loggedUserInfoStore(); const { t } = useTranslation('translation', { keyPrefix: 'related_question', }); diff --git a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx index 6343f7ef..d18eb4a8 100644 --- a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx @@ -6,7 +6,7 @@ import { marked } from 'marked'; import classNames from 'classnames'; import { Editor, Modal } from '@answer/components'; -import { postAnswer } from '@answer/api'; +import { postAnswer } from '@/services'; import { FormDataType } from '@answer/common/interface'; interface Props { diff --git a/ui/src/pages/Questions/Detail/index.tsx b/ui/src/pages/Questions/Detail/index.tsx index 2098b6a6..0eb6a15f 100644 --- a/ui/src/pages/Questions/Detail/index.tsx +++ b/ui/src/pages/Questions/Detail/index.tsx @@ -2,9 +2,9 @@ import { useEffect, useState } from 'react'; import { Container, Row, Col } from 'react-bootstrap'; import { useParams, useSearchParams, useNavigate } from 'react-router-dom'; -import { questionDetail, getAnswers } from '@answer/api'; +import { questionDetail, getAnswers } from '@/services'; import { Pagination, PageTitle } from '@answer/components'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; import { scrollTop } from '@answer/utils'; import { usePageUsers } from '@answer/hooks'; import type { @@ -37,7 +37,7 @@ const Index = () => { list: [], }); const { setUsers } = usePageUsers(); - const userInfo = userInfoStore((state) => state.user); + const userInfo = loggedUserInfoStore((state) => state.user); const isAuthor = userInfo?.username === question?.user_info?.username; const requestAnswers = async () => { const res = await getAnswers({ diff --git a/ui/src/pages/Questions/EditAnswer/index.tsx b/ui/src/pages/Questions/EditAnswer/index.tsx index 118fd33c..6a3d5127 100644 --- a/ui/src/pages/Questions/EditAnswer/index.tsx +++ b/ui/src/pages/Questions/EditAnswer/index.tsx @@ -11,7 +11,7 @@ import { useQueryAnswerInfo, modifyAnswer, useQueryRevisions, -} from '@answer/api'; +} from '@/services'; import type * as Type from '@answer/common/interface'; import './index.scss'; diff --git a/ui/src/pages/Search/components/Head/index.tsx b/ui/src/pages/Search/components/Head/index.tsx index c44f67bd..1558bc35 100644 --- a/ui/src/pages/Search/components/Head/index.tsx +++ b/ui/src/pages/Search/components/Head/index.tsx @@ -3,8 +3,8 @@ import { useSearchParams, Link } from 'react-router-dom'; import { Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { following } from '@answer/api'; -import { isLogin } from '@answer/utils'; +import { following } from '@/services'; +import { tryNormalLogged } from '@/utils/guards'; interface Props { data; @@ -20,7 +20,7 @@ const Index: FC = ({ data }) => { const [followed, setFollowed] = useState(data?.is_follower); const follow = () => { - if (!isLogin(true)) { + if (!tryNormalLogged(true)) { return; } following({ diff --git a/ui/src/pages/Search/index.tsx b/ui/src/pages/Search/index.tsx index d08968f4..0582684d 100644 --- a/ui/src/pages/Search/index.tsx +++ b/ui/src/pages/Search/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; import { Pagination, PageTitle } from '@answer/components'; -import { useSearch } from '@answer/api'; +import { useSearch } from '@/services'; import { Head, SearchHead, SearchItem, Tips, Empty } from './components'; diff --git a/ui/src/pages/Tags/Detail/index.tsx b/ui/src/pages/Tags/Detail/index.tsx index cb736c92..3098108c 100644 --- a/ui/src/pages/Tags/Detail/index.tsx +++ b/ui/src/pages/Tags/Detail/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import * as Type from '@answer/common/interface'; import { PageTitle, FollowingTags } from '@answer/components'; -import { useTagInfo, useFollow } from '@answer/api'; +import { useTagInfo, useFollow } from '@/services'; import QuestionList from '@/components/QuestionList'; import HotQuestions from '@/components/HotQuestions'; diff --git a/ui/src/pages/Tags/Edit/index.tsx b/ui/src/pages/Tags/Edit/index.tsx index 2b0541e1..12019642 100644 --- a/ui/src/pages/Tags/Edit/index.tsx +++ b/ui/src/pages/Tags/Edit/index.tsx @@ -7,8 +7,8 @@ import dayjs from 'dayjs'; import classNames from 'classnames'; import { Editor, EditorRef, PageTitle } from '@answer/components'; -import { useTagInfo, modifyTag, useQueryRevisions } from '@answer/api'; -import { userInfoStore } from '@answer/stores'; +import { useTagInfo, modifyTag, useQueryRevisions } from '@/services'; +import { loggedUserInfoStore } from '@answer/stores'; import type * as Type from '@answer/common/interface'; interface FormDataItem { @@ -40,7 +40,7 @@ const initFormData = { }, }; const Ask = () => { - const { is_admin = false } = userInfoStore((state) => state.user); + const { is_admin = false } = loggedUserInfoStore((state) => state.user); const { tagId } = useParams(); const navigate = useNavigate(); diff --git a/ui/src/pages/Tags/Info/index.tsx b/ui/src/pages/Tags/Info/index.tsx index 96cd814a..31c8ac29 100644 --- a/ui/src/pages/Tags/Info/index.tsx +++ b/ui/src/pages/Tags/Info/index.tsx @@ -17,7 +17,7 @@ import { useQuerySynonymsTags, saveSynonymsTags, deleteTag, -} from '@answer/api'; +} from '@/services'; const TagIntroduction = () => { const [isEdit, setEditState] = useState(false); diff --git a/ui/src/pages/Tags/index.tsx b/ui/src/pages/Tags/index.tsx index 2f9719f6..63fc7276 100644 --- a/ui/src/pages/Tags/index.tsx +++ b/ui/src/pages/Tags/index.tsx @@ -3,7 +3,7 @@ import { Container, Row, Col, Card, Button, Form } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQueryTags, following } from '@answer/api'; +import { useQueryTags, following } from '@/services'; import { Tag, Pagination, PageTitle, QueryGroup } from '@answer/components'; import { formatCount } from '@answer/utils'; diff --git a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx index 9ae6d195..e9619ced 100644 --- a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx +++ b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx @@ -2,7 +2,7 @@ import { FC, memo, useEffect, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { resetPassword, checkImgCode } from '@answer/api'; +import { resetPassword, checkImgCode } from '@/services'; import type { ImgCodeRes, PasswordResetReq, diff --git a/ui/src/pages/Users/AccountForgot/index.tsx b/ui/src/pages/Users/AccountForgot/index.tsx index b6a34610..5065e9b6 100644 --- a/ui/src/pages/Users/AccountForgot/index.tsx +++ b/ui/src/pages/Users/AccountForgot/index.tsx @@ -2,10 +2,9 @@ import React, { useState, useEffect } from 'react'; import { Container, Col } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import { isLogin } from '@answer/utils'; - import SendEmail from './components/sendEmail'; +import { tryNormalLogged } from '@/utils/guards'; import { PageTitle } from '@/components'; const Index: React.FC = () => { @@ -19,7 +18,7 @@ const Index: React.FC = () => { }; useEffect(() => { - isLogin(); + tryNormalLogged(); }, []); return ( diff --git a/ui/src/pages/Users/ActiveEmail/index.tsx b/ui/src/pages/Users/ActiveEmail/index.tsx index dee3fcb7..e2b3491e 100644 --- a/ui/src/pages/Users/ActiveEmail/index.tsx +++ b/ui/src/pages/Users/ActiveEmail/index.tsx @@ -1,15 +1,15 @@ import { FC, memo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { activateAccount } from '@answer/api'; -import { userInfoStore } from '@answer/stores'; +import { activateAccount } from '@/services'; +import { loggedUserInfoStore } from '@answer/stores'; import { getQueryString } from '@answer/utils'; import { PageTitle } from '@/components'; const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'page_title' }); - const updateUser = userInfoStore((state) => state.update); + const updateUser = loggedUserInfoStore((state) => state.update); useEffect(() => { const code = getQueryString('code'); diff --git a/ui/src/pages/Users/ConfirmNewEmail/index.tsx b/ui/src/pages/Users/ConfirmNewEmail/index.tsx index cbd9ad70..9adaf80e 100644 --- a/ui/src/pages/Users/ConfirmNewEmail/index.tsx +++ b/ui/src/pages/Users/ConfirmNewEmail/index.tsx @@ -3,7 +3,7 @@ import { Container, Row, Col } from 'react-bootstrap'; import { Link, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { changeEmailVerify } from '@answer/api'; +import { changeEmailVerify } from '@/services'; import { PageTitle } from '@/components'; diff --git a/ui/src/pages/Users/Login/index.tsx b/ui/src/pages/Users/Login/index.tsx index 8d18a500..6d9af5c2 100644 --- a/ui/src/pages/Users/Login/index.tsx +++ b/ui/src/pages/Users/Login/index.tsx @@ -1,26 +1,30 @@ import React, { FormEvent, useState, useEffect } from 'react'; import { Container, Form, Button, Col } from 'react-bootstrap'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; -import { login, checkImgCode } from '@answer/api'; import type { LoginReqParams, ImgCodeRes, FormDataType, } from '@answer/common/interface'; import { PageTitle, Unactivate } from '@answer/components'; -import { userInfoStore } from '@answer/stores'; -import { isLogin, getQueryString } from '@answer/utils'; +import { loggedUserInfoStore } from '@answer/stores'; +import { getQueryString } from '@answer/utils'; +import { login, checkImgCode } from '@/services'; +import { deriveUserStat, tryNormalLogged } from '@/utils/guards'; +import { REDIRECT_PATH_STORAGE_KEY } from '@/common/constants'; +import { RouteAlias } from '@/router/alias'; import { PicAuthCodeModal } from '@/components/Modal'; import Storage from '@/utils/storage'; const Index: React.FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'login' }); + const navigate = useNavigate(); const [refresh, setRefresh] = useState(0); - const updateUser = userInfoStore((state) => state.update); - const storeUser = userInfoStore((state) => state.user); + const updateUser = loggedUserInfoStore((state) => state.update); + const storeUser = loggedUserInfoStore((state) => state.user); const [formData, setFormData] = useState({ e_mail: { value: '', @@ -102,15 +106,16 @@ const Index: React.FC = () => { login(params) .then((res) => { updateUser(res); - if (res.mail_status === 2) { + const userStat = deriveUserStat(); + if (!userStat.isActivated) { // inactive setStep(2); setRefresh((pre) => pre + 1); - } - if (res.mail_status === 1) { - const path = Storage.get('ANSWER_PATH') || '/'; - Storage.remove('ANSWER_PATH'); - window.location.replace(path); + } else { + const path = + Storage.get(REDIRECT_PATH_STORAGE_KEY) || RouteAlias.home; + Storage.remove(REDIRECT_PATH_STORAGE_KEY); + navigate(path, { replace: true }); } setModalState(false); @@ -154,7 +159,7 @@ const Index: React.FC = () => { if ((storeUser.id && storeUser.mail_status === 2) || isInactive) { setStep(2); } else { - isLogin(); + tryNormalLogged(); } }, []); diff --git a/ui/src/pages/Users/Notifications/index.tsx b/ui/src/pages/Users/Notifications/index.tsx index aef9be45..687e7773 100644 --- a/ui/src/pages/Users/Notifications/index.tsx +++ b/ui/src/pages/Users/Notifications/index.tsx @@ -8,7 +8,7 @@ import { clearUnreadNotification, clearNotificationStatus, readNotification, -} from '@answer/api'; +} from '@/services'; import { PageTitle } from '@answer/components'; import Inbox from './components/Inbox'; diff --git a/ui/src/pages/Users/PasswordReset/index.tsx b/ui/src/pages/Users/PasswordReset/index.tsx index abffc6ba..af97001f 100644 --- a/ui/src/pages/Users/PasswordReset/index.tsx +++ b/ui/src/pages/Users/PasswordReset/index.tsx @@ -3,19 +3,19 @@ import { Container, Col, Form, Button } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { replacementPassword } from '@answer/api'; -import { userInfoStore } from '@answer/stores'; -import { getQueryString, isLogin } from '@answer/utils'; +import { loggedUserInfoStore } from '@answer/stores'; +import { getQueryString } from '@answer/utils'; import type { FormDataType } from '@answer/common/interface'; -import Storage from '@/utils/storage'; +import { replacementPassword } from '@/services'; +import { tryNormalLogged } from '@/utils/guards'; import { PageTitle } from '@/components'; const Index: React.FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'password_reset' }); const [step, setStep] = useState(1); - const clearUser = userInfoStore((state) => state.clear); + const clearUser = loggedUserInfoStore((state) => state.clear); const [formData, setFormData] = useState({ pass: { value: '', @@ -105,7 +105,6 @@ const Index: React.FC = () => { .then(() => { // clear login information then to login page clearUser(); - Storage.remove('token'); setStep(2); }) .catch((err) => { @@ -118,7 +117,7 @@ const Index: React.FC = () => { }; useEffect(() => { - isLogin(); + tryNormalLogged(); }, []); return ( <> diff --git a/ui/src/pages/Users/Personal/index.tsx b/ui/src/pages/Users/Personal/index.tsx index 7c099a4b..fa8c2200 100644 --- a/ui/src/pages/Users/Personal/index.tsx +++ b/ui/src/pages/Users/Personal/index.tsx @@ -4,12 +4,12 @@ import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'react-router-dom'; import { Pagination, FormatTime, PageTitle, Empty } from '@answer/components'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; import { usePersonalInfoByName, usePersonalTop, usePersonalListByTabName, -} from '@answer/api'; +} from '@/services'; import { UserInfo, @@ -30,7 +30,7 @@ const Personal: FC = () => { const page = searchParams.get('page') || 1; const order = searchParams.get('order') || 'newest'; const { t } = useTranslation('translation', { keyPrefix: 'personal' }); - const sessionUser = userInfoStore((state) => state.user); + const sessionUser = loggedUserInfoStore((state) => state.user); const isSelf = sessionUser?.username === username; const { data: userInfo } = usePersonalInfoByName(username); diff --git a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx index 2f067962..08afacf2 100644 --- a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx +++ b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx @@ -3,7 +3,7 @@ import { Form, Button, Col } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; -import { register } from '@answer/api'; +import { register } from '@/services'; import type { FormDataType } from '@answer/common/interface'; import userStore from '@/stores/userInfo'; diff --git a/ui/src/pages/Users/Register/index.tsx b/ui/src/pages/Users/Register/index.tsx index c50c353c..5f8370a0 100644 --- a/ui/src/pages/Users/Register/index.tsx +++ b/ui/src/pages/Users/Register/index.tsx @@ -3,10 +3,11 @@ import { Container } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { PageTitle, Unactivate } from '@answer/components'; -import { isLogin } from '@answer/utils'; import SignUpForm from './components/SignUpForm'; +import { tryNormalLogged } from '@/utils/guards'; + const Index: React.FC = () => { const [showForm, setShowForm] = useState(true); const { t } = useTranslation('translation', { keyPrefix: 'login' }); @@ -16,7 +17,7 @@ const Index: React.FC = () => { }; useEffect(() => { - isLogin(); + tryNormalLogged(); }, []); return ( diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx index 84182a53..495fd1d4 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx @@ -3,7 +3,7 @@ import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import type * as Type from '@answer/common/interface'; -import { getUserInfo, changeEmail } from '@answer/api'; +import { getLoggedUserInfo, changeEmail } from '@/services'; import { useToast } from '@answer/hooks'; const reg = /(?<=.{2}).+(?=@)/gi; @@ -23,7 +23,7 @@ const Index: FC = () => { const [userInfo, setUserInfo] = useState(); const toast = useToast(); useEffect(() => { - getUserInfo().then((resp) => { + getLoggedUserInfo().then((resp) => { setUserInfo(resp); }); }, []); diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx index 23265763..eac17839 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx @@ -2,7 +2,7 @@ import React, { FC, FormEvent, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { modifyPassword } from '@answer/api'; +import { modifyPassword } from '@/services'; import { useToast } from '@answer/hooks'; import type { FormDataType } from '@answer/common/interface'; diff --git a/ui/src/pages/Users/Settings/Interface/index.tsx b/ui/src/pages/Users/Settings/Interface/index.tsx index ca7ce38d..a256a65f 100644 --- a/ui/src/pages/Users/Settings/Interface/index.tsx +++ b/ui/src/pages/Users/Settings/Interface/index.tsx @@ -6,10 +6,11 @@ import dayjs from 'dayjs'; import en from 'dayjs/locale/en'; import zh from 'dayjs/locale/zh-cn'; -import { languages } from '@answer/api'; +import { languages } from '@/services'; import type { LangsType, FormDataType } from '@answer/common/interface'; import { useToast } from '@answer/hooks'; +import { DEFAULT_LANG, CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; const Index = () => { @@ -34,8 +35,8 @@ const Index = () => { const handleSubmit = (event: FormEvent) => { event.preventDefault(); - Storage.set('LANG', formData.lang.value); - dayjs.locale(formData.lang.value === 'en_US' ? en : zh); + Storage.set(CURRENT_LANG_STORAGE_KEY, formData.lang.value); + dayjs.locale(formData.lang.value === DEFAULT_LANG ? en : zh); i18n.changeLanguage(formData.lang.value); toast.onShow({ msg: t('update', { keyPrefix: 'toast' }), @@ -45,7 +46,7 @@ const Index = () => { useEffect(() => { getLangs(); - const lang = Storage.get('LANG'); + const lang = Storage.get(CURRENT_LANG_STORAGE_KEY); if (lang) { setFormData({ lang: { @@ -60,7 +61,6 @@ const Index = () => {
{t('lang.label')} - { @@ -20,7 +20,7 @@ const Index = () => { }); const getProfile = () => { - getUserInfo().then((res) => { + getLoggedUserInfo().then((res) => { setFormData({ notice_switch: { value: res.notice_status === 1, diff --git a/ui/src/pages/Users/Settings/Profile/index.tsx b/ui/src/pages/Users/Settings/Profile/index.tsx index 49266ceb..ecfd3449 100644 --- a/ui/src/pages/Users/Settings/Profile/index.tsx +++ b/ui/src/pages/Users/Settings/Profile/index.tsx @@ -4,10 +4,10 @@ import { Trans, useTranslation } from 'react-i18next'; import { marked } from 'marked'; -import { modifyUserInfo, uploadAvatar, getUserInfo } from '@answer/api'; +import { modifyUserInfo, uploadAvatar, getLoggedUserInfo } from '@/services'; import type { FormDataType } from '@answer/common/interface'; import { UploadImg, Avatar } from '@answer/components'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; import { useToast } from '@answer/hooks'; const Index: React.FC = () => { @@ -15,7 +15,7 @@ const Index: React.FC = () => { keyPrefix: 'settings.profile', }); const toast = useToast(); - const { user, update } = userInfoStore(); + const { user, update } = loggedUserInfoStore(); const [formData, setFormData] = useState({ display_name: { value: '', @@ -164,7 +164,7 @@ const Index: React.FC = () => { }; const getProfile = () => { - getUserInfo().then((res) => { + getLoggedUserInfo().then((res) => { formData.display_name.value = res.display_name; formData.username.value = res.username; formData.bio.value = res.bio; diff --git a/ui/src/pages/Users/Settings/index.tsx b/ui/src/pages/Users/Settings/index.tsx index 45f3080c..8967cd4e 100644 --- a/ui/src/pages/Users/Settings/index.tsx +++ b/ui/src/pages/Users/Settings/index.tsx @@ -3,7 +3,7 @@ import { Container, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; -import { getUserInfo } from '@answer/api'; +import { getLoggedUserInfo } from '@/services'; import type { FormDataType } from '@answer/common/interface'; import Nav from './components/Nav'; @@ -43,7 +43,7 @@ const Index: React.FC = () => { }, }); const getProfile = () => { - getUserInfo().then((res) => { + getLoggedUserInfo().then((res) => { formData.display_name.value = res.display_name; formData.bio.value = res.bio; formData.avatar.value = res.avatar; diff --git a/ui/src/pages/Users/Suspended/index.tsx b/ui/src/pages/Users/Suspended/index.tsx index 293603d0..4c381d44 100644 --- a/ui/src/pages/Users/Suspended/index.tsx +++ b/ui/src/pages/Users/Suspended/index.tsx @@ -1,12 +1,12 @@ import { useTranslation } from 'react-i18next'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; import { PageTitle } from '@/components'; const Suspended = () => { const { t } = useTranslation('translation', { keyPrefix: 'suspended' }); - const userInfo = userInfoStore((state) => state.user); + const userInfo = loggedUserInfoStore((state) => state.user); if (userInfo.status !== 'forbidden') { window.location.replace('/'); diff --git a/ui/src/router/alias.ts b/ui/src/router/alias.ts new file mode 100644 index 00000000..f6959ed3 --- /dev/null +++ b/ui/src/router/alias.ts @@ -0,0 +1,8 @@ +export const RouteAlias = { + home: '/', + login: '/users/login', + register: '/users/register', + activation: '/users/login?status=inactive', + activationFailed: '/users/account-activation/failed', + suspended: '/users/account-suspended', +}; diff --git a/ui/src/router/guarder.ts b/ui/src/router/guarder.ts new file mode 100644 index 00000000..8fd8a69b --- /dev/null +++ b/ui/src/router/guarder.ts @@ -0,0 +1,42 @@ +import { + pullLoggedUser, + isLoggedAndNormal, + isAdminLogged, + isLogged, + isNotLogged, + isNotLoggedOrNormal, + isLoggedAndInactive, + isLoggedAndSuspended, + isNotLoggedOrInactive, +} from '@/utils/guards'; + +const RouteGuarder = { + base: async () => { + return isNotLoggedOrNormal(); + }, + logged: async () => { + return isLogged(); + }, + notLogged: async () => { + return isNotLogged(); + }, + notLoggedOrInactive: async () => { + return isNotLoggedOrInactive(); + }, + loggedAndNormal: async () => { + await pullLoggedUser(true); + return isLoggedAndNormal(); + }, + loggedAndInactive: async () => { + return isLoggedAndInactive(); + }, + loggedAndSuspended: async () => { + return isLoggedAndSuspended(); + }, + adminLogged: async () => { + await pullLoggedUser(true); + return isAdminLogged(); + }, +}; + +export default RouteGuarder; diff --git a/ui/src/router/index.tsx b/ui/src/router/index.tsx index 99d44723..a7452ff8 100644 --- a/ui/src/router/index.tsx +++ b/ui/src/router/index.tsx @@ -1,14 +1,13 @@ import React, { Suspense, lazy } from 'react'; -import { RouteObject, createBrowserRouter } from 'react-router-dom'; +import { RouteObject, createBrowserRouter, redirect } from 'react-router-dom'; -import Layout from '@answer/pages/Layout'; - -import routeConfig, { RouteNode } from '@/router/route-config'; -import RouteRules from '@/router/route-rules'; +import Layout from '@/pages/Layout'; +import baseRoutes, { RouteNode } from '@/router/routes'; +import { floppyNavigation } from '@/utils'; const routes: RouteObject[] = []; -const routeGen = (routeNodes: RouteNode[], root: RouteObject[]) => { +const routeWrapper = (routeNodes: RouteNode[], root: RouteObject[]) => { routeNodes.forEach((rn) => { if (rn.path === '/') { rn.element = ; @@ -18,40 +17,37 @@ const routeGen = (routeNodes: RouteNode[], root: RouteObject[]) => { * ref: https://webpack.js.org/api/module-methods/#import-1 */ rn.page = rn.page.replace('pages/', ''); - const Control = lazy(() => import(`@/pages/${rn.page}`)); + const Ctrl = lazy(() => import(`@/pages/${rn.page}`)); rn.element = ( - + ); } root.push(rn); - if (Array.isArray(rn.rules)) { - const ruleFunc: Function[] = []; - if (typeof rn.loader === 'function') { - ruleFunc.push(rn.loader); - } - rn.rules.forEach((ruleKey) => { - const func = RouteRules[ruleKey]; - if (typeof func === 'function') { - ruleFunc.push(func); + if (rn.guard) { + const { guard } = rn; + const loaderRef = rn.loader; + rn.loader = async (args) => { + const gr = await guard(args); + if (gr?.redirect && floppyNavigation.differentCurrent(gr.redirect)) { + return redirect(gr.redirect); } - }); - rn.loader = ({ params }) => { - ruleFunc.forEach((func) => { - func(params); - }); + let ret; + if (typeof loaderRef === 'function') { + ret = await loaderRef(args); + } + return ret; }; } const children = Array.isArray(rn.children) ? rn.children : null; if (children) { rn.children = []; - routeGen(children, rn.children); + routeWrapper(children, rn.children); } }); }; -routeGen(routeConfig, routes); +routeWrapper(baseRoutes, routes); -const router = createBrowserRouter(routes); -export default router; +export { routes, createBrowserRouter }; diff --git a/ui/src/router/route-rules.ts b/ui/src/router/route-rules.ts deleted file mode 100644 index e7c2b83c..00000000 --- a/ui/src/router/route-rules.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { isLogin } from '@answer/utils'; - -const RouteRules = { - isLoginAndNormal: () => { - return isLogin(true); - }, -}; - -export default RouteRules; diff --git a/ui/src/router/route-config.ts b/ui/src/router/routes.ts similarity index 90% rename from ui/src/router/route-config.ts rename to ui/src/router/routes.ts index 6267482e..ad964c38 100644 --- a/ui/src/router/route-config.ts +++ b/ui/src/router/routes.ts @@ -1,14 +1,18 @@ import { RouteObject } from 'react-router-dom'; +import RouteGuarder from '@/router/guarder'; + export interface RouteNode extends RouteObject { page: string; children?: RouteNode[]; - rules?: string[]; + guard?: Function; } -const routeConfig: RouteNode[] = [ + +const routes: RouteNode[] = [ { path: '/', page: 'pages/Layout', + guard: RouteGuarder.base, children: [ // question and answer { @@ -31,12 +35,12 @@ const routeConfig: RouteNode[] = [ { path: 'questions/ask', page: 'pages/Questions/Ask', - rules: ['isLoginAndNormal'], + guard: RouteGuarder.loggedAndNormal, }, { path: 'posts/:qid/edit', page: 'pages/Questions/Ask', - rules: ['isLoginAndNormal'], + guard: RouteGuarder.loggedAndNormal, }, { path: 'posts/:qid/:aid/edit', @@ -105,18 +109,22 @@ const routeConfig: RouteNode[] = [ { path: 'users/login', page: 'pages/Users/Login', + guard: RouteGuarder.notLoggedOrInactive, }, { path: 'users/register', page: 'pages/Users/Register', + guard: RouteGuarder.notLogged, }, { path: 'users/account-recovery', page: 'pages/Users/AccountForgot', + guard: RouteGuarder.loggedAndNormal, }, { path: 'users/password-reset', page: 'pages/Users/PasswordReset', + guard: RouteGuarder.loggedAndNormal, }, { path: 'users/account-activation', @@ -142,6 +150,7 @@ const routeConfig: RouteNode[] = [ { path: 'admin', page: 'pages/Admin', + guard: RouteGuarder.adminLogged, children: [ { index: true, @@ -192,4 +201,4 @@ const routeConfig: RouteNode[] = [ ], }, ]; -export default routeConfig; +export default routes; diff --git a/ui/src/services/client/index.ts b/ui/src/services/client/index.ts index dbea39bf..1f1af66a 100644 --- a/ui/src/services/client/index.ts +++ b/ui/src/services/client/index.ts @@ -1,6 +1,5 @@ export * from './activity'; export * from './personal'; -export * from './user'; export * from './notification'; export * from './question'; export * from './search'; diff --git a/ui/src/services/client/notification.ts b/ui/src/services/client/notification.ts index 18b73343..dd9d880e 100644 --- a/ui/src/services/client/notification.ts +++ b/ui/src/services/client/notification.ts @@ -2,9 +2,10 @@ import useSWR from 'swr'; import qs from 'qs'; import request from '@answer/utils/request'; -import { isLogin } from '@answer/utils'; import type * as Type from '@answer/common/interface'; +import { tryNormalLogged } from '@/utils/guards'; + export const useQueryNotifications = (params) => { const apiUrl = `/answer/api/v1/notification/page?${qs.stringify(params, { skipNulls: true, @@ -33,7 +34,7 @@ export const useQueryNotificationStatus = () => { const apiUrl = '/answer/api/v1/notification/status'; return useSWR<{ inbox: number; achievement: number }>( - isLogin() ? apiUrl : null, + tryNormalLogged() ? apiUrl : null, request.instance.get, { refreshInterval: 3000, diff --git a/ui/src/services/client/tag.ts b/ui/src/services/client/tag.ts index 87e46743..634b4472 100644 --- a/ui/src/services/client/tag.ts +++ b/ui/src/services/client/tag.ts @@ -1,9 +1,10 @@ import useSWR from 'swr'; import request from '@answer/utils/request'; -import { isLogin } from '@answer/utils'; import type * as Type from '@answer/common/interface'; +import { tryNormalLogged } from '@/utils/guards'; + export const deleteTag = (id) => { return request.delete('/answer/api/v1/tag', { tag_id: id, @@ -24,7 +25,7 @@ export const saveSynonymsTags = (params) => { export const useFollowingTags = () => { let apiUrl = ''; - if (isLogin()) { + if (tryNormalLogged()) { apiUrl = '/answer/api/v1/tags/following'; } const { data, error, mutate } = useSWR(apiUrl, request.instance.get); diff --git a/ui/src/services/client/user.ts b/ui/src/services/client/user.ts deleted file mode 100644 index abe9ee3a..00000000 --- a/ui/src/services/client/user.ts +++ /dev/null @@ -1,17 +0,0 @@ -import useSWR from 'swr'; - -import request from '@answer/utils/request'; - -export const useCheckUserStatus = () => { - const apiUrl = '/answer/api/v1/user/status'; - const hasToken = localStorage.getItem('token'); - const { data, error } = useSWR<{ status: string }, Error>( - hasToken ? apiUrl : null, - request.instance.get, - ); - return { - data, - isLoading: !data && !error, - error, - }; -}; diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts index 6cef9f19..6f2cf83a 100644 --- a/ui/src/services/common.ts +++ b/ui/src/services/common.ts @@ -115,7 +115,7 @@ export const resendEmail = (params?: Type.ImgCodeReq) => { * @description get login userinfo * @returns {UserInfo} */ -export const getUserInfo = () => { +export const getLoggedUserInfo = () => { return request.get('/answer/api/v1/user/info'); }; diff --git a/ui/src/services/api.ts b/ui/src/services/index.ts similarity index 100% rename from ui/src/services/api.ts rename to ui/src/services/index.ts diff --git a/ui/src/stores/index.ts b/ui/src/stores/index.ts index 0962911e..6bc6377e 100644 --- a/ui/src/stores/index.ts +++ b/ui/src/stores/index.ts @@ -1,12 +1,12 @@ import toastStore from './toast'; -import userInfoStore from './userInfo'; +import loggedUserInfoStore from './userInfo'; import globalStore from './global'; import siteInfoStore from './siteInfo'; import interfaceStore from './interface'; export { toastStore, - userInfoStore, + loggedUserInfoStore, globalStore, siteInfoStore, interfaceStore, diff --git a/ui/src/stores/userInfo.ts b/ui/src/stores/userInfo.ts index bbfb640e..9aa540b5 100644 --- a/ui/src/stores/userInfo.ts +++ b/ui/src/stores/userInfo.ts @@ -3,6 +3,11 @@ import create from 'zustand'; import type { UserInfoRes } from '@answer/common/interface'; import Storage from '@answer/utils/storage'; +import { + LOGGED_USER_STORAGE_KEY, + LOGGED_TOKEN_STORAGE_KEY, +} from '@/common/constants'; + interface UserInfoStore { user: UserInfoRes; update: (params: UserInfoRes) => void; @@ -19,23 +24,23 @@ const initUser: UserInfoRes = { location: '', website: '', status: '', - mail_status: 0, + mail_status: 1, }; -const userInfoStore = create((set) => ({ +const loggedUserInfoStore = create((set) => ({ user: initUser, update: (params) => set(() => { - Storage.set('token', params.access_token); - Storage.set('userInfo', params); + Storage.set(LOGGED_TOKEN_STORAGE_KEY, params.access_token); + Storage.set(LOGGED_USER_STORAGE_KEY, params); return { user: params }; }), clear: () => set(() => { - // Storage.remove('token'); - Storage.remove('userInfo'); + Storage.remove(LOGGED_TOKEN_STORAGE_KEY); + Storage.remove(LOGGED_USER_STORAGE_KEY); return { user: initUser }; }), })); -export default userInfoStore; +export default loggedUserInfoStore; diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts new file mode 100644 index 00000000..169dc1ad --- /dev/null +++ b/ui/src/utils/common.ts @@ -0,0 +1,80 @@ +function getQueryString(name: string): string { + const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`); + const r = window.location.search.substr(1).match(reg); + if (r != null) return unescape(r[2]); + return ''; +} + +function thousandthDivision(num) { + const reg = /\d{1,3}(?=(\d{3})+$)/g; + return `${num}`.replace(reg, '$&,'); +} + +function formatCount($num: number): string { + let res = String($num); + if (!Number.isFinite($num)) { + res = '0'; + } else if ($num < 10000) { + res = thousandthDivision($num); + } else if ($num < 1000000) { + res = `${Math.round($num / 100) / 10}k`; + } else if ($num >= 1000000) { + res = `${Math.round($num / 100000) / 10}m`; + } + return res; +} + +function scrollTop(element) { + if (!element) { + return; + } + const offset = 120; + const bodyRect = document.body.getBoundingClientRect().top; + const elementRect = element.getBoundingClientRect().top; + const elementPosition = elementRect - bodyRect; + const offsetPosition = elementPosition - offset; + + window.scrollTo({ + top: offsetPosition, + }); +} + +/** + * Extract user info from markdown + * @param markdown string + * @returns Array<{displayName: string, userName: string}> + */ +function matchedUsers(markdown) { + const globalReg = /\B@([\w|]+)/g; + const reg = /\B@([\w\\_\\.]+)/; + + const users = markdown.match(globalReg); + if (!users) { + return []; + } + return users.map((user) => { + const matched = user.match(reg); + return { + userName: matched[1], + }; + }); +} + +/** + * Identify user information from markdown + * @param markdown string + * @returns string + */ +function parseUserInfo(markdown) { + const globalReg = /\B@([\w\\_\\.\\-]+)/g; + return markdown.replace(globalReg, '[@$1](/u/$1)'); +} + +export { + getQueryString, + thousandthDivision, + formatCount, + scrollTop, + matchedUsers, + parseUserInfo, +}; diff --git a/ui/src/utils/floppyNavigation.ts b/ui/src/utils/floppyNavigation.ts new file mode 100644 index 00000000..7edbbaa0 --- /dev/null +++ b/ui/src/utils/floppyNavigation.ts @@ -0,0 +1,40 @@ +import { RouteAlias } from '@/router/alias'; +import Storage from '@/utils/storage'; +import { REDIRECT_PATH_STORAGE_KEY } from '@/common/constants'; + +const differentCurrent = (target: string, base?: string) => { + base ||= window.location.origin; + const targetUrl = new URL(target, base); + return targetUrl.toString() !== window.location.href; +}; + +/** + * only navigate if not same as current url + * @param pathname + * @param callback + */ +const navigate = (pathname: string, callback: Function) => { + if (differentCurrent(pathname)) { + callback(); + } +}; + +/** + * auto navigate to login page with redirect info + */ +const navigateToLogin = () => { + const { pathname } = window.location; + if (pathname !== RouteAlias.login && pathname !== RouteAlias.register) { + const redirectUrl = window.location.href; + Storage.set(REDIRECT_PATH_STORAGE_KEY, redirectUrl); + } + navigate(RouteAlias.login, () => { + window.location.replace(RouteAlias.login); + }); +}; + +export const floppyNavigation = { + differentCurrent, + navigate, + navigateToLogin, +}; diff --git a/ui/src/utils/guards.ts b/ui/src/utils/guards.ts new file mode 100644 index 00000000..f0faf55b --- /dev/null +++ b/ui/src/utils/guards.ts @@ -0,0 +1,182 @@ +import { getLoggedUserInfo } from '@/services'; +import { loggedUserInfoStore } from '@/stores'; +import { RouteAlias } from '@/router/alias'; +import Storage from '@/utils/storage'; +import { LOGGED_USER_STORAGE_KEY } from '@/common/constants'; +import { floppyNavigation } from '@/utils/floppyNavigation'; + +type UserStat = { + isLogged: boolean; + isActivated: boolean; + isSuspended: boolean; + isNormal: boolean; + isAdmin: boolean; +}; +export const deriveUserStat = (): UserStat => { + const stat: UserStat = { + isLogged: false, + isActivated: false, + isSuspended: false, + isNormal: false, + isAdmin: false, + }; + const { user } = loggedUserInfoStore.getState(); + if (user.id && user.username) { + stat.isLogged = true; + } + if (stat.isLogged && user.mail_status === 1) { + stat.isActivated = true; + } + if (stat.isLogged && user.status === 'forbidden') { + stat.isSuspended = true; + } + if (stat.isLogged && stat.isActivated && !stat.isSuspended) { + stat.isNormal = true; + } + if (stat.isNormal && user.is_admin === true) { + stat.isAdmin = true; + } + + return stat; +}; + +type GuardResult = { + ok: boolean; + redirect?: string; +}; +let pullLock = false; +let dedupeTimestamp = 0; +export const pullLoggedUser = async (forceRePull = false) => { + // only pull once if not force re-pull + if (pullLock && !forceRePull) { + return; + } + // dedupe pull requests in this time span in 10 seconds + if (Date.now() - dedupeTimestamp < 1000 * 10) { + return; + } + dedupeTimestamp = Date.now(); + const loggedUserInfo = await getLoggedUserInfo().catch((ex) => { + dedupeTimestamp = 0; + if (!deriveUserStat().isLogged) { + // load fallback userInfo from local storage + const storageLoggedUserInfo = Storage.get(LOGGED_USER_STORAGE_KEY); + if (storageLoggedUserInfo) { + loggedUserInfoStore.getState().update(storageLoggedUserInfo); + } + } + console.error(ex); + }); + if (loggedUserInfo) { + pullLock = true; + loggedUserInfoStore.getState().update(loggedUserInfo); + } +}; + +export const isLogged = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + if (!userStat.isLogged) { + ret.ok = false; + ret.redirect = RouteAlias.login; + } + return ret; +}; + +export const isNotLogged = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + if (userStat.isLogged) { + ret.ok = false; + ret.redirect = RouteAlias.home; + } + return ret; +}; + +export const isLoggedAndInactive = () => { + const ret: GuardResult = { ok: false, redirect: undefined }; + const userStat = deriveUserStat(); + if (!userStat.isActivated) { + ret.ok = true; + ret.redirect = RouteAlias.activation; + } + return ret; +}; + +export const isLoggedAndSuspended = () => { + const ret: GuardResult = { ok: false, redirect: undefined }; + const userStat = deriveUserStat(); + if (userStat.isSuspended) { + ret.redirect = RouteAlias.suspended; + ret.ok = true; + } + return ret; +}; + +export const isLoggedAndNormal = () => { + const ret: GuardResult = { ok: false, redirect: undefined }; + const userStat = deriveUserStat(); + if (userStat.isNormal) { + ret.ok = true; + } else if (!userStat.isActivated) { + ret.redirect = RouteAlias.activation; + } else if (!userStat.isSuspended) { + ret.redirect = RouteAlias.suspended; + } else if (!userStat.isLogged) { + ret.redirect = RouteAlias.login; + } + return ret; +}; + +export const isNotLoggedOrNormal = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + const gr = isLoggedAndNormal(); + if (!gr.ok && userStat.isLogged) { + ret.ok = false; + ret.redirect = gr.redirect; + } + return ret; +}; + +export const isNotLoggedOrInactive = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + if (userStat.isLogged || userStat.isActivated) { + ret.ok = false; + ret.redirect = RouteAlias.home; + } + return ret; +}; + +export const isAdminLogged = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + if (!userStat.isAdmin) { + ret.redirect = RouteAlias.home; + ret.ok = false; + } + return ret; +}; + +/** + * try user was logged and all state ok + * @param autoLogin + */ +export const tryNormalLogged = (autoLogin: boolean = false) => { + const gr = isLoggedAndNormal(); + if (gr.ok) { + return true; + } + + if (gr.redirect === RouteAlias.login && autoLogin) { + floppyNavigation.navigateToLogin(); + } else if (gr.redirect) { + floppyNavigation.navigate(gr.redirect, () => { + // @ts-ignore + window.location.replace(gr.redirect); + }); + } + + return false; +}; diff --git a/ui/src/utils/index.ts b/ui/src/utils/index.ts index 20cde293..a1eaf02c 100644 --- a/ui/src/utils/index.ts +++ b/ui/src/utils/index.ts @@ -1,114 +1,6 @@ -import { LOGIN_NEED_BACK } from '@answer/common/constants'; +export * from './common'; +export * as guards from './guards'; -import Storage from './storage'; - -function getQueryString(name: string): string { - const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`); - const r = window.location.search.substr(1).match(reg); - if (r != null) return unescape(r[2]); - return ''; -} - -function thousandthDivision(num) { - const reg = /\d{1,3}(?=(\d{3})+$)/g; - return `${num}`.replace(reg, '$&,'); -} - -function formatCount($num: number): string { - let res = String($num); - if (!Number.isFinite($num)) { - res = '0'; - } else if ($num < 10000) { - res = thousandthDivision($num); - } else if ($num < 1000000) { - res = `${Math.round($num / 100) / 10}k`; - } else if ($num >= 1000000) { - res = `${Math.round($num / 100000) / 10}m`; - } - return res; -} - -function isLogin(needToLogin?: boolean): boolean { - const user = Storage.get('userInfo'); - const path = window.location.pathname; - - // User deleted or suspended - if (user.username && user.status === 'forbidden') { - if (path !== '/users/account-suspended') { - window.location.pathname = '/users/account-suspended'; - } - return false; - } - - // login and active - if (user.username && user.mail_status === 1) { - if (LOGIN_NEED_BACK.includes(path)) { - window.location.replace('/'); - } - return true; - } - - // un login or inactivated - if ((!user.username || user.mail_status === 2) && needToLogin) { - Storage.set('ANSWER_PATH', path); - window.location.href = '/users/login'; - } - - return false; -} - -function scrollTop(element) { - if (!element) { - return; - } - const offset = 120; - const bodyRect = document.body.getBoundingClientRect().top; - const elementRect = element.getBoundingClientRect().top; - const elementPosition = elementRect - bodyRect; - const offsetPosition = elementPosition - offset; - - window.scrollTo({ - top: offsetPosition, - }); -} - -/** - * Extract user info from markdown - * @param markdown string - * @returns Array<{displayName: string, userName: string}> - */ -function matchedUsers(markdown) { - const globalReg = /\B@([\w|]+)/g; - const reg = /\B@([\w\\_\\.]+)/; - - const users = markdown.match(globalReg); - if (!users) { - return []; - } - return users.map((user) => { - const matched = user.match(reg); - return { - userName: matched[1], - }; - }); -} - -/** - * Identify user infromation from markdown - * @param markdown string - * @returns string - */ -function parseUserInfo(markdown) { - const globalReg = /\B@([\w\\_\\.\\-]+)/g; - return markdown.replace(globalReg, '[@$1](/u/$1)'); -} - -export { - getQueryString, - thousandthDivision, - formatCount, - isLogin, - scrollTop, - matchedUsers, - parseUserInfo, -}; +export { default as request } from './request'; +export { default as Storage } from './storage'; +export { floppyNavigation } from './floppyNavigation'; diff --git a/ui/src/utils/request.ts b/ui/src/utils/request.ts index d532d707..c9f9e67f 100644 --- a/ui/src/utils/request.ts +++ b/ui/src/utils/request.ts @@ -2,9 +2,17 @@ import axios, { AxiosResponse } from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; import { Modal } from '@answer/components'; -import { userInfoStore, toastStore } from '@answer/stores'; +import { loggedUserInfoStore, toastStore } from '@answer/stores'; import Storage from './storage'; +import { floppyNavigation } from './floppyNavigation'; + +import { + LOGGED_TOKEN_STORAGE_KEY, + CURRENT_LANG_STORAGE_KEY, + DEFAULT_LANG, +} from '@/common/constants'; +import { RouteAlias } from '@/router/alias'; const API = { development: '', @@ -25,12 +33,11 @@ class Request { constructor(config: AxiosRequestConfig) { this.instance = axios.create(config); - this.instance.interceptors.request.use( (requestConfig: AxiosRequestConfig) => { - const token = Storage.get('token') || ''; + const token = Storage.get(LOGGED_TOKEN_STORAGE_KEY) || ''; // default lang en_US - const lang = Storage.get('LANG') || 'en_US'; + const lang = Storage.get(CURRENT_LANG_STORAGE_KEY) || DEFAULT_LANG; requestConfig.headers = { Authorization: token, 'Accept-Language': lang, @@ -54,23 +61,23 @@ class Request { return data; }, (error) => { - const { status, data, msg } = error.response; - const { data: realData, msg: realMsg = '' } = data; + const { status, data: respData, msg: respMsg } = error.response; + const { data, msg = '' } = respData; if (status === 400) { // show error message - if (realData instanceof Object && realData.err_type) { - if (realData.err_type === 'toast') { + if (data instanceof Object && data.err_type) { + if (data.err_type === 'toast') { // toast error message toastStore.getState().show({ - msg: realMsg, + msg, variant: 'danger', }); } - if (realData.type === 'modal') { + if (data.type === 'modal') { // modal error message Modal.confirm({ - content: realMsg, + content: msg, }); } @@ -78,63 +85,56 @@ class Request { } if ( - realData instanceof Object && - Object.keys(realData).length > 0 && - realData.key + data instanceof Object && + Object.keys(data).length > 0 && + data.key ) { // handle form error - return Promise.reject({ ...realData, isError: true }); + return Promise.reject({ ...data, isError: true }); } - if (!realData || Object.keys(realData).length <= 0) { + if (!data || Object.keys(data).length <= 0) { // default error msg will show modal Modal.confirm({ - content: realMsg, + content: msg, + }); + return Promise.reject(false); + } + } + // 401: Re-login required + if (status === 401) { + // clear userinfo + loggedUserInfoStore.getState().clear(); + floppyNavigation.navigateToLogin(); + return Promise.reject(false); + } + if (status === 403) { + // Permission interception + if (data?.type === 'url_expired') { + // url expired + floppyNavigation.navigate(RouteAlias.activationFailed, () => { + window.location.replace(RouteAlias.activationFailed); + }); + return Promise.reject(false); + } + if (data?.type === 'inactive') { + // inactivated + floppyNavigation.navigate(RouteAlias.activation, () => { + window.location.href = RouteAlias.activation; + }); + return Promise.reject(false); + } + + if (data?.type === 'suspended') { + floppyNavigation.navigate(RouteAlias.suspended, () => { + window.location.replace(RouteAlias.suspended); }); return Promise.reject(false); } } - if (status === 401) { - // clear userinfo; - Storage.remove('token'); - userInfoStore.getState().clear(); - // need login - const { pathname } = window.location; - if (pathname !== '/users/login' && pathname !== '/users/register') { - Storage.set('ANSWER_PATH', window.location.pathname); - } - window.location.href = '/users/login'; - - return Promise.reject(false); - } - - if (status === 403) { - // Permission interception - - if (realData?.type === 'inactive') { - // inactivated - window.location.href = '/users/login?status=inactive'; - return Promise.reject(false); - } - - if (realData?.type === 'url_expired') { - // url expired - window.location.href = '/users/account-activation/failed'; - return Promise.reject(false); - } - - if (realData?.type === 'suspended') { - if (window.location.pathname !== '/users/account-suspended') { - window.location.href = '/users/account-suspended'; - } - - return Promise.reject(false); - } - } - toastStore.getState().show({ - msg: `statusCode: ${status}; ${msg || ''}`, + msg: `statusCode: ${status}; ${respMsg || ''}`, variant: 'danger', }); return Promise.reject(false); @@ -178,6 +178,4 @@ class Request { } } -// export const Request; - export default new Request(baseConfig); diff --git a/ui/src/utils/storage.ts b/ui/src/utils/storage.ts index bf14d85e..0ab3b115 100644 --- a/ui/src/utils/storage.ts +++ b/ui/src/utils/storage.ts @@ -3,13 +3,12 @@ const Storage = { const value = localStorage.getItem(key); if (value) { try { - const v = JSON.parse(value); - return v; + return JSON.parse(value); } catch { return value; } } - return false; + return undefined; }, set: (key: string, value: any): void => { if (typeof value === 'string') { diff --git a/ui/tsconfig.json b/ui/tsconfig.json index c3804747..d7c5decf 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -26,7 +26,6 @@ "@answer/components/*": ["src/components/*"], "@answer/stores": ["src/stores"], "@answer/stores/*": ["src/stores/*"], - "@answer/api": ["src/services/api.ts"], "@answer/services/*": ["src/services/*"], "@answer/hooks": ["src/hooks"], "@answer/common": ["src/common"], From e2cf6d2b3d8fd87dd6eb9bbfdcd78598aff5ec7b Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Mon, 31 Oct 2022 10:34:51 +0800 Subject: [PATCH 02/22] feat(router-guard): set route guard for activation/* and confirm-new-email --- ui/src/router/guarder.ts | 4 ---- ui/src/router/routes.ts | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/src/router/guarder.ts b/ui/src/router/guarder.ts index 8fd8a69b..8c5bf7c2 100644 --- a/ui/src/router/guarder.ts +++ b/ui/src/router/guarder.ts @@ -2,7 +2,6 @@ import { pullLoggedUser, isLoggedAndNormal, isAdminLogged, - isLogged, isNotLogged, isNotLoggedOrNormal, isLoggedAndInactive, @@ -14,9 +13,6 @@ const RouteGuarder = { base: async () => { return isNotLoggedOrNormal(); }, - logged: async () => { - return isLogged(); - }, notLogged: async () => { return isNotLogged(); }, diff --git a/ui/src/router/routes.ts b/ui/src/router/routes.ts index ad964c38..5abf7e4b 100644 --- a/ui/src/router/routes.ts +++ b/ui/src/router/routes.ts @@ -126,9 +126,11 @@ const routes: RouteNode[] = [ page: 'pages/Users/PasswordReset', guard: RouteGuarder.loggedAndNormal, }, + // TODO: guard '/account-activation/*', '/users/confirm-new-email' { path: 'users/account-activation', page: 'pages/Users/ActiveEmail', + guard: RouteGuarder.loggedAndInactive, }, { path: 'users/account-activation/success', @@ -145,6 +147,7 @@ const routes: RouteNode[] = [ { path: '/users/account-suspended', page: 'pages/Users/Suspended', + guard: RouteGuarder.loggedAndSuspended, }, // for admin { From d3cd9a94dd733310aafb8365d53969b8fcf9ef07 Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Mon, 31 Oct 2022 11:39:33 +0800 Subject: [PATCH 03/22] refactor(import): fix import order --- ui/src/components/QuestionList/index.tsx | 3 ++- ui/src/components/TagSelector/index.tsx | 3 ++- ui/src/components/Unactivate/index.tsx | 2 +- ui/src/hooks/useChangeModal/index.tsx | 3 ++- ui/src/pages/Admin/Answers/index.tsx | 3 ++- ui/src/pages/Admin/Flags/index.tsx | 1 + ui/src/pages/Admin/General/index.tsx | 1 + ui/src/pages/Admin/Interface/index.tsx | 5 +++-- ui/src/pages/Admin/Questions/index.tsx | 3 ++- ui/src/pages/Admin/Users/index.tsx | 3 ++- ui/src/pages/Layout/index.tsx | 2 +- ui/src/pages/Questions/Ask/index.tsx | 7 ++++--- .../Questions/Detail/components/Answer/index.tsx | 3 ++- .../Questions/Detail/components/Question/index.tsx | 1 + .../Detail/components/RelatedQuestions/index.tsx | 2 +- .../Questions/Detail/components/WriteAnswer/index.tsx | 3 ++- ui/src/pages/Questions/Detail/index.tsx | 3 ++- ui/src/pages/Questions/EditAnswer/index.tsx | 3 ++- ui/src/pages/Search/index.tsx | 3 ++- ui/src/pages/Tags/Detail/index.tsx | 2 +- ui/src/pages/Tags/Edit/index.tsx | 3 ++- ui/src/pages/Tags/Info/index.tsx | 1 + ui/src/pages/Tags/index.tsx | 3 ++- .../Users/AccountForgot/components/sendEmail.tsx | 2 +- ui/src/pages/Users/ActiveEmail/index.tsx | 2 +- ui/src/pages/Users/ConfirmNewEmail/index.tsx | 1 - ui/src/pages/Users/Notifications/index.tsx | 9 +++++---- ui/src/pages/Users/Personal/index.tsx | 11 ++++++----- .../Users/Register/components/SignUpForm/index.tsx | 2 +- .../Settings/Account/components/ModifyEmail/index.tsx | 3 ++- .../Settings/Account/components/ModifyPass/index.tsx | 3 ++- ui/src/pages/Users/Settings/Interface/index.tsx | 2 +- ui/src/pages/Users/Settings/Notification/index.tsx | 3 ++- ui/src/pages/Users/Settings/Profile/index.tsx | 3 ++- ui/src/pages/Users/Settings/index.tsx | 2 +- 35 files changed, 65 insertions(+), 41 deletions(-) diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index 69c6ad9d..1be1f847 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -3,7 +3,6 @@ import { Row, Col, ListGroup } from 'react-bootstrap'; import { NavLink, useParams, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQuestionList } from '@/services'; import type * as Type from '@answer/common/interface'; import { Icon, @@ -15,6 +14,8 @@ import { QueryGroup, } from '@answer/components'; +import { useQuestionList } from '@/services'; + const QuestionOrderKeys: Type.QuestionOrderBy[] = [ 'newest', 'active', diff --git a/ui/src/components/TagSelector/index.tsx b/ui/src/components/TagSelector/index.tsx index f939b674..cedb85a3 100644 --- a/ui/src/components/TagSelector/index.tsx +++ b/ui/src/components/TagSelector/index.tsx @@ -6,9 +6,10 @@ import { marked } from 'marked'; import classNames from 'classnames'; import { useTagModal } from '@answer/hooks'; -import { queryTags } from '@/services'; import type * as Type from '@answer/common/interface'; +import { queryTags } from '@/services'; + import './index.scss'; interface IProps { diff --git a/ui/src/components/Unactivate/index.tsx b/ui/src/components/Unactivate/index.tsx index dd44f68f..eeb3a1af 100644 --- a/ui/src/components/Unactivate/index.tsx +++ b/ui/src/components/Unactivate/index.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { Button, Col } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import { resendEmail, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@answer/components/Modal'; import type { ImgCodeRes, @@ -11,6 +10,7 @@ import type { } from '@answer/common/interface'; import { loggedUserInfoStore } from '@answer/stores'; +import { resendEmail, checkImgCode } from '@/services'; import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; diff --git a/ui/src/hooks/useChangeModal/index.tsx b/ui/src/hooks/useChangeModal/index.tsx index 76b9f2ea..f484331c 100644 --- a/ui/src/hooks/useChangeModal/index.tsx +++ b/ui/src/hooks/useChangeModal/index.tsx @@ -4,9 +4,10 @@ import { useTranslation } from 'react-i18next'; import ReactDOM from 'react-dom/client'; -import { changeUserStatus } from '@/services'; import { Modal as AnswerModal } from '@answer/components'; +import { changeUserStatus } from '@/services'; + const div = document.createElement('div'); const root = ReactDOM.createRoot(div); diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx index ead61b57..2a25f53c 100644 --- a/ui/src/pages/Admin/Answers/index.tsx +++ b/ui/src/pages/Admin/Answers/index.tsx @@ -14,9 +14,10 @@ import { } from '@answer/components'; import { ADMIN_LIST_STATUS } from '@answer/common/constants'; import { useEditStatusModal } from '@answer/hooks'; -import { useAnswerSearch, changeAnswerStatus } from '@/services'; import * as Type from '@answer/common/interface'; +import { useAnswerSearch, changeAnswerStatus } from '@/services'; + import '../index.scss'; const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted']; diff --git a/ui/src/pages/Admin/Flags/index.tsx b/ui/src/pages/Admin/Flags/index.tsx index ed10eaa5..a7842429 100644 --- a/ui/src/pages/Admin/Flags/index.tsx +++ b/ui/src/pages/Admin/Flags/index.tsx @@ -12,6 +12,7 @@ import { } from '@answer/components'; import { useReportModal } from '@answer/hooks'; import * as Type from '@answer/common/interface'; + import { useFlagSearch } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/General/index.tsx b/ui/src/pages/Admin/General/index.tsx index 9caf81e8..3154caec 100644 --- a/ui/src/pages/Admin/General/index.tsx +++ b/ui/src/pages/Admin/General/index.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import type * as Type from '@answer/common/interface'; import { useToast } from '@answer/hooks'; import { siteInfoStore } from '@answer/stores'; + import { useGeneralSetting, updateGeneralSetting } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx index e22d1046..7c83e10e 100644 --- a/ui/src/pages/Admin/Interface/index.tsx +++ b/ui/src/pages/Admin/Interface/index.tsx @@ -8,6 +8,9 @@ import { FormDataType, AdminSettingsInterface, } from '@answer/common/interface'; +import { interfaceStore } from '@answer/stores'; +import { UploadImg } from '@answer/components'; + import { languages, uploadAvatar, @@ -15,8 +18,6 @@ import { useInterfaceSetting, useThemeOptions, } from '@/services'; -import { interfaceStore } from '@answer/stores'; -import { UploadImg } from '@answer/components'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx index 3235a819..1a188512 100644 --- a/ui/src/pages/Admin/Questions/index.tsx +++ b/ui/src/pages/Admin/Questions/index.tsx @@ -14,12 +14,13 @@ import { } from '@answer/components'; import { ADMIN_LIST_STATUS } from '@answer/common/constants'; import { useEditStatusModal, useReportModal } from '@answer/hooks'; +import * as Type from '@answer/common/interface'; + import { useQuestionSearch, changeQuestionStatus, deleteQuestion, } from '@/services'; -import * as Type from '@answer/common/interface'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index b4efe613..825e7f23 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -3,7 +3,6 @@ import { Button, Form, Table, Badge } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQueryUsers } from '@/services'; import { Pagination, FormatTime, @@ -14,6 +13,8 @@ import { import * as Type from '@answer/common/interface'; import { useChangeModal } from '@answer/hooks'; +import { useQueryUsers } from '@/services'; + import '../index.scss'; const UserFilterKeys: Type.UserFilterBy[] = [ diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx index bad408bf..7affe2f0 100644 --- a/ui/src/pages/Layout/index.tsx +++ b/ui/src/pages/Layout/index.tsx @@ -7,8 +7,8 @@ import { SWRConfig } from 'swr'; import { siteInfoStore, interfaceStore, toastStore } from '@answer/stores'; import { Header, AdminHeader, Footer, Toast } from '@answer/components'; -import { useSiteSettings } from '@/services'; +import { useSiteSettings } from '@/services'; import Storage from '@/utils/storage'; import { CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx index f202e0dc..f233150e 100644 --- a/ui/src/pages/Questions/Ask/index.tsx +++ b/ui/src/pages/Questions/Ask/index.tsx @@ -7,6 +7,10 @@ import dayjs from 'dayjs'; import classNames from 'classnames'; import { Editor, EditorRef, TagSelector, PageTitle } from '@answer/components'; +import type * as Type from '@answer/common/interface'; + +import SearchQuestion from './components/SearchQuestion'; + import { saveQuestion, questionDetail, @@ -15,9 +19,6 @@ import { postAnswer, useQueryQuestionByTitle, } from '@/services'; -import type * as Type from '@answer/common/interface'; - -import SearchQuestion from './components/SearchQuestion'; interface FormDataItem { title: Type.FormValue; diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx index 4ffed114..e97238cf 100644 --- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx @@ -11,10 +11,11 @@ import { FormatTime, htmlRender, } from '@answer/components'; -import { acceptanceAnswer } from '@/services'; import { scrollTop } from '@answer/utils'; import { AnswerItem } from '@answer/common/interface'; +import { acceptanceAnswer } from '@/services'; + interface Props { data: AnswerItem; /** router answer id */ diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx index 10c18a47..aa3a38ec 100644 --- a/ui/src/pages/Questions/Detail/components/Question/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx @@ -13,6 +13,7 @@ import { htmlRender, } from '@answer/components'; import { formatCount } from '@answer/utils'; + import { following } from '@/services'; interface Props { diff --git a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx index c6cf29d7..8493408c 100644 --- a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx @@ -3,9 +3,9 @@ import { Card, ListGroup } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useSimilarQuestion } from '@/services'; import { Icon } from '@answer/components'; +import { useSimilarQuestion } from '@/services'; import { loggedUserInfoStore } from '@/stores'; interface Props { diff --git a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx index d18eb4a8..9659b286 100644 --- a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx @@ -6,9 +6,10 @@ import { marked } from 'marked'; import classNames from 'classnames'; import { Editor, Modal } from '@answer/components'; -import { postAnswer } from '@/services'; import { FormDataType } from '@answer/common/interface'; +import { postAnswer } from '@/services'; + interface Props { visible?: boolean; data: { diff --git a/ui/src/pages/Questions/Detail/index.tsx b/ui/src/pages/Questions/Detail/index.tsx index 0eb6a15f..2f469415 100644 --- a/ui/src/pages/Questions/Detail/index.tsx +++ b/ui/src/pages/Questions/Detail/index.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; import { Container, Row, Col } from 'react-bootstrap'; import { useParams, useSearchParams, useNavigate } from 'react-router-dom'; -import { questionDetail, getAnswers } from '@/services'; import { Pagination, PageTitle } from '@answer/components'; import { loggedUserInfoStore } from '@answer/stores'; import { scrollTop } from '@answer/utils'; @@ -22,6 +21,8 @@ import { Alert, } from './components'; +import { questionDetail, getAnswers } from '@/services'; + import './index.scss'; const Index = () => { diff --git a/ui/src/pages/Questions/EditAnswer/index.tsx b/ui/src/pages/Questions/EditAnswer/index.tsx index 6a3d5127..c87733f4 100644 --- a/ui/src/pages/Questions/EditAnswer/index.tsx +++ b/ui/src/pages/Questions/EditAnswer/index.tsx @@ -7,12 +7,13 @@ import dayjs from 'dayjs'; import classNames from 'classnames'; import { Editor, EditorRef, Icon, PageTitle } from '@answer/components'; +import type * as Type from '@answer/common/interface'; + import { useQueryAnswerInfo, modifyAnswer, useQueryRevisions, } from '@/services'; -import type * as Type from '@answer/common/interface'; import './index.scss'; diff --git a/ui/src/pages/Search/index.tsx b/ui/src/pages/Search/index.tsx index 0582684d..914565ae 100644 --- a/ui/src/pages/Search/index.tsx +++ b/ui/src/pages/Search/index.tsx @@ -4,10 +4,11 @@ import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; import { Pagination, PageTitle } from '@answer/components'; -import { useSearch } from '@/services'; import { Head, SearchHead, SearchItem, Tips, Empty } from './components'; +import { useSearch } from '@/services'; + const Index = () => { const { t } = useTranslation('translation'); const [searchParams] = useSearchParams(); diff --git a/ui/src/pages/Tags/Detail/index.tsx b/ui/src/pages/Tags/Detail/index.tsx index 3098108c..e37ab0ab 100644 --- a/ui/src/pages/Tags/Detail/index.tsx +++ b/ui/src/pages/Tags/Detail/index.tsx @@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next'; import * as Type from '@answer/common/interface'; import { PageTitle, FollowingTags } from '@answer/components'; -import { useTagInfo, useFollow } from '@/services'; +import { useTagInfo, useFollow } from '@/services'; import QuestionList from '@/components/QuestionList'; import HotQuestions from '@/components/HotQuestions'; diff --git a/ui/src/pages/Tags/Edit/index.tsx b/ui/src/pages/Tags/Edit/index.tsx index 12019642..58e7d59c 100644 --- a/ui/src/pages/Tags/Edit/index.tsx +++ b/ui/src/pages/Tags/Edit/index.tsx @@ -7,10 +7,11 @@ import dayjs from 'dayjs'; import classNames from 'classnames'; import { Editor, EditorRef, PageTitle } from '@answer/components'; -import { useTagInfo, modifyTag, useQueryRevisions } from '@/services'; import { loggedUserInfoStore } from '@answer/stores'; import type * as Type from '@answer/common/interface'; +import { useTagInfo, modifyTag, useQueryRevisions } from '@/services'; + interface FormDataItem { displayName: Type.FormValue; slugName: Type.FormValue; diff --git a/ui/src/pages/Tags/Info/index.tsx b/ui/src/pages/Tags/Info/index.tsx index 31c8ac29..d8c4ad75 100644 --- a/ui/src/pages/Tags/Info/index.tsx +++ b/ui/src/pages/Tags/Info/index.tsx @@ -12,6 +12,7 @@ import { Modal, PageTitle, } from '@answer/components'; + import { useTagInfo, useQuerySynonymsTags, diff --git a/ui/src/pages/Tags/index.tsx b/ui/src/pages/Tags/index.tsx index 63fc7276..80256d94 100644 --- a/ui/src/pages/Tags/index.tsx +++ b/ui/src/pages/Tags/index.tsx @@ -3,10 +3,11 @@ import { Container, Row, Col, Card, Button, Form } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useQueryTags, following } from '@/services'; import { Tag, Pagination, PageTitle, QueryGroup } from '@answer/components'; import { formatCount } from '@answer/utils'; +import { useQueryTags, following } from '@/services'; + const sortBtns = ['popular', 'name', 'newest']; const Tags = () => { diff --git a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx index e9619ced..e95b1354 100644 --- a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx +++ b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx @@ -2,13 +2,13 @@ import { FC, memo, useEffect, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { resetPassword, checkImgCode } from '@/services'; import type { ImgCodeRes, PasswordResetReq, FormDataType, } from '@answer/common/interface'; +import { resetPassword, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@/components/Modal'; interface IProps { diff --git a/ui/src/pages/Users/ActiveEmail/index.tsx b/ui/src/pages/Users/ActiveEmail/index.tsx index e2b3491e..f50fbc3e 100644 --- a/ui/src/pages/Users/ActiveEmail/index.tsx +++ b/ui/src/pages/Users/ActiveEmail/index.tsx @@ -1,10 +1,10 @@ import { FC, memo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { activateAccount } from '@/services'; import { loggedUserInfoStore } from '@answer/stores'; import { getQueryString } from '@answer/utils'; +import { activateAccount } from '@/services'; import { PageTitle } from '@/components'; const Index: FC = () => { diff --git a/ui/src/pages/Users/ConfirmNewEmail/index.tsx b/ui/src/pages/Users/ConfirmNewEmail/index.tsx index 9adaf80e..d849ffa5 100644 --- a/ui/src/pages/Users/ConfirmNewEmail/index.tsx +++ b/ui/src/pages/Users/ConfirmNewEmail/index.tsx @@ -4,7 +4,6 @@ import { Link, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { changeEmailVerify } from '@/services'; - import { PageTitle } from '@/components'; const Index: FC = () => { diff --git a/ui/src/pages/Users/Notifications/index.tsx b/ui/src/pages/Users/Notifications/index.tsx index 687e7773..47e69a04 100644 --- a/ui/src/pages/Users/Notifications/index.tsx +++ b/ui/src/pages/Users/Notifications/index.tsx @@ -3,16 +3,17 @@ import { Container, Row, Col, ButtonGroup, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useParams, useNavigate } from 'react-router-dom'; +import { PageTitle } from '@answer/components'; + +import Inbox from './components/Inbox'; +import Achievements from './components/Achievements'; + import { useQueryNotifications, clearUnreadNotification, clearNotificationStatus, readNotification, } from '@/services'; -import { PageTitle } from '@answer/components'; - -import Inbox from './components/Inbox'; -import Achievements from './components/Achievements'; const PAGE_SIZE = 10; diff --git a/ui/src/pages/Users/Personal/index.tsx b/ui/src/pages/Users/Personal/index.tsx index fa8c2200..fcab49ff 100644 --- a/ui/src/pages/Users/Personal/index.tsx +++ b/ui/src/pages/Users/Personal/index.tsx @@ -5,11 +5,6 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { Pagination, FormatTime, PageTitle, Empty } from '@answer/components'; import { loggedUserInfoStore } from '@answer/stores'; -import { - usePersonalInfoByName, - usePersonalTop, - usePersonalListByTabName, -} from '@/services'; import { UserInfo, @@ -24,6 +19,12 @@ import { Votes, } from './components'; +import { + usePersonalInfoByName, + usePersonalTop, + usePersonalListByTabName, +} from '@/services'; + const Personal: FC = () => { const { tabName = 'overview', username = '' } = useParams(); const [searchParams] = useSearchParams(); diff --git a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx index 08afacf2..4f072fe7 100644 --- a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx +++ b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx @@ -3,9 +3,9 @@ import { Form, Button, Col } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; -import { register } from '@/services'; import type { FormDataType } from '@answer/common/interface'; +import { register } from '@/services'; import userStore from '@/stores/userInfo'; interface Props { diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx index 495fd1d4..4ed6b0ff 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx @@ -3,9 +3,10 @@ import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import type * as Type from '@answer/common/interface'; -import { getLoggedUserInfo, changeEmail } from '@/services'; import { useToast } from '@answer/hooks'; +import { getLoggedUserInfo, changeEmail } from '@/services'; + const reg = /(?<=.{2}).+(?=@)/gi; const Index: FC = () => { diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx index eac17839..2c7ed9ef 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx @@ -2,10 +2,11 @@ import React, { FC, FormEvent, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { modifyPassword } from '@/services'; import { useToast } from '@answer/hooks'; import type { FormDataType } from '@answer/common/interface'; +import { modifyPassword } from '@/services'; + const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'settings.account', diff --git a/ui/src/pages/Users/Settings/Interface/index.tsx b/ui/src/pages/Users/Settings/Interface/index.tsx index a256a65f..f38f92e8 100644 --- a/ui/src/pages/Users/Settings/Interface/index.tsx +++ b/ui/src/pages/Users/Settings/Interface/index.tsx @@ -6,10 +6,10 @@ import dayjs from 'dayjs'; import en from 'dayjs/locale/en'; import zh from 'dayjs/locale/zh-cn'; -import { languages } from '@/services'; import type { LangsType, FormDataType } from '@answer/common/interface'; import { useToast } from '@answer/hooks'; +import { languages } from '@/services'; import { DEFAULT_LANG, CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; diff --git a/ui/src/pages/Users/Settings/Notification/index.tsx b/ui/src/pages/Users/Settings/Notification/index.tsx index 8015901a..6d0cab61 100644 --- a/ui/src/pages/Users/Settings/Notification/index.tsx +++ b/ui/src/pages/Users/Settings/Notification/index.tsx @@ -3,9 +3,10 @@ import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import type { FormDataType } from '@answer/common/interface'; -import { setNotice, getLoggedUserInfo } from '@/services'; import { useToast } from '@answer/hooks'; +import { setNotice, getLoggedUserInfo } from '@/services'; + const Index = () => { const toast = useToast(); const { t } = useTranslation('translation', { diff --git a/ui/src/pages/Users/Settings/Profile/index.tsx b/ui/src/pages/Users/Settings/Profile/index.tsx index ecfd3449..661c222a 100644 --- a/ui/src/pages/Users/Settings/Profile/index.tsx +++ b/ui/src/pages/Users/Settings/Profile/index.tsx @@ -4,12 +4,13 @@ import { Trans, useTranslation } from 'react-i18next'; import { marked } from 'marked'; -import { modifyUserInfo, uploadAvatar, getLoggedUserInfo } from '@/services'; import type { FormDataType } from '@answer/common/interface'; import { UploadImg, Avatar } from '@answer/components'; import { loggedUserInfoStore } from '@answer/stores'; import { useToast } from '@answer/hooks'; +import { modifyUserInfo, uploadAvatar, getLoggedUserInfo } from '@/services'; + const Index: React.FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'settings.profile', diff --git a/ui/src/pages/Users/Settings/index.tsx b/ui/src/pages/Users/Settings/index.tsx index 8967cd4e..671707fe 100644 --- a/ui/src/pages/Users/Settings/index.tsx +++ b/ui/src/pages/Users/Settings/index.tsx @@ -3,11 +3,11 @@ import { Container, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; -import { getLoggedUserInfo } from '@/services'; import type { FormDataType } from '@answer/common/interface'; import Nav from './components/Nav'; +import { getLoggedUserInfo } from '@/services'; import { PageTitle } from '@/components'; const Index: React.FC = () => { From 04b1b46ef25975f6cbfeea40d8fe4d6a88fad610 Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Mon, 31 Oct 2022 16:10:12 +0800 Subject: [PATCH 04/22] refactor(services): update some service import issue --- ui/src/pages/Admin/Smtp/index.tsx | 2 +- ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx | 6 +++--- ui/src/pages/Users/ConfirmNewEmail/index.tsx | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ui/src/pages/Admin/Smtp/index.tsx b/ui/src/pages/Admin/Smtp/index.tsx index f6714255..17abd481 100644 --- a/ui/src/pages/Admin/Smtp/index.tsx +++ b/ui/src/pages/Admin/Smtp/index.tsx @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'; import type * as Type from '@answer/common/interface'; import { useToast } from '@answer/hooks'; -import { useSmtpSetting, updateSmtpSetting } from '@answer/api'; +import { useSmtpSetting, updateSmtpSetting } from '@/services'; import pattern from '@/common/pattern'; const Smtp: FC = () => { diff --git a/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx b/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx index 28de25c5..4d419091 100644 --- a/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx +++ b/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx @@ -3,14 +3,14 @@ import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { changeEmail, checkImgCode } from '@answer/api'; import type { ImgCodeRes, PasswordResetReq, FormDataType, } from '@answer/common/interface'; -import { userInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@answer/stores'; +import { changeEmail, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@/components/Modal'; const Index: FC = () => { @@ -34,7 +34,7 @@ const Index: FC = () => { }); const [showModal, setModalState] = useState(false); const navigate = useNavigate(); - const { user: userInfo, update: updateUser } = userInfoStore(); + const { user: userInfo, update: updateUser } = loggedUserInfoStore(); const getImgCode = () => { checkImgCode({ diff --git a/ui/src/pages/Users/ConfirmNewEmail/index.tsx b/ui/src/pages/Users/ConfirmNewEmail/index.tsx index 41695a89..79b55ee7 100644 --- a/ui/src/pages/Users/ConfirmNewEmail/index.tsx +++ b/ui/src/pages/Users/ConfirmNewEmail/index.tsx @@ -3,9 +3,8 @@ import { Container, Row, Col } from 'react-bootstrap'; import { Link, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { changeEmailVerify, getUserInfo } from '@/services'; -import { userInfoStore } from '@answer/stores'; - +import { loggedUserInfoStore } from '@/stores'; +import { changeEmailVerify, getLoggedUserInfo } from '@/services'; import { PageTitle } from '@/components'; const Index: FC = () => { @@ -13,7 +12,7 @@ const Index: FC = () => { const [searchParams] = useSearchParams(); const [step, setStep] = useState('loading'); - const updateUser = userInfoStore((state) => state.update); + const updateUser = loggedUserInfoStore((state) => state.update); useEffect(() => { const code = searchParams.get('code'); @@ -22,7 +21,7 @@ const Index: FC = () => { changeEmailVerify({ code }) .then(() => { setStep('success'); - getUserInfo().then((res) => { + getLoggedUserInfo().then((res) => { // update user info updateUser(res); }); From d72425b343043abc42291df22c99111afe36ea19 Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Mon, 31 Oct 2022 18:40:02 +0800 Subject: [PATCH 05/22] fix(router-guard): fix some router guard logic --- ui/src/router/guarder.ts | 18 +++++++++++------- ui/src/utils/guards.ts | 34 ++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/ui/src/router/guarder.ts b/ui/src/router/guarder.ts index 8c5bf7c2..e3b81598 100644 --- a/ui/src/router/guarder.ts +++ b/ui/src/router/guarder.ts @@ -7,17 +7,12 @@ import { isLoggedAndInactive, isLoggedAndSuspended, isNotLoggedOrInactive, + isNotLoggedOrNotSuspend, } from '@/utils/guards'; const RouteGuarder = { base: async () => { - return isNotLoggedOrNormal(); - }, - notLogged: async () => { - return isNotLogged(); - }, - notLoggedOrInactive: async () => { - return isNotLoggedOrInactive(); + return isNotLoggedOrNotSuspend(); }, loggedAndNormal: async () => { await pullLoggedUser(true); @@ -33,6 +28,15 @@ const RouteGuarder = { await pullLoggedUser(true); return isAdminLogged(); }, + notLogged: async () => { + return isNotLogged(); + }, + notLoggedOrNormal: async () => { + return isNotLoggedOrNormal(); + }, + notLoggedOrInactive: async () => { + return isNotLoggedOrInactive(); + }, }; export default RouteGuarder; diff --git a/ui/src/utils/guards.ts b/ui/src/utils/guards.ts index f0faf55b..47f79605 100644 --- a/ui/src/utils/guards.ts +++ b/ui/src/utils/guards.ts @@ -94,21 +94,21 @@ export const isNotLogged = () => { }; export const isLoggedAndInactive = () => { - const ret: GuardResult = { ok: false, redirect: undefined }; + const ret: GuardResult = { ok: true, redirect: undefined }; const userStat = deriveUserStat(); - if (!userStat.isActivated) { - ret.ok = true; - ret.redirect = RouteAlias.activation; + if (userStat.isActivated) { + ret.ok = false; + ret.redirect = RouteAlias.home; } return ret; }; export const isLoggedAndSuspended = () => { - const ret: GuardResult = { ok: false, redirect: undefined }; + const ret: GuardResult = { ok: true, redirect: undefined }; const userStat = deriveUserStat(); - if (userStat.isSuspended) { - ret.redirect = RouteAlias.suspended; - ret.ok = true; + if (!userStat.isSuspended) { + ret.ok = false; + ret.redirect = RouteAlias.home; } return ret; }; @@ -120,7 +120,7 @@ export const isLoggedAndNormal = () => { ret.ok = true; } else if (!userStat.isActivated) { ret.redirect = RouteAlias.activation; - } else if (!userStat.isSuspended) { + } else if (userStat.isSuspended) { ret.redirect = RouteAlias.suspended; } else if (!userStat.isLogged) { ret.redirect = RouteAlias.login; @@ -139,12 +139,26 @@ export const isNotLoggedOrNormal = () => { return ret; }; +export const isNotLoggedOrNotSuspend = () => { + const ret: GuardResult = { ok: true, redirect: undefined }; + const userStat = deriveUserStat(); + const gr = isLoggedAndNormal(); + if (!gr.ok && userStat.isSuspended) { + ret.ok = false; + ret.redirect = gr.redirect; + } + return ret; +}; + export const isNotLoggedOrInactive = () => { const ret: GuardResult = { ok: true, redirect: undefined }; const userStat = deriveUserStat(); - if (userStat.isLogged || userStat.isActivated) { + if (userStat.isActivated) { ret.ok = false; ret.redirect = RouteAlias.home; + } else if (userStat.isSuspended) { + ret.ok = false; + ret.redirect = RouteAlias.suspended; } return ret; }; From 2ad6a40d764a89c333381caa9309b56aec815859 Mon Sep 17 00:00:00 2001 From: shuai Date: Mon, 31 Oct 2022 18:48:27 +0800 Subject: [PATCH 06/22] chore: add install pages --- ui/src/i18n/locales/en.json | 60 +++++++++++++++++++ .../Install/components/FifthStep/index.tsx | 33 ++++++++++ .../Install/components/FirstStep/index.tsx | 31 ++++++++++ .../Install/components/FourthStep/index.tsx | 53 ++++++++++++++++ .../Install/components/Progress/index.tsx | 22 +++++++ .../Install/components/SecondStep/index.tsx | 57 ++++++++++++++++++ .../Install/components/ThirdStep/index.tsx | 39 ++++++++++++ ui/src/pages/Install/components/index.ts | 7 +++ ui/src/pages/Install/index.tsx | 44 ++++++++++++++ ui/src/router/routes.ts | 4 ++ 10 files changed, 350 insertions(+) create mode 100644 ui/src/pages/Install/components/FifthStep/index.tsx create mode 100644 ui/src/pages/Install/components/FirstStep/index.tsx create mode 100644 ui/src/pages/Install/components/FourthStep/index.tsx create mode 100644 ui/src/pages/Install/components/Progress/index.tsx create mode 100644 ui/src/pages/Install/components/SecondStep/index.tsx create mode 100644 ui/src/pages/Install/components/ThirdStep/index.tsx create mode 100644 ui/src/pages/Install/components/index.ts create mode 100644 ui/src/pages/Install/index.tsx diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index 94277ee9..0bf94f19 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -732,6 +732,66 @@ "x_answers": "answers", "x_questions": "questions" }, + "install": { + "title": "Answer", + "next": "Next", + "done": "Done", + "choose_lang": { + "label": "Please choose a language" + }, + "database_engine": { + "label": "Database Engine" + }, + "username": { + "label": "Username", + "placeholder": "root" + }, + "password": { + "label": "Password", + "placeholder": "root" + }, + "database_host": { + "label": "Database Host", + "placeholder": "db:3306" + }, + "database_name": { + "label": "Database Name", + "placeholder": "answer" + }, + "table_prefix": { + "label": "Table Prefix (optional)", + "placeholder": "answer_" + }, + "config_yaml": { + "title": "Create config.yaml", + "label": "The config.yaml file created.", + "description": "You can create the <1>config.yaml file manually in the <1>/var/wwww/xxx/ directory and paste the following text into it.", + "info": "After you’ve done that, click “Next” button." + }, + "site_information": "Site Information", + "admin_account": "Admin Account", + "site_name": { + "label": "Site Name" + }, + "contact_email": { + "label": "Contact Email", + "text": "Email address of key contact responsible for this site." + }, + "admin_name": { + "label": "Name" + }, + "admin_password": { + "label": "Password", + "text": "You will need this password to log in. Please store it in a secure location." + }, + "admin_email": { + "label": "Email", + "text": "You will need this email to log in." + }, + "ready_title": "Your Answer is Ready!", + "ready_description": "If you ever feel like changing more settings, visit <1>admin section; find it in the site menu.", + "good_luck": "Have fun, and good luck!" + }, "page_404": { "description": "Unfortunately, this page doesn't exist.", "back_home": "Back to homepage" diff --git a/ui/src/pages/Install/components/FifthStep/index.tsx b/ui/src/pages/Install/components/FifthStep/index.tsx new file mode 100644 index 00000000..63f008e0 --- /dev/null +++ b/ui/src/pages/Install/components/FifthStep/index.tsx @@ -0,0 +1,33 @@ +import { FC } from 'react'; +import { Button } from 'react-bootstrap'; +import { useTranslation, Trans } from 'react-i18next'; + +import Progress from '../Progress'; + +interface Props { + visible: boolean; +} +const Index: FC = ({ visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + + if (!visible) return null; + return ( +
+
{t('ready_title')}
+

+ + If you ever feel like changing more settings, visit + admin section; find it in the site menu. + +

+

{t('good_luck')}

+ +
+ + +
+
+ ); +}; + +export default Index; diff --git a/ui/src/pages/Install/components/FirstStep/index.tsx b/ui/src/pages/Install/components/FirstStep/index.tsx new file mode 100644 index 00000000..156f568d --- /dev/null +++ b/ui/src/pages/Install/components/FirstStep/index.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react'; +import { Form, Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import Progress from '../Progress'; + +interface Props { + visible: boolean; +} +const Index: FC = ({ visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + + if (!visible) return null; + return ( + + + {t('choose_lang.label')} + + + + + +
+ + +
+ + ); +}; + +export default Index; diff --git a/ui/src/pages/Install/components/FourthStep/index.tsx b/ui/src/pages/Install/components/FourthStep/index.tsx new file mode 100644 index 00000000..25fb47ef --- /dev/null +++ b/ui/src/pages/Install/components/FourthStep/index.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react'; +import { Form, Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import Progress from '../Progress'; + +interface Props { + visible: boolean; +} +const Index: FC = ({ visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + + if (!visible) return null; + return ( +
+
{t('site_information')}
+ + {t('site_name.label')} + + + + {t('contact_email.label')} + + {t('contact_email.text')} + + +
{t('admin_account')}
+ + {t('admin_name.label')} + + + + + {t('admin_password.label')} + + {t('admin_password.text')} + + + + {t('admin_email.label')} + + {t('admin_email.text')} + + +
+ + +
+
+ ); +}; + +export default Index; diff --git a/ui/src/pages/Install/components/Progress/index.tsx b/ui/src/pages/Install/components/Progress/index.tsx new file mode 100644 index 00000000..97f33ffe --- /dev/null +++ b/ui/src/pages/Install/components/Progress/index.tsx @@ -0,0 +1,22 @@ +import { FC, memo } from 'react'; +import { ProgressBar } from 'react-bootstrap'; + +interface IProps { + step: number; +} + +const Index: FC = ({ step }) => { + return ( +
+ + {step}/5 +
+ ); +}; + +export default memo(Index); diff --git a/ui/src/pages/Install/components/SecondStep/index.tsx b/ui/src/pages/Install/components/SecondStep/index.tsx new file mode 100644 index 00000000..7b21ab73 --- /dev/null +++ b/ui/src/pages/Install/components/SecondStep/index.tsx @@ -0,0 +1,57 @@ +import { FC } from 'react'; +import { Form, Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import Progress from '../Progress'; + +interface Props { + visible: boolean; +} + +const Index: FC = ({ visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + + if (!visible) return null; + return ( +
+ + {t('database_engine.label')} + + + + + + + {t('username.label')} + + + + + {t('password.label')} + + + + + {t('database_host.label')} + + + + + {t('database_name.label')} + + + + + {t('table_prefix.label')} + + + +
+ + +
+
+ ); +}; + +export default Index; diff --git a/ui/src/pages/Install/components/ThirdStep/index.tsx b/ui/src/pages/Install/components/ThirdStep/index.tsx new file mode 100644 index 00000000..4d3f702f --- /dev/null +++ b/ui/src/pages/Install/components/ThirdStep/index.tsx @@ -0,0 +1,39 @@ +import { FC } from 'react'; +import { Form, Button, FormGroup } from 'react-bootstrap'; +import { useTranslation, Trans } from 'react-i18next'; + +import Progress from '../Progress'; + +interface Props { + visible: boolean; +} + +const Index: FC = ({ visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + + if (!visible) return null; + return ( +
+
{t('config_yaml.title')}
+
{t('config_yaml.label')}
+
+

+ }} + /> +

+
+ + + +
{t('config_yaml.info')}
+
+ + +
+
+ ); +}; + +export default Index; diff --git a/ui/src/pages/Install/components/index.ts b/ui/src/pages/Install/components/index.ts new file mode 100644 index 00000000..ecc22539 --- /dev/null +++ b/ui/src/pages/Install/components/index.ts @@ -0,0 +1,7 @@ +import FirstStep from './FirstStep'; +import SecondStep from './SecondStep'; +import ThirdStep from './ThirdStep'; +import FourthStep from './FourthStep'; +import Fifth from './FifthStep'; + +export { FirstStep, SecondStep, ThirdStep, FourthStep, Fifth }; diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx new file mode 100644 index 00000000..e1425893 --- /dev/null +++ b/ui/src/pages/Install/index.tsx @@ -0,0 +1,44 @@ +import { FC, useState } from 'react'; +import { Container, Row, Col, Card, Alert } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import { + FirstStep, + SecondStep, + ThirdStep, + FourthStep, + Fifth, +} from './components'; + +const Index: FC = () => { + const { t } = useTranslation('translation', { keyPrefix: 'install' }); + const [step] = useState(5); + + return ( +
+ + + +

{t('title')}

+ + + show error msg + + + + + + + + + + + + +
+
+
+ ); +}; + +export default Index; diff --git a/ui/src/router/routes.ts b/ui/src/router/routes.ts index 5b49fb1a..69498f5d 100644 --- a/ui/src/router/routes.ts +++ b/ui/src/router/routes.ts @@ -211,5 +211,9 @@ const routes: RouteNode[] = [ }, ], }, + { + path: 'install', + page: 'pages/Install', + }, ]; export default routes; From 05f48669092a8c7d8cc1580be1bf54010f8a0c7d Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Tue, 1 Nov 2022 10:04:08 +0800 Subject: [PATCH 07/22] fix(notifications): fix message disappear when reclick notification type --- ui/src/pages/Users/Notifications/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/pages/Users/Notifications/index.tsx b/ui/src/pages/Users/Notifications/index.tsx index 47e69a04..b24367b7 100644 --- a/ui/src/pages/Users/Notifications/index.tsx +++ b/ui/src/pages/Users/Notifications/index.tsx @@ -47,6 +47,9 @@ const Notifications = () => { const handleTypeChange = (evt, val) => { evt.preventDefault(); + if (type === val) { + return; + } setPage(1); setNotificationData([]); navigate(`/users/notifications/${val}`); From e4f08e138675beb97552a2c1e8f68ad2d94ddd06 Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Tue, 1 Nov 2022 10:36:19 +0800 Subject: [PATCH 08/22] fix(router): add general route error element --- ui/src/router/index.tsx | 2 ++ ui/src/utils/request.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ui/src/router/index.tsx b/ui/src/router/index.tsx index a7452ff8..84eb37a6 100644 --- a/ui/src/router/index.tsx +++ b/ui/src/router/index.tsx @@ -2,6 +2,7 @@ import React, { Suspense, lazy } from 'react'; import { RouteObject, createBrowserRouter, redirect } from 'react-router-dom'; import Layout from '@/pages/Layout'; +import ErrorBoundary from '@/pages/50X'; import baseRoutes, { RouteNode } from '@/router/routes'; import { floppyNavigation } from '@/utils'; @@ -11,6 +12,7 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteObject[]) => { routeNodes.forEach((rn) => { if (rn.path === '/') { rn.element = ; + rn.errorElement = ; } else { /** * cannot use a fully dynamic import statement diff --git a/ui/src/utils/request.ts b/ui/src/utils/request.ts index c9f9e67f..d80cadf5 100644 --- a/ui/src/utils/request.ts +++ b/ui/src/utils/request.ts @@ -132,11 +132,12 @@ class Request { return Promise.reject(false); } } - - toastStore.getState().show({ - msg: `statusCode: ${status}; ${respMsg || ''}`, - variant: 'danger', - }); + if (respMsg) { + toastStore.getState().show({ + msg: `statusCode: ${status}; ${respMsg || ''}`, + variant: 'danger', + }); + } return Promise.reject(false); }, ); From a741bd5cfc40df72ed6b7623016b4b425e086a72 Mon Sep 17 00:00:00 2001 From: shuai Date: Tue, 1 Nov 2022 11:34:39 +0800 Subject: [PATCH 09/22] fix: add upgrad and maintenance page --- ui/src/i18n/locales/en.json | 23 +++++++++++-- ui/src/index.scss | 3 ++ ui/src/pages/Install/index.tsx | 27 ++++++++++++++-- ui/src/pages/Maintenance/index.tsx | 23 +++++++++++++ ui/src/pages/Upgrade/index.tsx | 52 ++++++++++++++++++++++++++++++ ui/src/router/routes.ts | 8 +++++ 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 ui/src/pages/Maintenance/index.tsx create mode 100644 ui/src/pages/Upgrade/index.tsx diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index 0bf94f19..ecbb7b7c 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -28,7 +28,10 @@ "confirm_email": "Confirm Email", "account_suspended": "Account Suspended", "admin": "Admin", - "change_email": "Modify Email" + "change_email": "Modify Email", + "install": "Answer Installation", + "upgrade": "Answer Upgrade", + "maintenance": "Webite Maintenance" }, "notifications": { "title": "Notifications", @@ -790,7 +793,20 @@ }, "ready_title": "Your Answer is Ready!", "ready_description": "If you ever feel like changing more settings, visit <1>admin section; find it in the site menu.", - "good_luck": "Have fun, and good luck!" + "good_luck": "Have fun, and good luck!", + "warning": "Warning", + "warning_description": "The file <1>config.yaml already exists. If you need to reset any of the configuration items in this file, please delete it first. You may try <2>installing now.", + "installed": "Already installed", + "installed_description": "You appear to have already installed. To reinstall please clear your old database tables first." + }, + "upgrade": { + "title": "Answer", + "update_btn": "Update data", + "update_title": "Data update required", + "update_description": "<1>Answer has been updated! Before you continue, we have to update your data to the newest version.<1>The update process may take a little while, so please be patient.", + "done_title": "No update required", + "done_btn": "Done", + "done_desscription": "Your Answer data is already up-to-date." }, "page_404": { "description": "Unfortunately, this page doesn't exist.", @@ -800,6 +816,9 @@ "description": "The server encountered an error and could not complete your request.", "back_home": "Back to homepage" }, + "page_maintenance": { + "description": "We are under maintenance, we’ll be back soon." + }, "admin": { "admin_header": { "title": "Admin" diff --git a/ui/src/index.scss b/ui/src/index.scss index e81a900c..dd25289f 100644 --- a/ui/src/index.scss +++ b/ui/src/index.scss @@ -77,6 +77,9 @@ a { .page-wrap { min-height: calc(100vh - 148px); } +.page-wrap2 { + min-height: 100vh; +} .btn-no-border, .btn-no-border:hover, diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx index e1425893..8de7cb11 100644 --- a/ui/src/pages/Install/index.tsx +++ b/ui/src/pages/Install/index.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react'; import { Container, Row, Col, Card, Alert } from 'react-bootstrap'; -import { useTranslation } from 'react-i18next'; +import { useTranslation, Trans } from 'react-i18next'; import { FirstStep, @@ -10,12 +10,15 @@ import { Fifth, } from './components'; +import { PageTitle } from '@/components'; + const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); - const [step] = useState(5); + const [step] = useState(7); return (
+ @@ -32,6 +35,26 @@ const Index: FC = () => { + {step === 6 && ( +
+
{t('warning')}
+

+ + The file config.yaml already exists. If you + need to reset any of the configuration items in this + file, please delete it first. You may try{' '} + installing now. + +

+
+ )} + + {step === 7 && ( +
+
{t('installed')}
+

{t('installed_description')}

+
+ )} diff --git a/ui/src/pages/Maintenance/index.tsx b/ui/src/pages/Maintenance/index.tsx new file mode 100644 index 00000000..3a2c2d86 --- /dev/null +++ b/ui/src/pages/Maintenance/index.tsx @@ -0,0 +1,23 @@ +import { Container } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import { PageTitle } from '@/components'; + +const Index = () => { + const { t } = useTranslation('translation', { + keyPrefix: 'page_maintenance', + }); + return ( + + +
+ (=‘_‘=) +
+
{t('description')}
+
+ ); +}; + +export default Index; diff --git a/ui/src/pages/Upgrade/index.tsx b/ui/src/pages/Upgrade/index.tsx new file mode 100644 index 00000000..aee2a37a --- /dev/null +++ b/ui/src/pages/Upgrade/index.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import { Container, Row, Col, Card, Button } from 'react-bootstrap'; +import { useTranslation, Trans } from 'react-i18next'; + +import { PageTitle } from '@/components'; + +const Index = () => { + const { t } = useTranslation('translation', { + keyPrefix: 'upgrade', + }); + const [step, setStep] = useState(1); + + const handleUpdate = () => { + setStep(2); + }; + return ( + + + + +

{t('title')}

+ + + {step === 1 && ( + <> +
{t('update_title')}
+ }} + /> + + + )} + + {step === 2 && ( + <> +
{t('done_title')}
+

{t('done_desscription')}

+ + + )} +
+
+ +
+
+ ); +}; + +export default Index; diff --git a/ui/src/router/routes.ts b/ui/src/router/routes.ts index 69498f5d..9436298a 100644 --- a/ui/src/router/routes.ts +++ b/ui/src/router/routes.ts @@ -215,5 +215,13 @@ const routes: RouteNode[] = [ path: 'install', page: 'pages/Install', }, + { + path: '/maintenance', + page: 'pages/Maintenance', + }, + { + path: '/upgrade', + page: 'pages/Upgrade', + }, ]; export default routes; From 2ff08c8b43abc66a163449abc258212214510dfd Mon Sep 17 00:00:00 2001 From: robin Date: Tue, 1 Nov 2022 16:26:42 +0800 Subject: [PATCH 10/22] feat(admin): question,answer,users adds filter --- ui/src/common/interface.ts | 2 ++ ui/src/pages/Admin/Answers/index.tsx | 14 +++++++++++--- ui/src/pages/Admin/Questions/index.tsx | 14 +++++++++++--- ui/src/pages/Admin/Users/index.tsx | 20 ++++++++++++-------- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 8dbc8b0d..7516a2d7 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -228,6 +228,7 @@ export type AdminContentsFilterBy = 'normal' | 'closed' | 'deleted'; export interface AdminContentsReq extends Paging { status: AdminContentsFilterBy; + query?: string; } /** @@ -263,6 +264,7 @@ export interface AdminSettingsInterface { logo: string; language: string; theme: string; + time_zone: string; } export interface AdminSettingsSmtp { diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx index 2a25f53c..89a0ae26 100644 --- a/ui/src/pages/Admin/Answers/index.tsx +++ b/ui/src/pages/Admin/Answers/index.tsx @@ -23,10 +23,11 @@ import '../index.scss'; const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted']; const Answers: FC = () => { - const [urlSearchParams] = useSearchParams(); + const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const curFilter = urlSearchParams.get('status') || answerFilterItems[0]; const PAGE_SIZE = 20; const curPage = Number(urlSearchParams.get('page')) || 1; + const curQuery = urlSearchParams.get('query') || ''; const { t } = useTranslation('translation', { keyPrefix: 'admin.answers' }); const { @@ -37,6 +38,7 @@ const Answers: FC = () => { page_size: PAGE_SIZE, page: curPage, status: curFilter as Type.AdminContentsFilterBy, + query: curQuery, }); const count = listData?.count || 0; @@ -78,6 +80,11 @@ const Answers: FC = () => { }); }; + const handleFilter = (e) => { + urlSearchParams.set('query', e.target.value); + urlSearchParams.delete('page'); + setUrlSearchParams(urlSearchParams); + }; return ( <>

{t('page_title')}

@@ -90,10 +97,11 @@ const Answers: FC = () => { />
diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx index 1a188512..c939bc06 100644 --- a/ui/src/pages/Admin/Questions/index.tsx +++ b/ui/src/pages/Admin/Questions/index.tsx @@ -32,9 +32,10 @@ const questionFilterItems: Type.AdminContentsFilterBy[] = [ const PAGE_SIZE = 20; const Questions: FC = () => { - const [urlSearchParams] = useSearchParams(); + const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const curFilter = urlSearchParams.get('status') || questionFilterItems[0]; const curPage = Number(urlSearchParams.get('page')) || 1; + const curQuery = urlSearchParams.get('query') || ''; const { t } = useTranslation('translation', { keyPrefix: 'admin.questions' }); const { @@ -45,6 +46,7 @@ const Questions: FC = () => { page_size: PAGE_SIZE, page: curPage, status: curFilter as Type.AdminContentsFilterBy, + query: curQuery, }); const count = listData?.count || 0; @@ -97,6 +99,11 @@ const Questions: FC = () => { }); }; + const handleFilter = (e) => { + urlSearchParams.set('query', e.target.value); + urlSearchParams.delete('page'); + setUrlSearchParams(urlSearchParams); + }; return ( <>

{t('page_title')}

@@ -109,10 +116,11 @@ const Questions: FC = () => { /> diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index de434084..4c129930 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react'; +import { FC } from 'react'; import { Button, Form, Table, Badge } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -34,11 +34,11 @@ const bgMap = { const PAGE_SIZE = 10; const Users: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); - const [userName, setUserName] = useState(''); - const [urlSearchParams] = useSearchParams(); + const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const curFilter = urlSearchParams.get('filter') || UserFilterKeys[0]; const curPage = Number(urlSearchParams.get('page') || '1'); + const curQuery = urlSearchParams.get('query') || ''; const { data, isLoading, @@ -46,7 +46,7 @@ const Users: FC = () => { } = useQueryUsers({ page: curPage, page_size: PAGE_SIZE, - ...(userName ? { username: userName } : {}), + query: curQuery, ...(curFilter === 'all' ? {} : { status: curFilter }), }); const changeModal = useChangeModal({ @@ -60,6 +60,11 @@ const Users: FC = () => { }); }; + const handleFilter = (e) => { + urlSearchParams.set('query', e.target.value); + urlSearchParams.delete('page'); + setUrlSearchParams(urlSearchParams); + }; return ( <>

{t('title')}

@@ -72,11 +77,10 @@ const Users: FC = () => { /> setUserName(e.target.value)} - placeholder="Filter by name" + value={curQuery} + onChange={handleFilter} + placeholder={t('filter.placeholder')} style={{ width: '12.25rem' }} /> From e45ecdc0ef9904eabb256f86ed6e95b3d9867527 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 11:12:05 +0800 Subject: [PATCH 11/22] refactor: Answer links for improved question management --- ui/src/pages/Admin/Answers/index.tsx | 9 ++++++--- ui/src/services/admin/answer.ts | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx index 89a0ae26..aa2c47d4 100644 --- a/ui/src/pages/Admin/Answers/index.tsx +++ b/ui/src/pages/Admin/Answers/index.tsx @@ -28,6 +28,7 @@ const Answers: FC = () => { const PAGE_SIZE = 20; const curPage = Number(urlSearchParams.get('page')) || 1; const curQuery = urlSearchParams.get('query') || ''; + const questionId = urlSearchParams.get('questionId') || ''; const { t } = useTranslation('translation', { keyPrefix: 'admin.answers' }); const { @@ -39,6 +40,7 @@ const Answers: FC = () => { page: curPage, status: curFilter as Type.AdminContentsFilterBy, query: curQuery, + question_id: questionId, }); const count = listData?.count || 0; @@ -105,12 +107,12 @@ const Answers: FC = () => { style={{ width: '12.25rem' }} /> - +
- + - + {curFilter !== 'deleted' && } @@ -141,6 +143,7 @@ const Answers: FC = () => { __html: li.description, }} className="last-p text-truncate-2 fs-14" + style={{ maxWidth: '30rem' }} /> diff --git a/ui/src/services/admin/answer.ts b/ui/src/services/admin/answer.ts index 6fd0fbb6..cd6bc824 100644 --- a/ui/src/services/admin/answer.ts +++ b/ui/src/services/admin/answer.ts @@ -4,7 +4,9 @@ import qs from 'qs'; import request from '@answer/utils/request'; import type * as Type from '@answer/common/interface'; -export const useAnswerSearch = (params: Type.AdminContentsReq) => { +export const useAnswerSearch = ( + params: Type.AdminContentsReq & { question_id?: string }, +) => { const apiUrl = `/answer/admin/api/answer/page?${qs.stringify(params)}`; const { data, error, mutate } = useSWR( [apiUrl], From f6545f2de3732cc583af0726e9dbb6ad0ded8871 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 11:12:57 +0800 Subject: [PATCH 12/22] refactor: update en.json --- ui/src/i18n/locales/en.json | 45 +++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index 94277ee9..a7d94f6d 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -759,7 +759,30 @@ "dashboard": { "title": "Dashboard", "welcome": "Welcome to Answer Admin !", - "version": "Version" + "site_statistics": "Site Statistics", + "questions": "Questions:", + "answers": "Answers:", + "comments": "Comments:", + "votes": "Votes:", + "active_users": "Active users:", + "flags": "Flags:", + "site_health_status": "Site Health Status", + "version": "Version:", + "https": "HTTPS:", + "uploading_files": "Uploading files:", + "smtp": "SMTP:", + "timezone": "Timezone:", + "system_info": "System Info", + "storage_used": "Storage used:", + "uptime": "Uptime:", + "answer_links": "Answer Links", + "documents": "Documents", + "feedback": "Feedback", + "review": "Review", + "config": "Config", + "update_to": "Update to", + "latest": "Latest", + "check_failed": "Check failed" }, "flags": { "title": "Flags", @@ -816,7 +839,10 @@ "inactive": "Inactive", "suspended": "Suspended", "deleted": "Deleted", - "normal": "Normal" + "normal": "Normal", + "filter": { + "placeholder": "Filter by name, user:id" + } }, "questions": { "page_title": "Questions", @@ -829,7 +855,10 @@ "created": "Created", "status": "Status", "action": "Action", - "change": "Change" + "change": "Change", + "filter": { + "placeholder": "Filter by title, question:id" + } }, "answers": { "page_title": "Answers", @@ -840,7 +869,10 @@ "created": "Created", "status": "Status", "action": "Action", - "change": "Change" + "change": "Change", + "filter": { + "placeholder": "Filter by title, answer:id" + } }, "general": { "page_title": "General", @@ -876,6 +908,11 @@ "label": "Interface Language", "msg": "Interface language cannot be empty.", "text": "User interface language. It will change when you refresh the page." + }, + "timezone": { + "label": "Timezone", + "msg": "Timezone cannot be empty.", + "text": "Choose a UTC (Coordinated Universal Time) time offset." } }, "smtp": { From 5a3bd799f772ed5a4bcd60c448925482dc282d33 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 11:24:12 +0800 Subject: [PATCH 13/22] refactor: refactor: Answer links for improved question management --- ui/src/pages/Admin/Questions/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx index c939bc06..c3111001 100644 --- a/ui/src/pages/Admin/Questions/index.tsx +++ b/ui/src/pages/Admin/Questions/index.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { Button, Form, Table, Stack, Badge } from 'react-bootstrap'; -import { useSearchParams } from 'react-router-dom'; +import { Link, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { @@ -156,12 +156,11 @@ const Questions: FC = () => { -

{t('title')}

- - - {step === 1 && ( - <> -
{t('update_title')}
- }} - /> - - - )} +
+ + + +
+

{t('title')}

+ + + {step === 1 && ( + <> +
{t('update_title')}
+ }} + /> + + + )} - {step === 2 && ( - <> -
{t('done_title')}
-

{t('done_desscription')}

- - - )} -
-
- - - + {step === 2 && ( + <> +
{t('done_title')}
+

{t('done_desscription')}

+ + + )} + + + + + + ); }; diff --git a/ui/src/pages/Users/Personal/components/DefaultList/index.tsx b/ui/src/pages/Users/Personal/components/DefaultList/index.tsx index 980a2df3..e249067f 100644 --- a/ui/src/pages/Users/Personal/components/DefaultList/index.tsx +++ b/ui/src/pages/Users/Personal/components/DefaultList/index.tsx @@ -34,7 +34,7 @@ const Index: FC = ({ visible, tabName, data }) => { : null} -
+
{tabName === 'bookmarks' && ( <> diff --git a/ui/src/pages/Users/Personal/components/NavBar/index.tsx b/ui/src/pages/Users/Personal/components/NavBar/index.tsx index 75fe68ee..4c98ce50 100644 --- a/ui/src/pages/Users/Personal/components/NavBar/index.tsx +++ b/ui/src/pages/Users/Personal/components/NavBar/index.tsx @@ -44,7 +44,10 @@ const list = [ const Index: FC = ({ slug, tabName = 'overview', isSelf }) => { const { t } = useTranslation('translation', { keyPrefix: 'personal' }); return ( -
+ From 847c9a343e139bdb3e4504b14e8193fd789c43ad Mon Sep 17 00:00:00 2001 From: shuai Date: Wed, 2 Nov 2022 17:33:04 +0800 Subject: [PATCH 18/22] fix: path alias --- ui/src/pages/Install/components/FirstStep/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ui/src/pages/Install/components/FirstStep/index.tsx b/ui/src/pages/Install/components/FirstStep/index.tsx index 2a2226d6..5690ca8e 100644 --- a/ui/src/pages/Install/components/FirstStep/index.tsx +++ b/ui/src/pages/Install/components/FirstStep/index.tsx @@ -2,13 +2,8 @@ import { FC, useEffect, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type { - LangsType, - FormValue, - FormDataType, -} from '@answer/common/interface'; +import type { LangsType, FormValue, FormDataType } from '@/common/interface'; import Progress from '../Progress'; - import { languages } from '@/services'; interface Props { From 44da3c24fd806c1536efe39448f7ccffce807cad Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 18:16:27 +0800 Subject: [PATCH 19/22] feat(admin): dashboard module --- ui/src/common/interface.ts | 18 +++++++ .../components/AnswerLinks/index.tsx | 31 +++++++++++ .../components/HealthStatus/index.tsx | 54 +++++++++++++++++++ .../Dashboard/components/Statistics/index.tsx | 51 ++++++++++++++++++ .../Dashboard/components/SystemInfo/index.tsx | 34 ++++++++++++ .../pages/Admin/Dashboard/components/index.ts | 6 +++ ui/src/pages/Admin/Dashboard/index.tsx | 29 ++++++++++ ui/src/services/admin/settings.ts | 13 +++++ ui/src/utils/common.ts | 16 ++++++ 9 files changed, 252 insertions(+) create mode 100644 ui/src/pages/Admin/Dashboard/components/AnswerLinks/index.tsx create mode 100644 ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx create mode 100644 ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx create mode 100644 ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx create mode 100644 ui/src/pages/Admin/Dashboard/components/index.ts diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 732964d1..98f7c046 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -323,3 +323,21 @@ export interface SearchResItem { export interface SearchRes extends ListResult { extra: any; } + +export interface AdminDashboard { + info: { + question_count: number; + answer_count: number; + comment_count: number; + vote_count: number; + user_count: number; + report_count: number; + uploading_files: boolean; + smtp: boolean; + time_zone: string; + occupying_storage_space: string; + app_start_time: number; + app_version: string; + https: boolean; + }; +} diff --git a/ui/src/pages/Admin/Dashboard/components/AnswerLinks/index.tsx b/ui/src/pages/Admin/Dashboard/components/AnswerLinks/index.tsx new file mode 100644 index 00000000..8f5048f8 --- /dev/null +++ b/ui/src/pages/Admin/Dashboard/components/AnswerLinks/index.tsx @@ -0,0 +1,31 @@ +import { Card, Row, Col } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +const AnswerLinks = () => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); + + return ( + + +
{t('answer_links')}
+ +
+ + {t('documents')} + + + + + {t('feedback')} + + + + + + ); +}; + +export default AnswerLinks; diff --git a/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx b/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx new file mode 100644 index 00000000..72c3b9a6 --- /dev/null +++ b/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx @@ -0,0 +1,54 @@ +import { FC } from 'react'; +import { Card, Row, Col, Badge } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; + +import type * as Type from '@answer/common/interface'; + +interface IProps { + data: Type.AdminDashboard['info']; +} + +const HealthStatus: FC = ({ data }) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); + + return ( + + +
{t('site_health_status')}
+ +
+ {t('version')} + 90 + + {t('update_to')} {data.app_version} + + + + {t('https')} + {data.https ? t('yes') : t('yes')} + + + {t('uploading_files')} + + {data.uploading_files ? t('allowed') : t('not_allowed')} + + + + {t('smtp')} + {data.smtp ? t('enabled') : t('disabled')} + + {t('config')} + + + + {t('timezone')} + {data.time_zone} + + + + + ); +}; + +export default HealthStatus; diff --git a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx new file mode 100644 index 00000000..43e1fa2d --- /dev/null +++ b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react'; +import { Card, Row, Col } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import type * as Type from '@answer/common/interface'; + +interface IProps { + data: Type.AdminDashboard['info']; +} +const Statistics: FC = ({ data }) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); + + return ( + + +
{t('site_statistics')}
+ +
+ {t('questions')} + {data.question_count} + + + {t('answers')} + {data.answer_count} + + + {t('comments')} + {data.comment_count} + + + {t('votes')} + {data.vote_count} + + + {t('active_users')} + {data.user_count} + + + {t('flags')} + {data.report_count} + + {t('review')} + + + + + + ); +}; + +export default Statistics; diff --git a/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx b/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx new file mode 100644 index 00000000..2bf222f5 --- /dev/null +++ b/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { Card, Row, Col } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import type * as Type from '@answer/common/interface'; + +import { formatUptime } from '@/utils'; + +interface IProps { + data: Type.AdminDashboard['info']; +} +const SystemInfo: FC = ({ data }) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); + + return ( + + +
{t('system_info')}
+ +
+ {t('storage_used')} + {data.occupying_storage_space} + + + {t('uptime')} + {formatUptime(data.app_start_time)} + + + + + ); +}; + +export default SystemInfo; diff --git a/ui/src/pages/Admin/Dashboard/components/index.ts b/ui/src/pages/Admin/Dashboard/components/index.ts new file mode 100644 index 00000000..877f643f --- /dev/null +++ b/ui/src/pages/Admin/Dashboard/components/index.ts @@ -0,0 +1,6 @@ +import SystemInfo from './SystemInfo'; +import Statistics from './Statistics'; +import AnswerLinks from './AnswerLinks'; +import HealthStatus from './HealthStatus'; + +export { SystemInfo, Statistics, AnswerLinks, HealthStatus }; diff --git a/ui/src/pages/Admin/Dashboard/index.tsx b/ui/src/pages/Admin/Dashboard/index.tsx index 45c19721..47393831 100644 --- a/ui/src/pages/Admin/Dashboard/index.tsx +++ b/ui/src/pages/Admin/Dashboard/index.tsx @@ -1,12 +1,41 @@ import { FC } from 'react'; +import { Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; +import { + AnswerLinks, + HealthStatus, + Statistics, + SystemInfo, +} from './components'; + +import { useDashBoard } from '@/services'; + const Dashboard: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); + const { data } = useDashBoard(); + + if (!data) { + return null; + } return ( <>

{t('title')}

{t('welcome')}

+ + + + + + + + + + + + + + {process.env.REACT_APP_VERSION && (

{`${t('version')} `} diff --git a/ui/src/services/admin/settings.ts b/ui/src/services/admin/settings.ts index 274e24eb..5f370260 100644 --- a/ui/src/services/admin/settings.ts +++ b/ui/src/services/admin/settings.ts @@ -70,3 +70,16 @@ export const updateSmtpSetting = (params: Type.AdminSettingsSmtp) => { const apiUrl = `/answer/admin/api/setting/smtp`; return request.put(apiUrl, params); }; + +export const useDashBoard = () => { + const apiUrl = `/answer/admin/api/dashboard`; + const { data, error } = useSWR( + [apiUrl], + request.instance.get, + ); + return { + data, + isLoading: !data && !error, + error, + }; +}; diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts index 169dc1ad..dcc2d084 100644 --- a/ui/src/utils/common.ts +++ b/ui/src/utils/common.ts @@ -1,3 +1,5 @@ +import i18next from 'i18next'; + function getQueryString(name: string): string { const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`); const r = window.location.search.substr(1).match(reg); @@ -70,6 +72,19 @@ function parseUserInfo(markdown) { return markdown.replace(globalReg, '[@$1](/u/$1)'); } +function formatUptime(value) { + const t = i18next.t.bind(i18next); + const second = parseInt(value, 10); + + if (second > 60 * 60 && second < 60 * 60 * 24) { + return `${Math.floor(second / 3600)} ${t('dates.hour')}`; + } + if (second > 60 * 60 * 24) { + return `${Math.floor(second / 3600 / 24)} ${t('dates.day')}`; + } + + return `< 1 ${t('dates.hour')}`; +} export { getQueryString, thousandthDivision, @@ -77,4 +92,5 @@ export { scrollTop, matchedUsers, parseUserInfo, + formatUptime, }; From 364a5a0e189b9defd0981fd7eba2a889cc5b565a Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 18:16:54 +0800 Subject: [PATCH 20/22] refactor(i18n): update en.json --- ui/src/i18n/locales/en.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index ec9012b2..8bc92db1 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -293,7 +293,9 @@ "now": "now", "x_seconds_ago": "{{count}}s ago", "x_minutes_ago": "{{count}}m ago", - "x_hours_ago": "{{count}}h ago" + "x_hours_ago": "{{count}}h ago", + "hour": "hour", + "day": "day" }, "comment": { "btn_add_comment": "Add comment", @@ -866,7 +868,13 @@ "config": "Config", "update_to": "Update to", "latest": "Latest", - "check_failed": "Check failed" + "check_failed": "Check failed", + "yes": "Yes", + "no": "No", + "not_allowed": "Not allowed", + "allowed": "Allowed", + "enabled": "Enabled", + "disabled": "Disabled" }, "flags": { "title": "Flags", From 41ff3dbbc798a0e7ca5717022204811ab0ba1111 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 18:19:05 +0800 Subject: [PATCH 21/22] refactor: Modify the import package path --- ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx | 2 +- ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx | 2 +- ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx b/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx index 72c3b9a6..1b1c7ced 100644 --- a/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx +++ b/ui/src/pages/Admin/Dashboard/components/HealthStatus/index.tsx @@ -3,7 +3,7 @@ import { Card, Row, Col, Badge } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import type * as Type from '@answer/common/interface'; +import type * as Type from '@/common/interface'; interface IProps { data: Type.AdminDashboard['info']; diff --git a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx index 43e1fa2d..9e6c979e 100644 --- a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx +++ b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { Card, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; +import type * as Type from '@/common/interface'; interface IProps { data: Type.AdminDashboard['info']; diff --git a/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx b/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx index 2bf222f5..cbc065c7 100644 --- a/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx +++ b/ui/src/pages/Admin/Dashboard/components/SystemInfo/index.tsx @@ -2,8 +2,7 @@ import { FC } from 'react'; import { Card, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; - +import type * as Type from '@/common/interface'; import { formatUptime } from '@/utils'; interface IProps { From 549f816d3e6a62749353fbdab5ceabec06759547 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 18:24:26 +0800 Subject: [PATCH 22/22] style: format --- ui/src/pages/Admin/Dashboard/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Admin/Dashboard/index.tsx b/ui/src/pages/Admin/Dashboard/index.tsx index 47393831..2037016e 100644 --- a/ui/src/pages/Admin/Dashboard/index.tsx +++ b/ui/src/pages/Admin/Dashboard/index.tsx @@ -2,6 +2,8 @@ import { FC } from 'react'; import { Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; +import { useDashBoard } from '@/services'; + import { AnswerLinks, HealthStatus, @@ -9,8 +11,6 @@ import { SystemInfo, } from './components'; -import { useDashBoard } from '@/services'; - const Dashboard: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' }); const { data } = useDashBoard();

{t('post')}{t('post')} {t('votes')}{t('created')}{t('created')} {t('status')}{t('action')}
{li.vote_count} - {li.answer_count} - + From 50fa034095d8fc356c77a8bc6ee4721c7ad9a47a Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 2 Nov 2022 16:38:41 +0800 Subject: [PATCH 14/22] feat(admin): Add time zone in admin background --- ui/src/common/constants.ts | 226 +++++++++++++++++++++++++ ui/src/pages/Admin/Interface/index.tsx | 34 +++- 2 files changed, 258 insertions(+), 2 deletions(-) diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts index ba74f143..b700ad54 100644 --- a/ui/src/common/constants.ts +++ b/ui/src/common/constants.ts @@ -56,3 +56,229 @@ export const ADMIN_NAV_MENUS = [ child: [{ name: 'general' }, { name: 'interface' }, { name: 'smtp' }], }, ]; +// timezones +export const TIMEZONES = [ + { + label: 'UTC-12', + value: 'UTC-12', + }, + { + label: 'UTC-11:30', + value: 'UTC-11.5', + }, + { + label: 'UTC-11', + value: 'UTC-11', + }, + { + label: 'UTC-10:30', + value: 'UTC-10.5', + }, + { + label: 'UTC-10', + value: 'UTC-10', + }, + { + label: 'UTC-9:30', + value: 'UTC-9.5', + }, + { + label: 'UTC-9', + value: 'UTC-9', + }, + { + label: 'UTC-8:30', + value: 'UTC-8.5', + }, + { + label: 'UTC-8', + value: 'UTC-8', + }, + { + label: 'UTC-7:30', + value: 'UTC-7.5', + }, + { + label: 'UTC-7', + value: 'UTC-7', + }, + { + label: 'UTC-6:30', + value: 'UTC-6.5', + }, + { + label: 'UTC-6', + value: 'UTC-6', + }, + { + label: 'UTC-5:30', + value: 'UTC-5.5', + }, + { + label: 'UTC-5', + value: 'UTC-5', + }, + { + label: 'UTC-4:30', + value: 'UTC-4.5', + }, + { + label: 'UTC-4', + value: 'UTC-4', + }, + { + label: 'UTC-3:30', + value: 'UTC-3.5', + }, + { + label: 'UTC-3', + value: 'UTC-3', + }, + { + label: 'UTC-2:30', + value: 'UTC-2.5', + }, + { + label: 'UTC-2', + value: 'UTC-2', + }, + { + label: 'UTC-1:30', + value: 'UTC-1.5', + }, + { + label: 'UTC-1', + value: 'UTC-1', + }, + { + label: 'UTC-0:30', + value: 'UTC-0.5', + }, + { + label: 'UTC+0', + value: 'UTC+0', + }, + { + label: 'UTC+0:30', + value: 'UTC+0.5', + }, + { + label: 'UTC+1', + value: 'UTC+1', + }, + { + label: 'UTC+1:30', + value: 'UTC+1.5', + }, + { + label: 'UTC+2', + value: 'UTC+2', + }, + { + label: 'UTC+2:30', + value: 'UTC+2.5', + }, + { + label: 'UTC+3', + value: 'UTC+3', + }, + { + label: 'UTC+3:30', + + value: 'UTC+3.5', + }, + { + label: 'UTC+4', + value: 'UTC+4', + }, + { + label: 'UTC+4:30', + value: 'UTC+4.5', + }, + { + label: 'UTC+5', + value: 'UTC+5', + }, + { + label: 'UTC+5:30', + value: 'UTC+5.5', + }, + { + label: 'UTC+5:45', + value: 'UTC+5.75', + }, + { + label: 'UTC+6', + value: 'UTC+6', + }, + { + label: 'UTC+6:30', + + value: 'UTC+6.5', + }, + { + label: 'UTC+7', + value: 'UTC+7', + }, + { + label: 'UTC+7:30', + value: 'UTC+7.5', + }, + { + label: 'UTC+8', + value: 'UTC+8', + }, + { + label: 'UTC+8:30', + value: 'UTC+8.5', + }, + { + label: 'UTC+8:45', + value: 'UTC+8.75', + }, + { + label: 'UTC+9', + value: 'UTC+9', + }, + { + label: 'UTC+9:30', + value: 'UTC+9.5', + }, + { + label: 'UTC+10', + value: 'UTC+10', + }, + { + label: 'UTC+10:30', + value: 'UTC+10.5', + }, + { + label: 'UTC+11', + value: 'UTC+11', + }, + { + label: 'UTC+11:30', + value: 'UTC+11.5', + }, + { + label: 'UTC+12', + value: 'UTC+12', + }, + { + label: 'UTC+12:45', + value: 'UTC+12.75', + }, + { + label: 'UTC+13', + value: 'UTC+13', + }, + { + label: 'UTC+13:45', + value: 'UTC+13.75', + }, + { + label: 'UTC+14', + value: 'UTC+14', + }, +]; +export const DEFAULT_TIMEZONE = 'UTC+0'; diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx index e5d68f32..deebcf9f 100644 --- a/ui/src/pages/Admin/Interface/index.tsx +++ b/ui/src/pages/Admin/Interface/index.tsx @@ -10,6 +10,7 @@ import { } from '@answer/common/interface'; import { interfaceStore } from '@answer/stores'; import { UploadImg } from '@answer/components'; +import { TIMEZONES, DEFAULT_TIMEZONE } from '@answer/common/constants'; import { languages, @@ -28,6 +29,7 @@ const Interface: FC = () => { const Toast = useToast(); const [langs, setLangs] = useState(); const { data: setting } = useInterfaceSetting(); + const [formData, setFormData] = useState({ logo: { value: setting?.logo || '', @@ -44,6 +46,11 @@ const Interface: FC = () => { isInvalid: false, errorMsg: '', }, + time_zone: { + value: setting?.time_zone || DEFAULT_TIMEZONE, + isInvalid: false, + errorMsg: '', + }, }); const getLangs = async () => { const res: LangsType[] = await languages(); @@ -107,6 +114,7 @@ const Interface: FC = () => { logo: formData.logo.value, theme: formData.theme.value, language: formData.language.value, + time_zone: formData.time_zone.value, }; updateInterfaceSetting(reqParams) @@ -159,12 +167,14 @@ const Interface: FC = () => { Object.keys(setting).forEach((k) => { formMeta[k] = { ...formData[k], value: setting[k] }; }); - setFormData(formMeta); + setFormData({ ...formData, ...formMeta }); } }, [setting]); useEffect(() => { getLangs(); }, []); + + console.log('formData', formData); return ( <>

{t('page_title')}

@@ -250,7 +260,27 @@ const Interface: FC = () => { {formData.language.errorMsg} - + + {t('time_zone.label')} + { + onChange('time_zone', evt.target.value); + }}> + {TIMEZONES?.map((item) => { + return ( + + ); + })} + + {t('time_zone.text')} + + {formData.time_zone.errorMsg} + + From 02de299c8692d28abc3b9a9f01180cb0c824156f Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Wed, 2 Nov 2022 17:14:47 +0800 Subject: [PATCH 15/22] refactor(router-guard): refactor router guard --- ui/.eslintrc.js | 2 +- ui/config-overrides.js | 7 - ui/src/components/AccordionNav/index.tsx | 2 +- ui/src/components/Actions/index.tsx | 9 +- ui/src/components/BaseUserCard/index.tsx | 3 +- .../Comment/components/ActionBar/index.tsx | 2 +- .../Comment/components/Form/index.tsx | 4 +- .../Comment/components/Reply/index.tsx | 4 +- ui/src/components/Comment/index.tsx | 15 +- ui/src/components/Editor/ToolBars/image.tsx | 3 +- ui/src/components/FollowingTags/index.tsx | 5 +- .../Header/components/NavItems/index.tsx | 2 +- ui/src/components/Header/index.tsx | 11 +- ui/src/components/HotQuestions/index.tsx | 3 +- ui/src/components/Mentions/index.tsx | 2 +- ui/src/components/Modal/PicAuthCodeModal.tsx | 9 +- ui/src/components/Operate/index.tsx | 7 +- ui/src/components/QuestionList/index.tsx | 5 +- ui/src/components/Share/index.tsx | 2 +- ui/src/components/TagSelector/index.tsx | 5 +- ui/src/components/Unactivate/index.tsx | 11 +- ui/src/components/UserCard/index.tsx | 3 +- ui/src/hooks/useChangeModal/index.tsx | 3 +- ui/src/hooks/usePageUsers/index.tsx | 2 +- ui/src/hooks/useReportModal/index.tsx | 5 +- ui/src/i18n/init.ts | 4 +- ui/src/index.tsx | 6 +- ui/src/pages/Admin/Answers/index.tsx | 9 +- ui/src/pages/Admin/Flags/index.tsx | 7 +- ui/src/pages/Admin/General/index.tsx | 7 +- ui/src/pages/Admin/Interface/index.tsx | 9 +- ui/src/pages/Admin/Questions/index.tsx | 9 +- ui/src/pages/Admin/Smtp/index.tsx | 5 +- ui/src/pages/Admin/Users/index.tsx | 7 +- ui/src/pages/Admin/index.tsx | 4 +- ui/src/pages/Layout/index.tsx | 5 +- .../Ask/components/SearchQuestion/index.tsx | 2 +- ui/src/pages/Questions/Ask/index.tsx | 9 +- .../Detail/components/Answer/index.tsx | 7 +- .../Detail/components/AnswerHead/index.tsx | 2 +- .../Detail/components/Question/index.tsx | 5 +- .../components/RelatedQuestions/index.tsx | 3 +- .../Detail/components/WriteAnswer/index.tsx | 5 +- ui/src/pages/Questions/Detail/index.tsx | 13 +- ui/src/pages/Questions/EditAnswer/index.tsx | 5 +- ui/src/pages/Questions/index.tsx | 3 +- ui/src/pages/Search/components/Head/index.tsx | 2 +- .../Search/components/SearchHead/index.tsx | 2 +- .../Search/components/SearchItem/index.tsx | 4 +- ui/src/pages/Search/index.tsx | 5 +- ui/src/pages/Tags/Detail/index.tsx | 5 +- ui/src/pages/Tags/Edit/index.tsx | 7 +- ui/src/pages/Tags/Info/index.tsx | 9 +- ui/src/pages/Tags/index.tsx | 5 +- .../AccountForgot/components/sendEmail.tsx | 3 +- ui/src/pages/Users/AccountForgot/index.tsx | 6 +- ui/src/pages/Users/ActiveEmail/index.tsx | 5 +- .../ChangeEmail/components/sendEmail.tsx | 5 +- ui/src/pages/Users/ChangeEmail/index.tsx | 4 +- ui/src/pages/Users/Login/index.tsx | 20 +- .../components/Achievements/index.tsx | 2 +- .../Notifications/components/Inbox/index.tsx | 2 +- ui/src/pages/Users/Notifications/index.tsx | 9 +- ui/src/pages/Users/PasswordReset/index.tsx | 9 +- .../Personal/components/Answers/index.tsx | 2 +- .../Personal/components/Comments/index.tsx | 2 +- .../Personal/components/DefaultList/index.tsx | 2 +- .../Personal/components/ListHead/index.tsx | 2 +- .../Personal/components/Reputation/index.tsx | 2 +- .../Personal/components/TopList/index.tsx | 2 +- .../Personal/components/UserInfo/index.tsx | 4 +- .../Users/Personal/components/Votes/index.tsx | 2 +- ui/src/pages/Users/Personal/index.tsx | 15 +- .../Register/components/SignUpForm/index.tsx | 3 +- ui/src/pages/Users/Register/index.tsx | 5 +- .../Account/components/ModifyEmail/index.tsx | 5 +- .../Account/components/ModifyPass/index.tsx | 5 +- .../pages/Users/Settings/Interface/index.tsx | 5 +- .../Users/Settings/Notification/index.tsx | 5 +- ui/src/pages/Users/Settings/Profile/index.tsx | 9 +- ui/src/pages/Users/Settings/index.tsx | 7 +- ui/src/pages/Users/Suspended/index.tsx | 3 +- ui/src/router/guarder.ts | 42 ---- ui/src/router/index.tsx | 15 +- ui/src/router/routes.ts | 76 +++++-- ui/src/services/admin/answer.ts | 4 +- ui/src/services/admin/flag.ts | 4 +- ui/src/services/admin/question.ts | 4 +- ui/src/services/admin/settings.ts | 4 +- ui/src/services/client/activity.ts | 4 +- ui/src/services/client/notification.ts | 7 +- ui/src/services/client/personal.ts | 4 +- ui/src/services/client/question.ts | 4 +- ui/src/services/client/search.ts | 4 +- ui/src/services/client/tag.ts | 7 +- ui/src/services/common.ts | 4 +- ui/src/stores/userInfo.ts | 6 +- ui/src/utils/floppyNavigation.ts | 3 +- ui/src/utils/guard.ts | 182 ++++++++++++++++ ui/src/utils/guards.ts | 196 ------------------ ui/src/utils/index.ts | 6 +- ui/src/utils/request.ts | 11 +- ui/tsconfig.json | 13 +- 103 files changed, 471 insertions(+), 565 deletions(-) delete mode 100644 ui/src/router/guarder.ts create mode 100644 ui/src/utils/guard.ts delete mode 100644 ui/src/utils/guards.ts diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 877a0039..98edc408 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -64,7 +64,7 @@ module.exports = { position: 'before', }, { - pattern: '@answer/**', + pattern: '@/**', group: 'internal', }, { diff --git a/ui/config-overrides.js b/ui/config-overrides.js index 06464361..ac52fa63 100644 --- a/ui/config-overrides.js +++ b/ui/config-overrides.js @@ -8,13 +8,6 @@ module.exports = { config.resolve.alias = { ...config.resolve.alias, '@': path.resolve(__dirname, 'src'), - '@answer/pages': path.resolve(__dirname, 'src/pages'), - '@answer/components': path.resolve(__dirname, 'src/components'), - '@answer/stores': path.resolve(__dirname, 'src/stores'), - '@answer/hooks': path.resolve(__dirname, 'src/hooks'), - '@answer/utils': path.resolve(__dirname, 'src/utils'), - '@answer/common': path.resolve(__dirname, 'src/common'), - '@answer/api': path.resolve(__dirname, 'src/services/api'), }; return config; diff --git a/ui/src/components/AccordionNav/index.tsx b/ui/src/components/AccordionNav/index.tsx index ccc14d44..b3403bc8 100644 --- a/ui/src/components/AccordionNav/index.tsx +++ b/ui/src/components/AccordionNav/index.tsx @@ -5,7 +5,7 @@ import { useNavigate, useMatch } from 'react-router-dom'; import { useAccordionButton } from 'react-bootstrap/AccordionButton'; -import { Icon } from '@answer/components'; +import { Icon } from '@/components'; function MenuNode({ menu, callback, activeKey, isLeaf = false }) { const { t } = useTranslation('translation', { keyPrefix: 'admin.nav_menus' }); diff --git a/ui/src/components/Actions/index.tsx b/ui/src/components/Actions/index.tsx index 25402ab2..d9395723 100644 --- a/ui/src/components/Actions/index.tsx +++ b/ui/src/components/Actions/index.tsx @@ -4,11 +4,10 @@ import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; -import { Icon } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; -import { useToast } from '@answer/hooks'; - -import { tryNormalLogged } from '@/utils/guards'; +import { Icon } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import { useToast } from '@/hooks'; +import { tryNormalLogged } from '@/utils/guard'; import { bookmark, postVote } from '@/services'; interface Props { diff --git a/ui/src/components/BaseUserCard/index.tsx b/ui/src/components/BaseUserCard/index.tsx index fa524bda..d170d3f8 100644 --- a/ui/src/components/BaseUserCard/index.tsx +++ b/ui/src/components/BaseUserCard/index.tsx @@ -1,8 +1,7 @@ import { memo, FC } from 'react'; import { Link } from 'react-router-dom'; -import { Avatar } from '@answer/components'; - +import { Avatar } from '@/components'; import { formatCount } from '@/utils'; interface Props { diff --git a/ui/src/components/Comment/components/ActionBar/index.tsx b/ui/src/components/Comment/components/ActionBar/index.tsx index a801a6bb..a13bff1a 100644 --- a/ui/src/components/Comment/components/ActionBar/index.tsx +++ b/ui/src/components/Comment/components/ActionBar/index.tsx @@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'; import classNames from 'classnames'; -import { Icon, FormatTime } from '@answer/components'; +import { Icon, FormatTime } from '@/components'; const ActionBar = ({ nickName, diff --git a/ui/src/components/Comment/components/Form/index.tsx b/ui/src/components/Comment/components/Form/index.tsx index 4971c4ef..70c62fdd 100644 --- a/ui/src/components/Comment/components/Form/index.tsx +++ b/ui/src/components/Comment/components/Form/index.tsx @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; -import { TextArea, Mentions } from '@answer/components'; -import { usePageUsers } from '@answer/hooks'; +import { TextArea, Mentions } from '@/components'; +import { usePageUsers } from '@/hooks'; const Form = ({ className = '', diff --git a/ui/src/components/Comment/components/Reply/index.tsx b/ui/src/components/Comment/components/Reply/index.tsx index dcbf53bb..16b04822 100644 --- a/ui/src/components/Comment/components/Reply/index.tsx +++ b/ui/src/components/Comment/components/Reply/index.tsx @@ -2,8 +2,8 @@ import { useState, memo } from 'react'; import { Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { TextArea, Mentions } from '@answer/components'; -import { usePageUsers } from '@answer/hooks'; +import { TextArea, Mentions } from '@/components'; +import { usePageUsers } from '@/hooks'; const Form = ({ userName, onSendReply, onCancel, mode }) => { const [value, setValue] = useState(''); diff --git a/ui/src/components/Comment/index.tsx b/ui/src/components/Comment/index.tsx index a6dcdc98..1f57e737 100644 --- a/ui/src/components/Comment/index.tsx +++ b/ui/src/components/Comment/index.tsx @@ -7,14 +7,11 @@ import classNames from 'classnames'; import { unionBy } from 'lodash'; import { marked } from 'marked'; -import * as Types from '@answer/common/interface'; -import { Modal } from '@answer/components'; -import { usePageUsers, useReportModal } from '@answer/hooks'; -import { matchedUsers, parseUserInfo } from '@answer/utils'; - -import { Form, ActionBar, Reply } from './components'; - -import { tryNormalLogged } from '@/utils/guards'; +import * as Types from '@/common/interface'; +import { Modal } from '@/components'; +import { usePageUsers, useReportModal } from '@/hooks'; +import { matchedUsers, parseUserInfo } from '@/utils'; +import { tryNormalLogged } from '@/utils/guard'; import { useQueryComments, addComment, @@ -23,6 +20,8 @@ import { postVote, } from '@/services'; +import { Form, ActionBar, Reply } from './components'; + import './index.scss'; const Comment = ({ objectId, mode }) => { diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx index ead20eee..bfa4c530 100644 --- a/ui/src/components/Editor/ToolBars/image.tsx +++ b/ui/src/components/Editor/ToolBars/image.tsx @@ -2,10 +2,9 @@ import { FC, useEffect, useState, memo } from 'react'; import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Modal as AnswerModal } from '@answer/components'; +import { Modal as AnswerModal } from '@/components'; import ToolItem from '../toolItem'; import { IEditorContext } from '../types'; - import { uploadImage } from '@/services'; const Image: FC = ({ editor }) => { diff --git a/ui/src/components/FollowingTags/index.tsx b/ui/src/components/FollowingTags/index.tsx index 55e9bc76..06fec6ec 100644 --- a/ui/src/components/FollowingTags/index.tsx +++ b/ui/src/components/FollowingTags/index.tsx @@ -3,9 +3,8 @@ import { Card, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { NavLink } from 'react-router-dom'; -import { TagSelector, Tag } from '@answer/components'; - -import { tryNormalLogged } from '@/utils/guards'; +import { TagSelector, Tag } from '@/components'; +import { tryNormalLogged } from '@/utils/guard'; import { useFollowingTags, followTags } from '@/services'; const Index: FC = () => { diff --git a/ui/src/components/Header/components/NavItems/index.tsx b/ui/src/components/Header/components/NavItems/index.tsx index 67112758..8a4872d9 100644 --- a/ui/src/components/Header/components/NavItems/index.tsx +++ b/ui/src/components/Header/components/NavItems/index.tsx @@ -3,7 +3,7 @@ import { Nav, Dropdown } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Link, NavLink } from 'react-router-dom'; -import { Avatar, Icon } from '@answer/components'; +import { Avatar, Icon } from '@/components'; interface Props { redDot; diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index b4cd4e16..748eb318 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -17,17 +17,12 @@ import { useLocation, } from 'react-router-dom'; -import { - loggedUserInfoStore, - siteInfoStore, - interfaceStore, -} from '@answer/stores'; - -import NavItems from './components/NavItems'; - +import { loggedUserInfoStore, siteInfoStore, interfaceStore } from '@/stores'; import { logout, useQueryNotificationStatus } from '@/services'; import { RouteAlias } from '@/router/alias'; +import NavItems from './components/NavItems'; + import './index.scss'; const Header: FC = () => { diff --git a/ui/src/components/HotQuestions/index.tsx b/ui/src/components/HotQuestions/index.tsx index 920bdce2..da0305d0 100644 --- a/ui/src/components/HotQuestions/index.tsx +++ b/ui/src/components/HotQuestions/index.tsx @@ -3,8 +3,7 @@ import { Card, ListGroup, ListGroupItem } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Icon } from '@answer/components'; - +import { Icon } from '@/components'; import { useHotQuestions } from '@/services'; const HotQuestions: FC = () => { diff --git a/ui/src/components/Mentions/index.tsx b/ui/src/components/Mentions/index.tsx index 9b29c86d..23cfa083 100644 --- a/ui/src/components/Mentions/index.tsx +++ b/ui/src/components/Mentions/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState, FC } from 'react'; import { Dropdown } from 'react-bootstrap'; -import * as Types from '@answer/common/interface'; +import * as Types from '@/common/interface'; interface IProps { children: React.ReactNode; diff --git a/ui/src/components/Modal/PicAuthCodeModal.tsx b/ui/src/components/Modal/PicAuthCodeModal.tsx index 3a081a0c..ec194a64 100644 --- a/ui/src/components/Modal/PicAuthCodeModal.tsx +++ b/ui/src/components/Modal/PicAuthCodeModal.tsx @@ -2,13 +2,8 @@ import React from 'react'; import { Modal, Form, Button, InputGroup } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon } from '@answer/components'; -import type { - FormValue, - FormDataType, - ImgCodeRes, -} from '@answer/common/interface'; - +import { Icon } from '@/components'; +import type { FormValue, FormDataType, ImgCodeRes } from '@/common/interface'; import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; diff --git a/ui/src/components/Operate/index.tsx b/ui/src/components/Operate/index.tsx index 201774c6..cdacc751 100644 --- a/ui/src/components/Operate/index.tsx +++ b/ui/src/components/Operate/index.tsx @@ -3,12 +3,11 @@ import { Button } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Modal } from '@answer/components'; -import { useReportModal, useToast } from '@answer/hooks'; +import { Modal } from '@/components'; +import { useReportModal, useToast } from '@/hooks'; import Share from '../Share'; - import { deleteQuestion, deleteAnswer } from '@/services'; -import { tryNormalLogged } from '@/utils/guards'; +import { tryNormalLogged } from '@/utils/guard'; interface IProps { type: 'answer' | 'question'; diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index 85b72ce1..76a6ccd9 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -3,7 +3,7 @@ import { Row, Col, ListGroup } from 'react-bootstrap'; import { NavLink, useParams, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; +import type * as Type from '@/common/interface'; import { Icon, Tag, @@ -12,8 +12,7 @@ import { Empty, BaseUserCard, QueryGroup, -} from '@answer/components'; - +} from '@/components'; import { useQuestionList } from '@/services'; const QuestionOrderKeys: Type.QuestionOrderBy[] = [ diff --git a/ui/src/components/Share/index.tsx b/ui/src/components/Share/index.tsx index 74b3de34..652dcef1 100644 --- a/ui/src/components/Share/index.tsx +++ b/ui/src/components/Share/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { FacebookShareButton, TwitterShareButton } from 'next-share'; import copy from 'copy-to-clipboard'; -import { loggedUserInfoStore } from '@answer/stores'; +import { loggedUserInfoStore } from '@/stores'; interface IProps { type: 'answer' | 'question'; diff --git a/ui/src/components/TagSelector/index.tsx b/ui/src/components/TagSelector/index.tsx index cedb85a3..8747f65a 100644 --- a/ui/src/components/TagSelector/index.tsx +++ b/ui/src/components/TagSelector/index.tsx @@ -5,9 +5,8 @@ import { useTranslation } from 'react-i18next'; import { marked } from 'marked'; import classNames from 'classnames'; -import { useTagModal } from '@answer/hooks'; -import type * as Type from '@answer/common/interface'; - +import { useTagModal } from '@/hooks'; +import type * as Type from '@/common/interface'; import { queryTags } from '@/services'; import './index.scss'; diff --git a/ui/src/components/Unactivate/index.tsx b/ui/src/components/Unactivate/index.tsx index 6b7a26bf..420891cc 100644 --- a/ui/src/components/Unactivate/index.tsx +++ b/ui/src/components/Unactivate/index.tsx @@ -3,14 +3,9 @@ import { Button, Col } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { PicAuthCodeModal } from '@answer/components/Modal'; -import type { - ImgCodeRes, - ImgCodeReq, - FormDataType, -} from '@answer/common/interface'; -import { loggedUserInfoStore } from '@answer/stores'; - +import { PicAuthCodeModal } from '@/components/Modal'; +import type { ImgCodeRes, ImgCodeReq, FormDataType } from '@/common/interface'; +import { loggedUserInfoStore } from '@/stores'; import { resendEmail, checkImgCode } from '@/services'; import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; diff --git a/ui/src/components/UserCard/index.tsx b/ui/src/components/UserCard/index.tsx index 0110c82a..27441231 100644 --- a/ui/src/components/UserCard/index.tsx +++ b/ui/src/components/UserCard/index.tsx @@ -3,8 +3,7 @@ import { Link } from 'react-router-dom'; import classnames from 'classnames'; -import { Avatar, FormatTime } from '@answer/components'; - +import { Avatar, FormatTime } from '@/components'; import { formatCount } from '@/utils'; interface Props { diff --git a/ui/src/hooks/useChangeModal/index.tsx b/ui/src/hooks/useChangeModal/index.tsx index f484331c..529f7a3a 100644 --- a/ui/src/hooks/useChangeModal/index.tsx +++ b/ui/src/hooks/useChangeModal/index.tsx @@ -4,8 +4,7 @@ import { useTranslation } from 'react-i18next'; import ReactDOM from 'react-dom/client'; -import { Modal as AnswerModal } from '@answer/components'; - +import { Modal as AnswerModal } from '@/components'; import { changeUserStatus } from '@/services'; const div = document.createElement('div'); diff --git a/ui/src/hooks/usePageUsers/index.tsx b/ui/src/hooks/usePageUsers/index.tsx index 72203619..2c828695 100644 --- a/ui/src/hooks/usePageUsers/index.tsx +++ b/ui/src/hooks/usePageUsers/index.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { uniqBy } from 'lodash'; -import * as Types from '@answer/common/interface'; +import * as Types from '@/common/interface'; let globalUsers: Types.PageUser[] = []; const usePageUsers = () => { diff --git a/ui/src/hooks/useReportModal/index.tsx b/ui/src/hooks/useReportModal/index.tsx index 2f7c8f18..28b85eb5 100644 --- a/ui/src/hooks/useReportModal/index.tsx +++ b/ui/src/hooks/useReportModal/index.tsx @@ -4,9 +4,8 @@ import { useTranslation } from 'react-i18next'; import ReactDOM from 'react-dom/client'; -import { useToast } from '@answer/hooks'; -import type * as Type from '@answer/common/interface'; - +import { useToast } from '@/hooks'; +import type * as Type from '@/common/interface'; import { reportList, postReport, closeQuestion, putReport } from '@/services'; interface Params { diff --git a/ui/src/i18n/init.ts b/ui/src/i18n/init.ts index deecc0e2..e8b41e31 100644 --- a/ui/src/i18n/init.ts +++ b/ui/src/i18n/init.ts @@ -3,11 +3,11 @@ import { initReactI18next } from 'react-i18next'; import i18next from 'i18next'; import Backend from 'i18next-http-backend'; +import { DEFAULT_LANG } from '@/common/constants'; + import en from './locales/en.json'; import zh from './locales/zh_CN.json'; -import { DEFAULT_LANG } from '@/common/constants'; - i18next // load translation using http .use(Backend) diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 1943b61a..67e5ee50 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -2,9 +2,9 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; +import { Guard } from '@/utils'; -import { pullLoggedUser } from '@/utils/guards'; +import App from './App'; import './i18n/init'; import './index.scss'; @@ -17,7 +17,7 @@ async function bootstrapApp() { /** * NOTICE: must pre init logged user info for router */ - await pullLoggedUser(); + await Guard.pullLoggedUser(); root.render( diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx index 2a25f53c..2c14c6d7 100644 --- a/ui/src/pages/Admin/Answers/index.tsx +++ b/ui/src/pages/Admin/Answers/index.tsx @@ -11,11 +11,10 @@ import { BaseUserCard, Empty, QueryGroup, -} from '@answer/components'; -import { ADMIN_LIST_STATUS } from '@answer/common/constants'; -import { useEditStatusModal } from '@answer/hooks'; -import * as Type from '@answer/common/interface'; - +} from '@/components'; +import { ADMIN_LIST_STATUS } from '@/common/constants'; +import { useEditStatusModal } from '@/hooks'; +import * as Type from '@/common/interface'; import { useAnswerSearch, changeAnswerStatus } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Flags/index.tsx b/ui/src/pages/Admin/Flags/index.tsx index a7842429..353bd382 100644 --- a/ui/src/pages/Admin/Flags/index.tsx +++ b/ui/src/pages/Admin/Flags/index.tsx @@ -9,10 +9,9 @@ import { Empty, Pagination, QueryGroup, -} from '@answer/components'; -import { useReportModal } from '@answer/hooks'; -import * as Type from '@answer/common/interface'; - +} from '@/components'; +import { useReportModal } from '@/hooks'; +import * as Type from '@/common/interface'; import { useFlagSearch } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/General/index.tsx b/ui/src/pages/Admin/General/index.tsx index 3154caec..14b95978 100644 --- a/ui/src/pages/Admin/General/index.tsx +++ b/ui/src/pages/Admin/General/index.tsx @@ -2,10 +2,9 @@ import React, { FC, useEffect, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; -import { useToast } from '@answer/hooks'; -import { siteInfoStore } from '@answer/stores'; - +import type * as Type from '@/common/interface'; +import { useToast } from '@/hooks'; +import { siteInfoStore } from '@/stores'; import { useGeneralSetting, updateGeneralSetting } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx index e5d68f32..3f179c57 100644 --- a/ui/src/pages/Admin/Interface/index.tsx +++ b/ui/src/pages/Admin/Interface/index.tsx @@ -2,15 +2,14 @@ import React, { FC, FormEvent, useEffect, useState } from 'react'; import { Form, Button, Image, Stack } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import { useToast } from '@answer/hooks'; +import { useToast } from '@/hooks'; import { LangsType, FormDataType, AdminSettingsInterface, -} from '@answer/common/interface'; -import { interfaceStore } from '@answer/stores'; -import { UploadImg } from '@answer/components'; - +} from '@/common/interface'; +import { interfaceStore } from '@/stores'; +import { UploadImg } from '@/components'; import { languages, uploadAvatar, diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx index 1a188512..e27a715c 100644 --- a/ui/src/pages/Admin/Questions/index.tsx +++ b/ui/src/pages/Admin/Questions/index.tsx @@ -11,11 +11,10 @@ import { BaseUserCard, Empty, QueryGroup, -} from '@answer/components'; -import { ADMIN_LIST_STATUS } from '@answer/common/constants'; -import { useEditStatusModal, useReportModal } from '@answer/hooks'; -import * as Type from '@answer/common/interface'; - +} from '@/components'; +import { ADMIN_LIST_STATUS } from '@/common/constants'; +import { useEditStatusModal, useReportModal } from '@/hooks'; +import * as Type from '@/common/interface'; import { useQuestionSearch, changeQuestionStatus, diff --git a/ui/src/pages/Admin/Smtp/index.tsx b/ui/src/pages/Admin/Smtp/index.tsx index 17abd481..33de30ab 100644 --- a/ui/src/pages/Admin/Smtp/index.tsx +++ b/ui/src/pages/Admin/Smtp/index.tsx @@ -2,9 +2,8 @@ import React, { FC, useEffect, useState } from 'react'; import { Form, Button, Stack } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; -import { useToast } from '@answer/hooks'; - +import type * as Type from '@/common/interface'; +import { useToast } from '@/hooks'; import { useSmtpSetting, updateSmtpSetting } from '@/services'; import pattern from '@/common/pattern'; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index de434084..a14f6b74 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -9,10 +9,9 @@ import { BaseUserCard, Empty, QueryGroup, -} from '@answer/components'; -import * as Type from '@answer/common/interface'; -import { useChangeModal } from '@answer/hooks'; - +} from '@/components'; +import * as Type from '@/common/interface'; +import { useChangeModal } from '@/hooks'; import { useQueryUsers } from '@/services'; import '../index.scss'; diff --git a/ui/src/pages/Admin/index.tsx b/ui/src/pages/Admin/index.tsx index 8d7b452e..27ef8565 100644 --- a/ui/src/pages/Admin/index.tsx +++ b/ui/src/pages/Admin/index.tsx @@ -3,8 +3,8 @@ import { Container, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; -import { AccordionNav, PageTitle } from '@answer/components'; -import { ADMIN_NAV_MENUS } from '@answer/common/constants'; +import { AccordionNav, PageTitle } from '@/components'; +import { ADMIN_NAV_MENUS } from '@/common/constants'; import './index.scss'; diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx index 7affe2f0..363c9b01 100644 --- a/ui/src/pages/Layout/index.tsx +++ b/ui/src/pages/Layout/index.tsx @@ -5,9 +5,8 @@ import { Helmet, HelmetProvider } from 'react-helmet-async'; import { SWRConfig } from 'swr'; -import { siteInfoStore, interfaceStore, toastStore } from '@answer/stores'; -import { Header, AdminHeader, Footer, Toast } from '@answer/components'; - +import { siteInfoStore, interfaceStore, toastStore } from '@/stores'; +import { Header, AdminHeader, Footer, Toast } from '@/components'; import { useSiteSettings } from '@/services'; import Storage from '@/utils/storage'; import { CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; diff --git a/ui/src/pages/Questions/Ask/components/SearchQuestion/index.tsx b/ui/src/pages/Questions/Ask/components/SearchQuestion/index.tsx index 16388963..99a8026a 100644 --- a/ui/src/pages/Questions/Ask/components/SearchQuestion/index.tsx +++ b/ui/src/pages/Questions/Ask/components/SearchQuestion/index.tsx @@ -2,7 +2,7 @@ import { memo } from 'react'; import { Accordion, ListGroup } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon } from '@answer/components'; +import { Icon } from '@/components'; import './index.scss'; diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx index f233150e..fe4d3319 100644 --- a/ui/src/pages/Questions/Ask/index.tsx +++ b/ui/src/pages/Questions/Ask/index.tsx @@ -6,11 +6,8 @@ import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import classNames from 'classnames'; -import { Editor, EditorRef, TagSelector, PageTitle } from '@answer/components'; -import type * as Type from '@answer/common/interface'; - -import SearchQuestion from './components/SearchQuestion'; - +import { Editor, EditorRef, TagSelector, PageTitle } from '@/components'; +import type * as Type from '@/common/interface'; import { saveQuestion, questionDetail, @@ -20,6 +17,8 @@ import { useQueryQuestionByTitle, } from '@/services'; +import SearchQuestion from './components/SearchQuestion'; + interface FormDataItem { title: Type.FormValue; tags: Type.FormValue; diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx index e97238cf..2fe87d1c 100644 --- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx @@ -10,10 +10,9 @@ import { Comment, FormatTime, htmlRender, -} from '@answer/components'; -import { scrollTop } from '@answer/utils'; -import { AnswerItem } from '@answer/common/interface'; - +} from '@/components'; +import { scrollTop } from '@/utils'; +import { AnswerItem } from '@/common/interface'; import { acceptanceAnswer } from '@/services'; interface Props { diff --git a/ui/src/pages/Questions/Detail/components/AnswerHead/index.tsx b/ui/src/pages/Questions/Detail/components/AnswerHead/index.tsx index f2c7efe9..32abc17b 100644 --- a/ui/src/pages/Questions/Detail/components/AnswerHead/index.tsx +++ b/ui/src/pages/Questions/Detail/components/AnswerHead/index.tsx @@ -1,7 +1,7 @@ import { memo, FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { QueryGroup } from '@answer/components'; +import { QueryGroup } from '@/components'; interface Props { count: number; diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx index aa3a38ec..1b46a869 100644 --- a/ui/src/pages/Questions/Detail/components/Question/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx @@ -11,9 +11,8 @@ import { Comment, FormatTime, htmlRender, -} from '@answer/components'; -import { formatCount } from '@answer/utils'; - +} from '@/components'; +import { formatCount } from '@/utils'; import { following } from '@/services'; interface Props { diff --git a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx index 8493408c..3dd91c7f 100644 --- a/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/RelatedQuestions/index.tsx @@ -3,8 +3,7 @@ import { Card, ListGroup } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Icon } from '@answer/components'; - +import { Icon } from '@/components'; import { useSimilarQuestion } from '@/services'; import { loggedUserInfoStore } from '@/stores'; diff --git a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx index 9659b286..8d1ef1be 100644 --- a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx @@ -5,9 +5,8 @@ import { useTranslation } from 'react-i18next'; import { marked } from 'marked'; import classNames from 'classnames'; -import { Editor, Modal } from '@answer/components'; -import { FormDataType } from '@answer/common/interface'; - +import { Editor, Modal } from '@/components'; +import { FormDataType } from '@/common/interface'; import { postAnswer } from '@/services'; interface Props { diff --git a/ui/src/pages/Questions/Detail/index.tsx b/ui/src/pages/Questions/Detail/index.tsx index 2f469415..cc19a719 100644 --- a/ui/src/pages/Questions/Detail/index.tsx +++ b/ui/src/pages/Questions/Detail/index.tsx @@ -2,15 +2,16 @@ import { useEffect, useState } from 'react'; import { Container, Row, Col } from 'react-bootstrap'; import { useParams, useSearchParams, useNavigate } from 'react-router-dom'; -import { Pagination, PageTitle } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; -import { scrollTop } from '@answer/utils'; -import { usePageUsers } from '@answer/hooks'; +import { Pagination, PageTitle } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import { scrollTop } from '@/utils'; +import { usePageUsers } from '@/hooks'; import type { ListResult, QuestionDetailRes, AnswerItem, -} from '@answer/common/interface'; +} from '@/common/interface'; +import { questionDetail, getAnswers } from '@/services'; import { Question, @@ -21,8 +22,6 @@ import { Alert, } from './components'; -import { questionDetail, getAnswers } from '@/services'; - import './index.scss'; const Index = () => { diff --git a/ui/src/pages/Questions/EditAnswer/index.tsx b/ui/src/pages/Questions/EditAnswer/index.tsx index c87733f4..0fab103c 100644 --- a/ui/src/pages/Questions/EditAnswer/index.tsx +++ b/ui/src/pages/Questions/EditAnswer/index.tsx @@ -6,9 +6,8 @@ import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import classNames from 'classnames'; -import { Editor, EditorRef, Icon, PageTitle } from '@answer/components'; -import type * as Type from '@answer/common/interface'; - +import { Editor, EditorRef, Icon, PageTitle } from '@/components'; +import type * as Type from '@/common/interface'; import { useQueryAnswerInfo, modifyAnswer, diff --git a/ui/src/pages/Questions/index.tsx b/ui/src/pages/Questions/index.tsx index 9e7ed318..ea10ec15 100644 --- a/ui/src/pages/Questions/index.tsx +++ b/ui/src/pages/Questions/index.tsx @@ -3,8 +3,7 @@ import { Container, Row, Col } from 'react-bootstrap'; import { useMatch } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { PageTitle, FollowingTags } from '@answer/components'; - +import { PageTitle, FollowingTags } from '@/components'; import QuestionList from '@/components/QuestionList'; import HotQuestions from '@/components/HotQuestions'; import { siteInfoStore } from '@/stores'; diff --git a/ui/src/pages/Search/components/Head/index.tsx b/ui/src/pages/Search/components/Head/index.tsx index 1558bc35..095796d2 100644 --- a/ui/src/pages/Search/components/Head/index.tsx +++ b/ui/src/pages/Search/components/Head/index.tsx @@ -4,7 +4,7 @@ import { Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { following } from '@/services'; -import { tryNormalLogged } from '@/utils/guards'; +import { tryNormalLogged } from '@/utils/guard'; interface Props { data; diff --git a/ui/src/pages/Search/components/SearchHead/index.tsx b/ui/src/pages/Search/components/SearchHead/index.tsx index 2d7549a4..fb185bc1 100644 --- a/ui/src/pages/Search/components/SearchHead/index.tsx +++ b/ui/src/pages/Search/components/SearchHead/index.tsx @@ -2,7 +2,7 @@ import { FC, memo } from 'react'; import { ListGroupItem } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { QueryGroup } from '@answer/components'; +import { QueryGroup } from '@/components'; const sortBtns = ['relevance', 'newest', 'active', 'score']; diff --git a/ui/src/pages/Search/components/SearchItem/index.tsx b/ui/src/pages/Search/components/SearchItem/index.tsx index ec703277..09cecd32 100644 --- a/ui/src/pages/Search/components/SearchItem/index.tsx +++ b/ui/src/pages/Search/components/SearchItem/index.tsx @@ -2,8 +2,8 @@ import { memo, FC } from 'react'; import { ListGroupItem, Badge } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon, Tag, FormatTime, BaseUserCard } from '@answer/components'; -import type { SearchResItem } from '@answer/common/interface'; +import { Icon, Tag, FormatTime, BaseUserCard } from '@/components'; +import type { SearchResItem } from '@/common/interface'; interface Props { data: SearchResItem; diff --git a/ui/src/pages/Search/index.tsx b/ui/src/pages/Search/index.tsx index 914565ae..f22dc3ce 100644 --- a/ui/src/pages/Search/index.tsx +++ b/ui/src/pages/Search/index.tsx @@ -3,12 +3,11 @@ import { Container, Row, Col, ListGroup } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; -import { Pagination, PageTitle } from '@answer/components'; +import { Pagination, PageTitle } from '@/components'; +import { useSearch } from '@/services'; import { Head, SearchHead, SearchItem, Tips, Empty } from './components'; -import { useSearch } from '@/services'; - const Index = () => { const { t } = useTranslation('translation'); const [searchParams] = useSearchParams(); diff --git a/ui/src/pages/Tags/Detail/index.tsx b/ui/src/pages/Tags/Detail/index.tsx index e37ab0ab..452ef37d 100644 --- a/ui/src/pages/Tags/Detail/index.tsx +++ b/ui/src/pages/Tags/Detail/index.tsx @@ -3,9 +3,8 @@ import { Container, Row, Col, Button } from 'react-bootstrap'; import { useParams, Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import * as Type from '@answer/common/interface'; -import { PageTitle, FollowingTags } from '@answer/components'; - +import * as Type from '@/common/interface'; +import { PageTitle, FollowingTags } from '@/components'; import { useTagInfo, useFollow } from '@/services'; import QuestionList from '@/components/QuestionList'; import HotQuestions from '@/components/HotQuestions'; diff --git a/ui/src/pages/Tags/Edit/index.tsx b/ui/src/pages/Tags/Edit/index.tsx index 58e7d59c..890bd32e 100644 --- a/ui/src/pages/Tags/Edit/index.tsx +++ b/ui/src/pages/Tags/Edit/index.tsx @@ -6,10 +6,9 @@ import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import classNames from 'classnames'; -import { Editor, EditorRef, PageTitle } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; -import type * as Type from '@answer/common/interface'; - +import { Editor, EditorRef, PageTitle } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import type * as Type from '@/common/interface'; import { useTagInfo, modifyTag, useQueryRevisions } from '@/services'; interface FormDataItem { diff --git a/ui/src/pages/Tags/Info/index.tsx b/ui/src/pages/Tags/Info/index.tsx index d8c4ad75..3b792adb 100644 --- a/ui/src/pages/Tags/Info/index.tsx +++ b/ui/src/pages/Tags/Info/index.tsx @@ -5,14 +5,7 @@ import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; -import { - Tag, - TagSelector, - FormatTime, - Modal, - PageTitle, -} from '@answer/components'; - +import { Tag, TagSelector, FormatTime, Modal, PageTitle } from '@/components'; import { useTagInfo, useQuerySynonymsTags, diff --git a/ui/src/pages/Tags/index.tsx b/ui/src/pages/Tags/index.tsx index 80256d94..a01e3e56 100644 --- a/ui/src/pages/Tags/index.tsx +++ b/ui/src/pages/Tags/index.tsx @@ -3,9 +3,8 @@ import { Container, Row, Col, Card, Button, Form } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Tag, Pagination, PageTitle, QueryGroup } from '@answer/components'; -import { formatCount } from '@answer/utils'; - +import { Tag, Pagination, PageTitle, QueryGroup } from '@/components'; +import { formatCount } from '@/utils'; import { useQueryTags, following } from '@/services'; const sortBtns = ['popular', 'name', 'newest']; diff --git a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx index e95b1354..f919e560 100644 --- a/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx +++ b/ui/src/pages/Users/AccountForgot/components/sendEmail.tsx @@ -6,8 +6,7 @@ import type { ImgCodeRes, PasswordResetReq, FormDataType, -} from '@answer/common/interface'; - +} from '@/common/interface'; import { resetPassword, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@/components/Modal'; diff --git a/ui/src/pages/Users/AccountForgot/index.tsx b/ui/src/pages/Users/AccountForgot/index.tsx index 5065e9b6..e7a77ea5 100644 --- a/ui/src/pages/Users/AccountForgot/index.tsx +++ b/ui/src/pages/Users/AccountForgot/index.tsx @@ -2,11 +2,11 @@ import React, { useState, useEffect } from 'react'; import { Container, Col } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import SendEmail from './components/sendEmail'; - -import { tryNormalLogged } from '@/utils/guards'; +import { tryNormalLogged } from '@/utils/guard'; import { PageTitle } from '@/components'; +import SendEmail from './components/sendEmail'; + const Index: React.FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'account_forgot' }); const [step, setStep] = useState(1); diff --git a/ui/src/pages/Users/ActiveEmail/index.tsx b/ui/src/pages/Users/ActiveEmail/index.tsx index f50fbc3e..ac2223a7 100644 --- a/ui/src/pages/Users/ActiveEmail/index.tsx +++ b/ui/src/pages/Users/ActiveEmail/index.tsx @@ -1,9 +1,8 @@ import { FC, memo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { loggedUserInfoStore } from '@answer/stores'; -import { getQueryString } from '@answer/utils'; - +import { loggedUserInfoStore } from '@/stores'; +import { getQueryString } from '@/utils'; import { activateAccount } from '@/services'; import { PageTitle } from '@/components'; diff --git a/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx b/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx index 4d419091..f87a6adf 100644 --- a/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx +++ b/ui/src/pages/Users/ChangeEmail/components/sendEmail.tsx @@ -7,9 +7,8 @@ import type { ImgCodeRes, PasswordResetReq, FormDataType, -} from '@answer/common/interface'; -import { loggedUserInfoStore } from '@answer/stores'; - +} from '@/common/interface'; +import { loggedUserInfoStore } from '@/stores'; import { changeEmail, checkImgCode } from '@/services'; import { PicAuthCodeModal } from '@/components/Modal'; diff --git a/ui/src/pages/Users/ChangeEmail/index.tsx b/ui/src/pages/Users/ChangeEmail/index.tsx index cbb743a5..cabc5e5f 100644 --- a/ui/src/pages/Users/ChangeEmail/index.tsx +++ b/ui/src/pages/Users/ChangeEmail/index.tsx @@ -2,10 +2,10 @@ import { FC, memo } from 'react'; import { Container, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import SendEmail from './components/sendEmail'; - import { PageTitle } from '@/components'; +import SendEmail from './components/sendEmail'; + const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'change_email' }); diff --git a/ui/src/pages/Users/Login/index.tsx b/ui/src/pages/Users/Login/index.tsx index 6d9af5c2..423a6654 100644 --- a/ui/src/pages/Users/Login/index.tsx +++ b/ui/src/pages/Users/Login/index.tsx @@ -7,13 +7,11 @@ import type { LoginReqParams, ImgCodeRes, FormDataType, -} from '@answer/common/interface'; -import { PageTitle, Unactivate } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; -import { getQueryString } from '@answer/utils'; - +} from '@/common/interface'; +import { PageTitle, Unactivate } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import { getQueryString, Guard, floppyNavigation } from '@/utils'; import { login, checkImgCode } from '@/services'; -import { deriveUserStat, tryNormalLogged } from '@/utils/guards'; import { REDIRECT_PATH_STORAGE_KEY } from '@/common/constants'; import { RouteAlias } from '@/router/alias'; import { PicAuthCodeModal } from '@/components/Modal'; @@ -106,8 +104,8 @@ const Index: React.FC = () => { login(params) .then((res) => { updateUser(res); - const userStat = deriveUserStat(); - if (!userStat.isActivated) { + const userStat = Guard.deriveLoginState(); + if (userStat.isNotActivated) { // inactive setStep(2); setRefresh((pre) => pre + 1); @@ -115,7 +113,9 @@ const Index: React.FC = () => { const path = Storage.get(REDIRECT_PATH_STORAGE_KEY) || RouteAlias.home; Storage.remove(REDIRECT_PATH_STORAGE_KEY); - navigate(path, { replace: true }); + floppyNavigation.navigate(path, () => { + navigate(path, { replace: true }); + }); } setModalState(false); @@ -159,7 +159,7 @@ const Index: React.FC = () => { if ((storeUser.id && storeUser.mail_status === 2) || isInactive) { setStep(2); } else { - tryNormalLogged(); + Guard.tryNormalLogged(); } }, []); diff --git a/ui/src/pages/Users/Notifications/components/Achievements/index.tsx b/ui/src/pages/Users/Notifications/components/Achievements/index.tsx index 12cfe93b..54bd4cdb 100644 --- a/ui/src/pages/Users/Notifications/components/Achievements/index.tsx +++ b/ui/src/pages/Users/Notifications/components/Achievements/index.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import classNames from 'classnames'; import { isEmpty } from 'lodash'; -import { Empty } from '@answer/components'; +import { Empty } from '@/components'; import './index.scss'; diff --git a/ui/src/pages/Users/Notifications/components/Inbox/index.tsx b/ui/src/pages/Users/Notifications/components/Inbox/index.tsx index 27366b81..68739b0c 100644 --- a/ui/src/pages/Users/Notifications/components/Inbox/index.tsx +++ b/ui/src/pages/Users/Notifications/components/Inbox/index.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import classNames from 'classnames'; import { isEmpty } from 'lodash'; -import { FormatTime, Empty } from '@answer/components'; +import { FormatTime, Empty } from '@/components'; const Inbox = ({ data, handleReadNotification }) => { if (!data) { diff --git a/ui/src/pages/Users/Notifications/index.tsx b/ui/src/pages/Users/Notifications/index.tsx index b24367b7..1ee7158c 100644 --- a/ui/src/pages/Users/Notifications/index.tsx +++ b/ui/src/pages/Users/Notifications/index.tsx @@ -3,11 +3,7 @@ import { Container, Row, Col, ButtonGroup, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useParams, useNavigate } from 'react-router-dom'; -import { PageTitle } from '@answer/components'; - -import Inbox from './components/Inbox'; -import Achievements from './components/Achievements'; - +import { PageTitle } from '@/components'; import { useQueryNotifications, clearUnreadNotification, @@ -15,6 +11,9 @@ import { readNotification, } from '@/services'; +import Inbox from './components/Inbox'; +import Achievements from './components/Achievements'; + const PAGE_SIZE = 10; const Notifications = () => { diff --git a/ui/src/pages/Users/PasswordReset/index.tsx b/ui/src/pages/Users/PasswordReset/index.tsx index af97001f..b97bd707 100644 --- a/ui/src/pages/Users/PasswordReset/index.tsx +++ b/ui/src/pages/Users/PasswordReset/index.tsx @@ -3,12 +3,11 @@ import { Container, Col, Form, Button } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { loggedUserInfoStore } from '@answer/stores'; -import { getQueryString } from '@answer/utils'; -import type { FormDataType } from '@answer/common/interface'; - +import { loggedUserInfoStore } from '@/stores'; +import { getQueryString } from '@/utils'; +import type { FormDataType } from '@/common/interface'; import { replacementPassword } from '@/services'; -import { tryNormalLogged } from '@/utils/guards'; +import { tryNormalLogged } from '@/utils/guard'; import { PageTitle } from '@/components'; const Index: React.FC = () => { diff --git a/ui/src/pages/Users/Personal/components/Answers/index.tsx b/ui/src/pages/Users/Personal/components/Answers/index.tsx index 12ab0059..a7d8c48a 100644 --- a/ui/src/pages/Users/Personal/components/Answers/index.tsx +++ b/ui/src/pages/Users/Personal/components/Answers/index.tsx @@ -2,7 +2,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon, FormatTime, Tag } from '@answer/components'; +import { Icon, FormatTime, Tag } from '@/components'; interface Props { visible: boolean; diff --git a/ui/src/pages/Users/Personal/components/Comments/index.tsx b/ui/src/pages/Users/Personal/components/Comments/index.tsx index 483ce361..f53e468e 100644 --- a/ui/src/pages/Users/Personal/components/Comments/index.tsx +++ b/ui/src/pages/Users/Personal/components/Comments/index.tsx @@ -1,7 +1,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; -import { FormatTime } from '@answer/components'; +import { FormatTime } from '@/components'; interface Props { visible: boolean; diff --git a/ui/src/pages/Users/Personal/components/DefaultList/index.tsx b/ui/src/pages/Users/Personal/components/DefaultList/index.tsx index 7eb67525..980a2df3 100644 --- a/ui/src/pages/Users/Personal/components/DefaultList/index.tsx +++ b/ui/src/pages/Users/Personal/components/DefaultList/index.tsx @@ -2,7 +2,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon, FormatTime, Tag, BaseUserCard } from '@answer/components'; +import { Icon, FormatTime, Tag, BaseUserCard } from '@/components'; interface Props { visible: boolean; diff --git a/ui/src/pages/Users/Personal/components/ListHead/index.tsx b/ui/src/pages/Users/Personal/components/ListHead/index.tsx index e1ce494f..de550754 100644 --- a/ui/src/pages/Users/Personal/components/ListHead/index.tsx +++ b/ui/src/pages/Users/Personal/components/ListHead/index.tsx @@ -1,7 +1,7 @@ import { FC, memo } from 'react'; import { useTranslation } from 'react-i18next'; -import { QueryGroup } from '@answer/components'; +import { QueryGroup } from '@/components'; const sortBtns = ['newest', 'score']; diff --git a/ui/src/pages/Users/Personal/components/Reputation/index.tsx b/ui/src/pages/Users/Personal/components/Reputation/index.tsx index fa1cb3db..6458e921 100644 --- a/ui/src/pages/Users/Personal/components/Reputation/index.tsx +++ b/ui/src/pages/Users/Personal/components/Reputation/index.tsx @@ -2,7 +2,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { FormatTime } from '@answer/components'; +import { FormatTime } from '@/components'; interface Props { visible: boolean; diff --git a/ui/src/pages/Users/Personal/components/TopList/index.tsx b/ui/src/pages/Users/Personal/components/TopList/index.tsx index cdd7ba2e..86179305 100644 --- a/ui/src/pages/Users/Personal/components/TopList/index.tsx +++ b/ui/src/pages/Users/Personal/components/TopList/index.tsx @@ -2,7 +2,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Icon } from '@answer/components'; +import { Icon } from '@/components'; interface Props { data: any[]; diff --git a/ui/src/pages/Users/Personal/components/UserInfo/index.tsx b/ui/src/pages/Users/Personal/components/UserInfo/index.tsx index 97caca3e..880b810f 100644 --- a/ui/src/pages/Users/Personal/components/UserInfo/index.tsx +++ b/ui/src/pages/Users/Personal/components/UserInfo/index.tsx @@ -3,8 +3,8 @@ import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { Avatar, Icon } from '@answer/components'; -import type { UserInfoRes } from '@answer/common/interface'; +import { Avatar, Icon } from '@/components'; +import type { UserInfoRes } from '@/common/interface'; interface Props { data: UserInfoRes; diff --git a/ui/src/pages/Users/Personal/components/Votes/index.tsx b/ui/src/pages/Users/Personal/components/Votes/index.tsx index 308af877..fa5023db 100644 --- a/ui/src/pages/Users/Personal/components/Votes/index.tsx +++ b/ui/src/pages/Users/Personal/components/Votes/index.tsx @@ -1,7 +1,7 @@ import { FC, memo } from 'react'; import { ListGroup, ListGroupItem } from 'react-bootstrap'; -import { FormatTime } from '@answer/components'; +import { FormatTime } from '@/components'; interface Props { visible: boolean; diff --git a/ui/src/pages/Users/Personal/index.tsx b/ui/src/pages/Users/Personal/index.tsx index fcab49ff..4720db93 100644 --- a/ui/src/pages/Users/Personal/index.tsx +++ b/ui/src/pages/Users/Personal/index.tsx @@ -3,8 +3,13 @@ import { Container, Row, Col, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'react-router-dom'; -import { Pagination, FormatTime, PageTitle, Empty } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; +import { Pagination, FormatTime, PageTitle, Empty } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import { + usePersonalInfoByName, + usePersonalTop, + usePersonalListByTabName, +} from '@/services'; import { UserInfo, @@ -19,12 +24,6 @@ import { Votes, } from './components'; -import { - usePersonalInfoByName, - usePersonalTop, - usePersonalListByTabName, -} from '@/services'; - const Personal: FC = () => { const { tabName = 'overview', username = '' } = useParams(); const [searchParams] = useSearchParams(); diff --git a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx index 4f072fe7..0d92c5fa 100644 --- a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx +++ b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx @@ -3,8 +3,7 @@ import { Form, Button, Col } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; -import type { FormDataType } from '@answer/common/interface'; - +import type { FormDataType } from '@/common/interface'; import { register } from '@/services'; import userStore from '@/stores/userInfo'; diff --git a/ui/src/pages/Users/Register/index.tsx b/ui/src/pages/Users/Register/index.tsx index 5f8370a0..e4d94b2d 100644 --- a/ui/src/pages/Users/Register/index.tsx +++ b/ui/src/pages/Users/Register/index.tsx @@ -2,12 +2,11 @@ import React, { useState, useEffect } from 'react'; import { Container } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { PageTitle, Unactivate } from '@answer/components'; +import { PageTitle, Unactivate } from '@/components'; +import { tryNormalLogged } from '@/utils/guard'; import SignUpForm from './components/SignUpForm'; -import { tryNormalLogged } from '@/utils/guards'; - const Index: React.FC = () => { const [showForm, setShowForm] = useState(true); const { t } = useTranslation('translation', { keyPrefix: 'login' }); diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx index 4ed6b0ff..8bfa6dfa 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyEmail/index.tsx @@ -2,9 +2,8 @@ import React, { FC, FormEvent, useEffect, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type * as Type from '@answer/common/interface'; -import { useToast } from '@answer/hooks'; - +import type * as Type from '@/common/interface'; +import { useToast } from '@/hooks'; import { getLoggedUserInfo, changeEmail } from '@/services'; const reg = /(?<=.{2}).+(?=@)/gi; diff --git a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx index 2c7ed9ef..56d8f17b 100644 --- a/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx +++ b/ui/src/pages/Users/Settings/Account/components/ModifyPass/index.tsx @@ -2,9 +2,8 @@ import React, { FC, FormEvent, useState } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { useToast } from '@answer/hooks'; -import type { FormDataType } from '@answer/common/interface'; - +import { useToast } from '@/hooks'; +import type { FormDataType } from '@/common/interface'; import { modifyPassword } from '@/services'; const Index: FC = () => { diff --git a/ui/src/pages/Users/Settings/Interface/index.tsx b/ui/src/pages/Users/Settings/Interface/index.tsx index f38f92e8..3a3941b9 100644 --- a/ui/src/pages/Users/Settings/Interface/index.tsx +++ b/ui/src/pages/Users/Settings/Interface/index.tsx @@ -6,9 +6,8 @@ import dayjs from 'dayjs'; import en from 'dayjs/locale/en'; import zh from 'dayjs/locale/zh-cn'; -import type { LangsType, FormDataType } from '@answer/common/interface'; -import { useToast } from '@answer/hooks'; - +import type { LangsType, FormDataType } from '@/common/interface'; +import { useToast } from '@/hooks'; import { languages } from '@/services'; import { DEFAULT_LANG, CURRENT_LANG_STORAGE_KEY } from '@/common/constants'; import Storage from '@/utils/storage'; diff --git a/ui/src/pages/Users/Settings/Notification/index.tsx b/ui/src/pages/Users/Settings/Notification/index.tsx index 6d0cab61..e7310145 100644 --- a/ui/src/pages/Users/Settings/Notification/index.tsx +++ b/ui/src/pages/Users/Settings/Notification/index.tsx @@ -2,9 +2,8 @@ import React, { useState, FormEvent, useEffect } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import type { FormDataType } from '@answer/common/interface'; -import { useToast } from '@answer/hooks'; - +import type { FormDataType } from '@/common/interface'; +import { useToast } from '@/hooks'; import { setNotice, getLoggedUserInfo } from '@/services'; const Index = () => { diff --git a/ui/src/pages/Users/Settings/Profile/index.tsx b/ui/src/pages/Users/Settings/Profile/index.tsx index 670bec91..a4442b08 100644 --- a/ui/src/pages/Users/Settings/Profile/index.tsx +++ b/ui/src/pages/Users/Settings/Profile/index.tsx @@ -5,11 +5,10 @@ import { Trans, useTranslation } from 'react-i18next'; import { marked } from 'marked'; import MD5 from 'md5'; -import type { FormDataType } from '@answer/common/interface'; -import { UploadImg, Avatar } from '@answer/components'; -import { loggedUserInfoStore } from '@answer/stores'; -import { useToast } from '@answer/hooks'; - +import type { FormDataType } from '@/common/interface'; +import { UploadImg, Avatar } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; +import { useToast } from '@/hooks'; import { modifyUserInfo, uploadAvatar, getLoggedUserInfo } from '@/services'; const Index: React.FC = () => { diff --git a/ui/src/pages/Users/Settings/index.tsx b/ui/src/pages/Users/Settings/index.tsx index 671707fe..dc591323 100644 --- a/ui/src/pages/Users/Settings/index.tsx +++ b/ui/src/pages/Users/Settings/index.tsx @@ -3,13 +3,12 @@ import { Container, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; -import type { FormDataType } from '@answer/common/interface'; - -import Nav from './components/Nav'; - +import type { FormDataType } from '@/common/interface'; import { getLoggedUserInfo } from '@/services'; import { PageTitle } from '@/components'; +import Nav from './components/Nav'; + const Index: React.FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'settings.profile', diff --git a/ui/src/pages/Users/Suspended/index.tsx b/ui/src/pages/Users/Suspended/index.tsx index 4c381d44..403595a9 100644 --- a/ui/src/pages/Users/Suspended/index.tsx +++ b/ui/src/pages/Users/Suspended/index.tsx @@ -1,7 +1,6 @@ import { useTranslation } from 'react-i18next'; -import { loggedUserInfoStore } from '@answer/stores'; - +import { loggedUserInfoStore } from '@/stores'; import { PageTitle } from '@/components'; const Suspended = () => { diff --git a/ui/src/router/guarder.ts b/ui/src/router/guarder.ts deleted file mode 100644 index e3b81598..00000000 --- a/ui/src/router/guarder.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - pullLoggedUser, - isLoggedAndNormal, - isAdminLogged, - isNotLogged, - isNotLoggedOrNormal, - isLoggedAndInactive, - isLoggedAndSuspended, - isNotLoggedOrInactive, - isNotLoggedOrNotSuspend, -} from '@/utils/guards'; - -const RouteGuarder = { - base: async () => { - return isNotLoggedOrNotSuspend(); - }, - loggedAndNormal: async () => { - await pullLoggedUser(true); - return isLoggedAndNormal(); - }, - loggedAndInactive: async () => { - return isLoggedAndInactive(); - }, - loggedAndSuspended: async () => { - return isLoggedAndSuspended(); - }, - adminLogged: async () => { - await pullLoggedUser(true); - return isAdminLogged(); - }, - notLogged: async () => { - return isNotLogged(); - }, - notLoggedOrNormal: async () => { - return isNotLoggedOrNormal(); - }, - notLoggedOrInactive: async () => { - return isNotLoggedOrInactive(); - }, -}; - -export default RouteGuarder; diff --git a/ui/src/router/index.tsx b/ui/src/router/index.tsx index 84eb37a6..e5aa2797 100644 --- a/ui/src/router/index.tsx +++ b/ui/src/router/index.tsx @@ -28,18 +28,19 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteObject[]) => { } root.push(rn); if (rn.guard) { - const { guard } = rn; - const loaderRef = rn.loader; + const refLoader = rn.loader; + const refGuard = rn.guard; rn.loader = async (args) => { - const gr = await guard(args); + const gr = await refGuard(); if (gr?.redirect && floppyNavigation.differentCurrent(gr.redirect)) { return redirect(gr.redirect); } - let ret; - if (typeof loaderRef === 'function') { - ret = await loaderRef(args); + + let lr; + if (typeof refLoader === 'function') { + lr = await refLoader(args); } - return ret; + return lr; }; } const children = Array.isArray(rn.children) ? rn.children : null; diff --git a/ui/src/router/routes.ts b/ui/src/router/routes.ts index 5b49fb1a..126dbac0 100644 --- a/ui/src/router/routes.ts +++ b/ui/src/router/routes.ts @@ -1,18 +1,28 @@ import { RouteObject } from 'react-router-dom'; -import RouteGuarder from '@/router/guarder'; +import { Guard } from '@/utils'; +import type { TGuardResult } from '@/utils/guard'; export interface RouteNode extends RouteObject { page: string; children?: RouteNode[]; - guard?: Function; + /** + * a method to auto guard route before route enter + * if the `ok` field in guard returned `TGuardResult` is true, + * it means the guard passed then enter the route. + * if guard returned the `TGuardResult` has `redirect` field, + * then auto redirect route to the `redirect` target. + */ + guard?: () => Promise; } const routes: RouteNode[] = [ { path: '/', page: 'pages/Layout', - guard: RouteGuarder.base, + guard: async () => { + return Guard.notForbidden(); + }, children: [ // question and answer { @@ -35,12 +45,16 @@ const routes: RouteNode[] = [ { path: 'questions/ask', page: 'pages/Questions/Ask', - guard: RouteGuarder.loggedAndNormal, + guard: async () => { + return Guard.activated(); + }, }, { path: 'posts/:qid/edit', page: 'pages/Questions/Ask', - guard: RouteGuarder.loggedAndNormal, + guard: async () => { + return Guard.activated(); + }, }, { path: 'posts/:qid/:aid/edit', @@ -66,6 +80,9 @@ const routes: RouteNode[] = [ { path: 'tags/:tagId/edit', page: 'pages/Tags/Edit', + guard: async () => { + return Guard.activated(); + }, }, // users { @@ -79,6 +96,9 @@ const routes: RouteNode[] = [ { path: 'users/settings', page: 'pages/Users/Settings', + guard: async () => { + return Guard.logged(); + }, children: [ { index: true, @@ -109,55 +129,85 @@ const routes: RouteNode[] = [ { path: 'users/login', page: 'pages/Users/Login', - guard: RouteGuarder.notLoggedOrInactive, + guard: async () => { + const notLogged = Guard.notLogged(); + if (notLogged.ok) { + return notLogged; + } + return Guard.notActivated(); + }, }, { path: 'users/register', page: 'pages/Users/Register', - guard: RouteGuarder.notLogged, + guard: async () => { + return Guard.notLogged(); + }, }, { path: 'users/account-recovery', page: 'pages/Users/AccountForgot', - guard: RouteGuarder.loggedAndNormal, + guard: async () => { + return Guard.activated(); + }, }, { path: 'users/change-email', page: 'pages/Users/ChangeEmail', + // TODO: guard this (change email when user not activated) ? }, { path: 'users/password-reset', page: 'pages/Users/PasswordReset', - guard: RouteGuarder.loggedAndNormal, + guard: async () => { + return Guard.activated(); + }, }, - // TODO: guard '/account-activation/*', '/users/confirm-new-email' { path: 'users/account-activation', page: 'pages/Users/ActiveEmail', - guard: RouteGuarder.loggedAndInactive, + guard: async () => { + const notActivated = Guard.notActivated(); + if (notActivated.ok) { + return notActivated; + } + return Guard.notLogged(); + }, }, { path: 'users/account-activation/success', page: 'pages/Users/ActivationResult', + guard: async () => { + return Guard.activated(); + }, }, { path: '/users/account-activation/failed', page: 'pages/Users/ActivationResult', + guard: async () => { + return Guard.notActivated(); + }, }, { path: '/users/confirm-new-email', page: 'pages/Users/ConfirmNewEmail', + // TODO: guard this }, { path: '/users/account-suspended', page: 'pages/Users/Suspended', - guard: RouteGuarder.loggedAndSuspended, + guard: async () => { + return Guard.forbidden(); + }, }, // for admin { path: 'admin', page: 'pages/Admin', - guard: RouteGuarder.adminLogged, + guard: async () => { + await Guard.pullLoggedUser(true); + return Guard.admin(); + }, children: [ { index: true, diff --git a/ui/src/services/admin/answer.ts b/ui/src/services/admin/answer.ts index 6fd0fbb6..c3340502 100644 --- a/ui/src/services/admin/answer.ts +++ b/ui/src/services/admin/answer.ts @@ -1,8 +1,8 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const useAnswerSearch = (params: Type.AdminContentsReq) => { const apiUrl = `/answer/admin/api/answer/page?${qs.stringify(params)}`; diff --git a/ui/src/services/admin/flag.ts b/ui/src/services/admin/flag.ts index 710cb447..64ea59f7 100644 --- a/ui/src/services/admin/flag.ts +++ b/ui/src/services/admin/flag.ts @@ -1,8 +1,8 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const putReport = (params) => { return request.instance.put('/answer/admin/api/report', params); diff --git a/ui/src/services/admin/question.ts b/ui/src/services/admin/question.ts index a6308bf6..9e8d726a 100644 --- a/ui/src/services/admin/question.ts +++ b/ui/src/services/admin/question.ts @@ -1,8 +1,8 @@ import qs from 'qs'; import useSWR from 'swr'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const changeUserStatus = (params) => { return request.put('/answer/admin/api/user/status', params); diff --git a/ui/src/services/admin/settings.ts b/ui/src/services/admin/settings.ts index e1f486e7..274e24eb 100644 --- a/ui/src/services/admin/settings.ts +++ b/ui/src/services/admin/settings.ts @@ -1,7 +1,7 @@ import useSWR from 'swr'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const useGeneralSetting = () => { const apiUrl = `/answer/admin/api/siteinfo/general`; diff --git a/ui/src/services/client/activity.ts b/ui/src/services/client/activity.ts index 7b7b539c..6f1a4fdb 100644 --- a/ui/src/services/client/activity.ts +++ b/ui/src/services/client/activity.ts @@ -1,7 +1,7 @@ import useSWR from 'swr'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const useFollow = (params?: Type.FollowParams) => { const apiUrl = '/answer/api/v1/follow'; diff --git a/ui/src/services/client/notification.ts b/ui/src/services/client/notification.ts index dd9d880e..a849db0a 100644 --- a/ui/src/services/client/notification.ts +++ b/ui/src/services/client/notification.ts @@ -1,10 +1,9 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; - -import { tryNormalLogged } from '@/utils/guards'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; +import { tryNormalLogged } from '@/utils/guard'; export const useQueryNotifications = (params) => { const apiUrl = `/answer/api/v1/notification/page?${qs.stringify(params, { diff --git a/ui/src/services/client/personal.ts b/ui/src/services/client/personal.ts index b84e260f..6b61aaba 100644 --- a/ui/src/services/client/personal.ts +++ b/ui/src/services/client/personal.ts @@ -1,8 +1,8 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const usePersonalInfoByName = (username: string) => { const apiUrl = '/answer/api/v1/personal/user/info'; diff --git a/ui/src/services/client/question.ts b/ui/src/services/client/question.ts index c35fe544..c418f26c 100644 --- a/ui/src/services/client/question.ts +++ b/ui/src/services/client/question.ts @@ -1,8 +1,8 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const useQuestionList = (params: Type.QueryQuestionsReq) => { const apiUrl = `/answer/api/v1/question/page?${qs.stringify(params)}`; diff --git a/ui/src/services/client/search.ts b/ui/src/services/client/search.ts index f5fe86fe..8d380294 100644 --- a/ui/src/services/client/search.ts +++ b/ui/src/services/client/search.ts @@ -1,8 +1,8 @@ import useSWR from 'swr'; import qs from 'qs'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const useSearch = (params?: Type.SearchParams) => { const apiUrl = '/answer/api/v1/search'; diff --git a/ui/src/services/client/tag.ts b/ui/src/services/client/tag.ts index 634b4472..42b9f1ac 100644 --- a/ui/src/services/client/tag.ts +++ b/ui/src/services/client/tag.ts @@ -1,9 +1,8 @@ import useSWR from 'swr'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; - -import { tryNormalLogged } from '@/utils/guards'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; +import { tryNormalLogged } from '@/utils/guard'; export const deleteTag = (id) => { return request.delete('/answer/api/v1/tag', { diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts index 6f2cf83a..3e9e7872 100644 --- a/ui/src/services/common.ts +++ b/ui/src/services/common.ts @@ -1,8 +1,8 @@ import qs from 'qs'; import useSWR from 'swr'; -import request from '@answer/utils/request'; -import type * as Type from '@answer/common/interface'; +import request from '@/utils/request'; +import type * as Type from '@/common/interface'; export const uploadImage = (file) => { const form = new FormData(); diff --git a/ui/src/stores/userInfo.ts b/ui/src/stores/userInfo.ts index 9aa540b5..017c3149 100644 --- a/ui/src/stores/userInfo.ts +++ b/ui/src/stores/userInfo.ts @@ -1,8 +1,7 @@ import create from 'zustand'; -import type { UserInfoRes } from '@answer/common/interface'; -import Storage from '@answer/utils/storage'; - +import type { UserInfoRes } from '@/common/interface'; +import Storage from '@/utils/storage'; import { LOGGED_USER_STORAGE_KEY, LOGGED_TOKEN_STORAGE_KEY, @@ -15,6 +14,7 @@ interface UserInfoStore { } const initUser: UserInfoRes = { + access_token: '', username: '', avatar: '', rank: 0, diff --git a/ui/src/utils/floppyNavigation.ts b/ui/src/utils/floppyNavigation.ts index 7edbbaa0..68e6f8a5 100644 --- a/ui/src/utils/floppyNavigation.ts +++ b/ui/src/utils/floppyNavigation.ts @@ -25,7 +25,8 @@ const navigate = (pathname: string, callback: Function) => { const navigateToLogin = () => { const { pathname } = window.location; if (pathname !== RouteAlias.login && pathname !== RouteAlias.register) { - const redirectUrl = window.location.href; + const loc = window.location; + const redirectUrl = loc.href.replace(loc.origin, ''); Storage.set(REDIRECT_PATH_STORAGE_KEY, redirectUrl); } navigate(RouteAlias.login, () => { diff --git a/ui/src/utils/guard.ts b/ui/src/utils/guard.ts new file mode 100644 index 00000000..bc73ab9a --- /dev/null +++ b/ui/src/utils/guard.ts @@ -0,0 +1,182 @@ +import { getLoggedUserInfo } from '@/services'; +import { loggedUserInfoStore } from '@/stores'; +import { RouteAlias } from '@/router/alias'; +import Storage from '@/utils/storage'; +import { LOGGED_USER_STORAGE_KEY } from '@/common/constants'; + +import { floppyNavigation } from './floppyNavigation'; + +type TLoginState = { + isLogged: boolean; + isNotActivated: boolean; + isActivated: boolean; + isForbidden: boolean; + isNormal: boolean; + isAdmin: boolean; +}; + +export type TGuardResult = { + ok: boolean; + redirect?: string; +}; + +export const deriveLoginState = (): TLoginState => { + const ls: TLoginState = { + isLogged: false, + isNotActivated: false, + isActivated: false, + isForbidden: false, + isNormal: false, + isAdmin: false, + }; + const { user } = loggedUserInfoStore.getState(); + if (user.access_token) { + ls.isLogged = true; + } + if (ls.isLogged && user.mail_status === 1) { + ls.isActivated = true; + } + if (ls.isLogged && user.mail_status === 2) { + ls.isNotActivated = true; + } + if (ls.isLogged && user.status === 'forbidden') { + ls.isForbidden = true; + } + if (ls.isActivated && !ls.isForbidden) { + ls.isNormal = true; + } + if (ls.isNormal && user.is_admin === true) { + ls.isAdmin = true; + } + + return ls; +}; + +let pullLock = false; +let dedupeTimestamp = 0; +export const pullLoggedUser = async (forceRePull = false) => { + // only pull once if not force re-pull + if (pullLock && !forceRePull) { + return; + } + // dedupe pull requests in this time span in 10 seconds + if (Date.now() - dedupeTimestamp < 1000 * 10) { + return; + } + dedupeTimestamp = Date.now(); + const loggedUserInfo = await getLoggedUserInfo().catch((ex) => { + dedupeTimestamp = 0; + if (!deriveLoginState().isLogged) { + // load fallback userInfo from local storage + const storageLoggedUserInfo = Storage.get(LOGGED_USER_STORAGE_KEY); + if (storageLoggedUserInfo) { + loggedUserInfoStore.getState().update(storageLoggedUserInfo); + } + } + console.error(ex); + }); + if (loggedUserInfo) { + pullLock = true; + loggedUserInfoStore.getState().update(loggedUserInfo); + } +}; + +export const logged = () => { + const gr: TGuardResult = { ok: true }; + const us = deriveLoginState(); + if (!us.isLogged) { + gr.ok = false; + gr.redirect = RouteAlias.login; + } + return gr; +}; + +export const notLogged = () => { + const gr: TGuardResult = { ok: true }; + const us = deriveLoginState(); + if (us.isLogged) { + gr.ok = false; + gr.redirect = RouteAlias.home; + } + return gr; +}; + +export const notActivated = () => { + const gr = logged(); + const us = deriveLoginState(); + if (us.isActivated) { + gr.ok = false; + gr.redirect = RouteAlias.home; + } + return gr; +}; + +export const activated = () => { + const gr = logged(); + const us = deriveLoginState(); + if (us.isNotActivated) { + gr.ok = false; + gr.redirect = RouteAlias.activation; + } + return gr; +}; + +export const forbidden = () => { + const gr = logged(); + const us = deriveLoginState(); + if (gr.ok && !us.isForbidden) { + gr.ok = false; + gr.redirect = RouteAlias.home; + } + return gr; +}; + +export const notForbidden = () => { + const gr: TGuardResult = { ok: true }; + const us = deriveLoginState(); + if (us.isForbidden) { + gr.ok = false; + gr.redirect = RouteAlias.suspended; + } + return gr; +}; + +export const admin = () => { + const gr = logged(); + const us = deriveLoginState(); + if (gr.ok && !us.isAdmin) { + gr.ok = false; + gr.redirect = RouteAlias.home; + } + return gr; +}; + +/** + * try user was logged and all state ok + * @param autoLogin + */ +export const tryNormalLogged = (autoLogin: boolean = false) => { + const us = deriveLoginState(); + + if (us.isNormal) { + return true; + } + // must assert logged state first and return + if (!us.isLogged) { + if (autoLogin) { + floppyNavigation.navigateToLogin(); + } + return false; + } + if (us.isNotActivated) { + floppyNavigation.navigate(RouteAlias.activation, () => { + window.location.href = RouteAlias.activation; + }); + } else if (us.isForbidden) { + floppyNavigation.navigate(RouteAlias.suspended, () => { + window.location.replace(RouteAlias.suspended); + }); + } + + return false; +}; diff --git a/ui/src/utils/guards.ts b/ui/src/utils/guards.ts deleted file mode 100644 index 47f79605..00000000 --- a/ui/src/utils/guards.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { getLoggedUserInfo } from '@/services'; -import { loggedUserInfoStore } from '@/stores'; -import { RouteAlias } from '@/router/alias'; -import Storage from '@/utils/storage'; -import { LOGGED_USER_STORAGE_KEY } from '@/common/constants'; -import { floppyNavigation } from '@/utils/floppyNavigation'; - -type UserStat = { - isLogged: boolean; - isActivated: boolean; - isSuspended: boolean; - isNormal: boolean; - isAdmin: boolean; -}; -export const deriveUserStat = (): UserStat => { - const stat: UserStat = { - isLogged: false, - isActivated: false, - isSuspended: false, - isNormal: false, - isAdmin: false, - }; - const { user } = loggedUserInfoStore.getState(); - if (user.id && user.username) { - stat.isLogged = true; - } - if (stat.isLogged && user.mail_status === 1) { - stat.isActivated = true; - } - if (stat.isLogged && user.status === 'forbidden') { - stat.isSuspended = true; - } - if (stat.isLogged && stat.isActivated && !stat.isSuspended) { - stat.isNormal = true; - } - if (stat.isNormal && user.is_admin === true) { - stat.isAdmin = true; - } - - return stat; -}; - -type GuardResult = { - ok: boolean; - redirect?: string; -}; -let pullLock = false; -let dedupeTimestamp = 0; -export const pullLoggedUser = async (forceRePull = false) => { - // only pull once if not force re-pull - if (pullLock && !forceRePull) { - return; - } - // dedupe pull requests in this time span in 10 seconds - if (Date.now() - dedupeTimestamp < 1000 * 10) { - return; - } - dedupeTimestamp = Date.now(); - const loggedUserInfo = await getLoggedUserInfo().catch((ex) => { - dedupeTimestamp = 0; - if (!deriveUserStat().isLogged) { - // load fallback userInfo from local storage - const storageLoggedUserInfo = Storage.get(LOGGED_USER_STORAGE_KEY); - if (storageLoggedUserInfo) { - loggedUserInfoStore.getState().update(storageLoggedUserInfo); - } - } - console.error(ex); - }); - if (loggedUserInfo) { - pullLock = true; - loggedUserInfoStore.getState().update(loggedUserInfo); - } -}; - -export const isLogged = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (!userStat.isLogged) { - ret.ok = false; - ret.redirect = RouteAlias.login; - } - return ret; -}; - -export const isNotLogged = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (userStat.isLogged) { - ret.ok = false; - ret.redirect = RouteAlias.home; - } - return ret; -}; - -export const isLoggedAndInactive = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (userStat.isActivated) { - ret.ok = false; - ret.redirect = RouteAlias.home; - } - return ret; -}; - -export const isLoggedAndSuspended = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (!userStat.isSuspended) { - ret.ok = false; - ret.redirect = RouteAlias.home; - } - return ret; -}; - -export const isLoggedAndNormal = () => { - const ret: GuardResult = { ok: false, redirect: undefined }; - const userStat = deriveUserStat(); - if (userStat.isNormal) { - ret.ok = true; - } else if (!userStat.isActivated) { - ret.redirect = RouteAlias.activation; - } else if (userStat.isSuspended) { - ret.redirect = RouteAlias.suspended; - } else if (!userStat.isLogged) { - ret.redirect = RouteAlias.login; - } - return ret; -}; - -export const isNotLoggedOrNormal = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - const gr = isLoggedAndNormal(); - if (!gr.ok && userStat.isLogged) { - ret.ok = false; - ret.redirect = gr.redirect; - } - return ret; -}; - -export const isNotLoggedOrNotSuspend = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - const gr = isLoggedAndNormal(); - if (!gr.ok && userStat.isSuspended) { - ret.ok = false; - ret.redirect = gr.redirect; - } - return ret; -}; - -export const isNotLoggedOrInactive = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (userStat.isActivated) { - ret.ok = false; - ret.redirect = RouteAlias.home; - } else if (userStat.isSuspended) { - ret.ok = false; - ret.redirect = RouteAlias.suspended; - } - return ret; -}; - -export const isAdminLogged = () => { - const ret: GuardResult = { ok: true, redirect: undefined }; - const userStat = deriveUserStat(); - if (!userStat.isAdmin) { - ret.redirect = RouteAlias.home; - ret.ok = false; - } - return ret; -}; - -/** - * try user was logged and all state ok - * @param autoLogin - */ -export const tryNormalLogged = (autoLogin: boolean = false) => { - const gr = isLoggedAndNormal(); - if (gr.ok) { - return true; - } - - if (gr.redirect === RouteAlias.login && autoLogin) { - floppyNavigation.navigateToLogin(); - } else if (gr.redirect) { - floppyNavigation.navigate(gr.redirect, () => { - // @ts-ignore - window.location.replace(gr.redirect); - }); - } - - return false; -}; diff --git a/ui/src/utils/index.ts b/ui/src/utils/index.ts index a1eaf02c..69f70696 100644 --- a/ui/src/utils/index.ts +++ b/ui/src/utils/index.ts @@ -1,6 +1,6 @@ -export * from './common'; -export * as guards from './guards'; - export { default as request } from './request'; export { default as Storage } from './storage'; export { floppyNavigation } from './floppyNavigation'; + +export * as Guard from './guard'; +export * from './common'; diff --git a/ui/src/utils/request.ts b/ui/src/utils/request.ts index d80cadf5..4e878cb3 100644 --- a/ui/src/utils/request.ts +++ b/ui/src/utils/request.ts @@ -1,12 +1,8 @@ import axios, { AxiosResponse } from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; -import { Modal } from '@answer/components'; -import { loggedUserInfoStore, toastStore } from '@answer/stores'; - -import Storage from './storage'; -import { floppyNavigation } from './floppyNavigation'; - +import { Modal } from '@/components'; +import { loggedUserInfoStore, toastStore } from '@/stores'; import { LOGGED_TOKEN_STORAGE_KEY, CURRENT_LANG_STORAGE_KEY, @@ -14,6 +10,9 @@ import { } from '@/common/constants'; import { RouteAlias } from '@/router/alias'; +import Storage from './storage'; +import { floppyNavigation } from './floppyNavigation'; + const API = { development: '', production: '', diff --git a/ui/tsconfig.json b/ui/tsconfig.json index d7c5decf..f270b720 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -20,18 +20,7 @@ "jsx": "react-jsx", "baseUrl": "./", "paths": { - "@/*": ["src/*"], - "@answer/pages/*": ["src/pages/*"], - "@answer/components": ["src/components/index.ts"], - "@answer/components/*": ["src/components/*"], - "@answer/stores": ["src/stores"], - "@answer/stores/*": ["src/stores/*"], - "@answer/services/*": ["src/services/*"], - "@answer/hooks": ["src/hooks"], - "@answer/common": ["src/common"], - "@answer/common/*": ["src/common/*"], - "@answer/utils": ["src/utils"], - "@answer/utils/*": ["src/utils/*"] + "@/*": ["src/*"] } }, "include": ["src", "node_modules/@testing-library/jest-dom"] From f196972fff1e87f678447b09229d4e44e659a7ae Mon Sep 17 00:00:00 2001 From: "haitao(lj)" Date: Wed, 2 Nov 2022 17:23:41 +0800 Subject: [PATCH 16/22] ci(admin/interface): fix interface merge issue --- ui/src/pages/Admin/Interface/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx index ec8c770e..395b9616 100644 --- a/ui/src/pages/Admin/Interface/index.tsx +++ b/ui/src/pages/Admin/Interface/index.tsx @@ -8,11 +8,9 @@ import { FormDataType, AdminSettingsInterface, } from '@/common/interface'; - import { interfaceStore } from '@/stores'; import { UploadImg } from '@/components'; import { TIMEZONES, DEFAULT_TIMEZONE } from '@/common/constants'; - import { languages, uploadAvatar, From bf4e9824f59a1006d7c029163c9d5be84725a911 Mon Sep 17 00:00:00 2001 From: shuai Date: Wed, 2 Nov 2022 17:26:55 +0800 Subject: [PATCH 17/22] fix: handle install page forms --- ui/src/common/interface.ts | 2 +- ui/src/components/Header/index.scss | 13 + ui/src/components/Header/index.tsx | 4 +- ui/src/i18n/locales/en.json | 31 ++- ui/src/index.scss | 1 + .../Install/components/FirstStep/index.tsx | 58 ++++- .../Install/components/FourthStep/index.tsx | 172 +++++++++++- .../Install/components/SecondStep/index.tsx | 245 ++++++++++++++++-- .../Install/components/ThirdStep/index.tsx | 5 +- ui/src/pages/Install/index.tsx | 135 +++++++++- ui/src/pages/Maintenance/index.tsx | 22 +- ui/src/pages/Upgrade/index.tsx | 64 ++--- .../Personal/components/DefaultList/index.tsx | 2 +- .../Personal/components/NavBar/index.tsx | 5 +- .../Personal/components/UserInfo/index.tsx | 6 +- .../Users/Personal/components/Votes/index.tsx | 2 +- ui/src/pages/Users/Personal/index.tsx | 6 +- 17 files changed, 651 insertions(+), 122 deletions(-) diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 7516a2d7..732964d1 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -264,7 +264,7 @@ export interface AdminSettingsInterface { logo: string; language: string; theme: string; - time_zone: string; + time_zone?: string; } export interface AdminSettingsSmtp { diff --git a/ui/src/components/Header/index.scss b/ui/src/components/Header/index.scss index 348632a3..f3f67b78 100644 --- a/ui/src/components/Header/index.scss +++ b/ui/src/components/Header/index.scss @@ -50,6 +50,10 @@ @media (max-width: 992.9px) { #header { + .logo { + max-width: 93px; + max-height: auto; + } .nav-grow { flex-grow: 1!important; } @@ -65,3 +69,12 @@ } +@media (max-width: 576px) { + #header { + .logo { + max-width: 93px; + max-height: auto; + } + } +} + diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 748eb318..4de50cee 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -71,8 +71,8 @@ const Header: FC = () => { id="navBarToggle" /> -
- +
+ {interfaceInfo.logo ? ( void; + nextCallback: () => void; visible: boolean; } -const Index: FC = ({ visible }) => { +const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); + const [langs, setLangs] = useState(); + + const getLangs = async () => { + const res: LangsType[] = await languages(); + setLangs(res); + }; + + const handleSubmit = () => { + nextCallback(); + }; + + useEffect(() => { + getLangs(); + }, []); + if (!visible) return null; return ( -
- - {t('choose_lang.label')} - - + + + {t('lang.label')} + { + changeCallback({ + lang: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }}> + {langs?.map((item) => { + return ( + + ); + })}
- +
); diff --git a/ui/src/pages/Install/components/FourthStep/index.tsx b/ui/src/pages/Install/components/FourthStep/index.tsx index 25fb47ef..ccd44be2 100644 --- a/ui/src/pages/Install/components/FourthStep/index.tsx +++ b/ui/src/pages/Install/components/FourthStep/index.tsx @@ -1,50 +1,204 @@ -import { FC } from 'react'; +import { FC, FormEvent } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; +import type { FormDataType } from '@/common/interface'; import Progress from '../Progress'; interface Props { + data: FormDataType; + changeCallback: (value: FormDataType) => void; + nextCallback: () => void; visible: boolean; } -const Index: FC = ({ visible }) => { +const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); + const checkValidated = (): boolean => { + let bol = true; + const { + site_name, + contact_email, + admin_name, + admin_password, + admin_email, + } = data; + + if (!site_name.value) { + bol = false; + data.site_name = { + value: '', + isInvalid: true, + errorMsg: t('site_name.msg'), + }; + } + + if (!contact_email.value) { + bol = false; + data.contact_email = { + value: '', + isInvalid: true, + errorMsg: t('contact_email.msg'), + }; + } + + if (!admin_name.value) { + bol = false; + data.admin_name = { + value: '', + isInvalid: true, + errorMsg: t('admin_name.msg'), + }; + } + + if (!admin_password.value) { + bol = false; + data.admin_password = { + value: '', + isInvalid: true, + errorMsg: t('admin_password.msg'), + }; + } + + if (!admin_email.value) { + bol = false; + data.admin_email = { + value: '', + isInvalid: true, + errorMsg: t('admin_email.msg'), + }; + } + + changeCallback({ + ...data, + }); + return bol; + }; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (!checkValidated()) { + return; + } + nextCallback(); + }; + if (!visible) return null; return ( -
+
{t('site_information')}
{t('site_name.label')} - + { + changeCallback({ + site_name: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.site_name.errorMsg} + {t('contact_email.label')} - + { + changeCallback({ + contact_email: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> {t('contact_email.text')} + + {data.contact_email.errorMsg} +
{t('admin_account')}
{t('admin_name.label')} - + { + changeCallback({ + admin_name: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.admin_name.errorMsg} + {t('admin_password.label')} - + { + changeCallback({ + admin_password: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> {t('admin_password.text')} + + {data.admin_password.errorMsg} + {t('admin_email.label')} - + { + changeCallback({ + admin_email: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> {t('admin_email.text')} + + {data.admin_email.errorMsg} +
- +
); diff --git a/ui/src/pages/Install/components/SecondStep/index.tsx b/ui/src/pages/Install/components/SecondStep/index.tsx index 7b21ab73..6c97ec61 100644 --- a/ui/src/pages/Install/components/SecondStep/index.tsx +++ b/ui/src/pages/Install/components/SecondStep/index.tsx @@ -1,54 +1,243 @@ -import { FC } from 'react'; +import { FC, FormEvent } from 'react'; import { Form, Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import Progress from '../Progress'; +import type { FormDataType } from '@/common/interface'; interface Props { + data: FormDataType; + changeCallback: (value: FormDataType) => void; + nextCallback: () => void; visible: boolean; } -const Index: FC = ({ visible }) => { +const sqlData = [ + { + value: 'mysql', + label: 'MariaDB/MySQL', + }, + { + value: 'sqlite3', + label: 'SQLite', + }, + { + value: 'postgres', + label: 'PostgreSQL', + }, +]; + +const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); + const checkValidated = (): boolean => { + let bol = true; + const { db_type, db_username, db_password, db_host, db_name, db_file } = + data; + + if (db_type.value !== 'sqllite3') { + if (!db_username.value) { + bol = false; + data.db_username = { + value: '', + isInvalid: true, + errorMsg: t('db_username.msg'), + }; + } + + if (!db_password.value) { + bol = false; + data.db_password = { + value: '', + isInvalid: true, + errorMsg: t('db_password.msg'), + }; + } + + if (!db_host.value) { + bol = false; + data.db_host = { + value: '', + isInvalid: true, + errorMsg: t('db_host.msg'), + }; + } + + if (!db_name.value) { + bol = false; + data.db_name = { + value: '', + isInvalid: true, + errorMsg: t('db_name.msg'), + }; + } + } else if (!db_file.value) { + bol = false; + data.db_file = { + value: '', + isInvalid: true, + errorMsg: t('db_file.msg'), + }; + } + changeCallback({ + ...data, + }); + return bol; + }; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (!checkValidated()) { + return; + } + nextCallback(); + }; + if (!visible) return null; return ( -
+ - {t('database_engine.label')} - - + {t('db_type.label')} + { + changeCallback({ + db_type: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }}> + {sqlData.map((item) => { + return ( + + ); + })} + {data.db_type.value !== 'sqlite3' ? ( + <> + + {t('db_username.label')} + { + changeCallback({ + db_username: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.db_username.errorMsg} + + - - {t('username.label')} - - + + {t('db_password.label')} + { + changeCallback({ + db_password: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> - - {t('password.label')} - - + + {data.db_password.errorMsg} + + - - {t('database_host.label')} - - + + {t('db_host.label')} + { + changeCallback({ + db_host: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.db_host.errorMsg} + + - - {t('database_name.label')} - - - - - {t('table_prefix.label')} - - + + {t('db_name.label')} + { + changeCallback({ + db_name: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.db_name.errorMsg} + + + + ) : ( + + {t('db_file.label')} + { + changeCallback({ + db_file: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + + {data.db_file.errorMsg} + + + )}
- +
); diff --git a/ui/src/pages/Install/components/ThirdStep/index.tsx b/ui/src/pages/Install/components/ThirdStep/index.tsx index 4d3f702f..3b5ca08c 100644 --- a/ui/src/pages/Install/components/ThirdStep/index.tsx +++ b/ui/src/pages/Install/components/ThirdStep/index.tsx @@ -6,9 +6,10 @@ import Progress from '../Progress'; interface Props { visible: boolean; + nextCallback: () => void; } -const Index: FC = ({ visible }) => { +const Index: FC = ({ visible, nextCallback }) => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); if (!visible) return null; @@ -30,7 +31,7 @@ const Index: FC = ({ visible }) => {
{t('config_yaml.info')}
- +
); diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx index 8de7cb11..e8c40d5a 100644 --- a/ui/src/pages/Install/index.tsx +++ b/ui/src/pages/Install/index.tsx @@ -1,7 +1,11 @@ -import { FC, useState } from 'react'; +import { FC, useState, useEffect } from 'react'; import { Container, Row, Col, Card, Alert } from 'react-bootstrap'; import { useTranslation, Trans } from 'react-i18next'; +import type { FormDataType } from '@/common/interface'; +import { Storage } from '@/utils'; +import { PageTitle } from '@/components'; + import { FirstStep, SecondStep, @@ -10,14 +14,109 @@ import { Fifth, } from './components'; -import { PageTitle } from '@/components'; - const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); - const [step] = useState(7); + const [step, setStep] = useState(1); + const [showError] = useState(false); + + const [formData, setFormData] = useState({ + lang: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_type: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_username: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_password: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_host: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_name: { + value: '', + isInvalid: false, + errorMsg: '', + }, + db_file: { + value: '', + isInvalid: false, + errorMsg: '', + }, + + site_name: { + value: '', + isInvalid: false, + errorMsg: '', + }, + contact_email: { + value: '', + isInvalid: false, + errorMsg: '', + }, + admin_name: { + value: '', + isInvalid: false, + errorMsg: '', + }, + admin_password: { + value: '', + isInvalid: false, + errorMsg: '', + }, + admin_email: { + value: '', + isInvalid: false, + errorMsg: '', + }, + }); + + const handleChange = (params: FormDataType) => { + console.log(params); + setFormData({ ...formData, ...params }); + }; + + const handleStep = () => { + setStep((pre) => pre + 1); + }; + + // const handleSubmit = () => { + // const params = { + // lang: formData.lang.value, + // db_type: formData.db_type.value, + // db_username: formData.db_username.value, + // db_password: formData.db_password.value, + // db_host: formData.db_host.value, + // db_name: formData.db_name.value, + // db_file: formData.db_file.value, + // site_name: formData.site_name.value, + // contact_email: formData.contact_email.value, + // admin_name: formData.admin_name.value, + // admin_password: formData.admin_password.value, + // admin_email: formData.admin_email.value, + // }; + + // console.log(params); + // }; + + useEffect(() => { + console.log('step===', Storage.get('INSTALL_STEP')); + }, []); return ( -
+
@@ -25,14 +124,30 @@ const Index: FC = () => {

{t('title')}

- show error msg - + {showError && show error msg } - + - + - + + + {step === 6 && ( diff --git a/ui/src/pages/Maintenance/index.tsx b/ui/src/pages/Maintenance/index.tsx index 3a2c2d86..560108bf 100644 --- a/ui/src/pages/Maintenance/index.tsx +++ b/ui/src/pages/Maintenance/index.tsx @@ -8,15 +8,19 @@ const Index = () => { keyPrefix: 'page_maintenance', }); return ( - - -
- (=‘_‘=) -
-
{t('description')}
-
+
+ + +
+ (=‘_‘=) +
+
{t('description')}
+
+
); }; diff --git a/ui/src/pages/Upgrade/index.tsx b/ui/src/pages/Upgrade/index.tsx index aee2a37a..c65c9098 100644 --- a/ui/src/pages/Upgrade/index.tsx +++ b/ui/src/pages/Upgrade/index.tsx @@ -14,38 +14,40 @@ const Index = () => { setStep(2); }; return ( - - - -