Merge pull request #283 from answerdev/feat/1.0.8/ui

Feat/1.0.8/UI
This commit is contained in:
haitao.jarvis
2023-03-24 18:42:22 +08:00
committed by GitHub
16 changed files with 148 additions and 126 deletions
+23 -5
View File
@@ -1,30 +1,48 @@
import { memo } from 'react';
import { memo, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { usePageTags } from '@/hooks';
const Index = ({ httpCode = '' }) => {
const Index = ({ httpCode = '', errMsg = '' }) => {
const { t } = useTranslation('translation', { keyPrefix: 'page_error' });
useEffect(() => {
// auto height of container
const pageWrap = document.querySelector('.page-wrap');
if (pageWrap) {
// @ts-ignore
pageWrap.style.display = 'contents';
}
return () => {
if (pageWrap) {
// @ts-ignore
pageWrap.style.display = 'block';
}
};
}, []);
usePageTags({
title: t(`http_${httpCode}`, { keyPrefix: 'page_title' }),
});
return (
<>
<div className="d-flex flex-column flex-shrink-1 flex-grow-1 justify-content-center align-items-center">
<div
className="mb-4 text-secondary"
style={{ fontSize: '120px', lineHeight: 1.2 }}>
(=x=)
</div>
<h4 className="text-center">{t('http_error', { code: httpCode })}</h4>
<div className="text-center mb-3 fs-5">{t(`desc_${httpCode}`)}</div>
<div className="text-center mb-3 fs-5">
{errMsg || t(`desc_${httpCode}`)}
</div>
<div className="text-center">
<Link to="/" className="btn btn-link">
{t('back_home')}
</Link>
</div>
</>
</div>
);
};
-27
View File
@@ -1,27 +0,0 @@
/* eslint-disable import/no-unresolved */
import { useEffect } from 'react';
import { Container } from 'react-bootstrap';
import { HttpErrorContent } from '@/components';
const Index = () => {
useEffect(() => {
// auto height of container
const pageWrap = document.querySelector('.page-wrap');
pageWrap.style.display = 'contents';
return () => {
pageWrap.style.display = 'block';
};
}, []);
return (
<Container
className="d-flex flex-column justify-content-center align-items-center"
style={{ flex: 1 }}>
<HttpErrorContent httpCode="404" />
</Container>
);
};
export default Index;
+7
View File
@@ -0,0 +1,7 @@
import { HttpErrorContent } from '@/components';
const Index = () => {
return <HttpErrorContent httpCode="404" />;
};
export default Index;
-27
View File
@@ -1,27 +0,0 @@
/* eslint-disable import/no-unresolved */
import { useEffect } from 'react';
import { Container } from 'react-bootstrap';
import { HttpErrorContent } from '@/components';
const Index = () => {
useEffect(() => {
// auto height of container
const pageWrap = document.querySelector('.page-wrap');
pageWrap.style.display = 'contents';
return () => {
pageWrap.style.display = 'block';
};
}, []);
return (
<Container
className="d-flex flex-column justify-content-center align-items-center"
style={{ flex: 1 }}>
<HttpErrorContent httpCode="50X" />
</Container>
);
};
export default Index;
+7
View File
@@ -0,0 +1,7 @@
import { HttpErrorContent } from '@/components';
const Index = () => {
return <HttpErrorContent httpCode="50X" />;
};
export default Index;
+3 -9
View File
@@ -4,7 +4,7 @@ import { HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
import { toastStore, loginToContinueStore, errorCode } from '@/stores';
import { toastStore, loginToContinueStore, errorCodeStore } from '@/stores';
import {
Header,
Footer,
@@ -23,8 +23,7 @@ const Layout: FC = () => {
const closeToast = () => {
toastClear();
};
const { code: httpStatusCode, reset: httpStatusReset } = errorCode();
const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
const imgViewer = useImgViewer();
const { show: showLoginToContinueModal } = loginToContinueStore();
@@ -43,14 +42,9 @@ const Layout: FC = () => {
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
className="position-relative page-wrap"
style={{ display: httpStatusCode ? 'contents' : 'block' }}
onClick={imgViewer.checkClickForImgView}>
{httpStatusCode ? (
<div
className="d-flex flex-column justify-content-center align-items-center"
style={{ flex: 1 }}>
<HttpErrorContent httpCode={httpStatusCode} />
</div>
<HttpErrorContent httpCode={httpStatusCode} />
) : (
<Outlet />
)}
+3 -4
View File
@@ -1,8 +1,7 @@
import Error50X from '@/pages/50X';
// import Page404 from '@/pages/404';
import { HttpErrorContent } from '@/components';
const Index = () => {
return <Error50X />;
const Index = ({ errCode = '50X', errMsg = '' }) => {
return <HttpErrorContent httpCode={errCode} errMsg={errMsg} />;
};
export default Index;
+34 -22
View File
@@ -1,41 +1,53 @@
import { FC, ReactNode, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation, useNavigate, useLoaderData } from 'react-router-dom';
import { floppyNavigation } from '@/utils';
import { TGuardFunc } from '@/utils/guard';
const Index: FC<{
import RouteErrorBoundary from './RouteErrorBoundary';
const RouteGuard: FC<{
children: ReactNode;
onEnter?: TGuardFunc;
onEnter: TGuardFunc;
path?: string;
}> = ({
children,
onEnter,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
path,
}) => {
page?: string;
}> = ({ children, onEnter, path, page }) => {
const navigate = useNavigate();
const location = useLocation();
const callGuards = () => {
if (onEnter) {
const gr = onEnter();
const redirectUrl = gr.redirect;
if (redirectUrl) {
floppyNavigation.navigate(redirectUrl, () => {
navigate(redirectUrl, { replace: true });
});
}
const loaderData = useLoaderData();
const gr = onEnter({
loaderData,
path,
page,
});
let guardError;
const errCode = gr.error?.code;
if (errCode === '403' || errCode === '404' || errCode === '50X') {
guardError = {
code: errCode,
msg: gr.error?.msg,
};
}
const handleGuardRedirect = () => {
const redirectUrl = gr.redirect;
if (redirectUrl) {
floppyNavigation.navigate(redirectUrl, () => {
navigate(redirectUrl, { replace: true });
});
}
};
useEffect(() => {
callGuards();
handleGuardRedirect();
}, [location]);
return (
<>
{/* Route Guard */}
{children}
{gr.ok ? children : null}
{!gr.ok && guardError ? (
<RouteErrorBoundary errCode={guardError.code} />
) : null}
</>
);
};
export default Index;
export default RouteGuard;
+2 -2
View File
@@ -13,7 +13,7 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteNode[]) => {
routeNodes.forEach((rn) => {
if (rn.page === 'pages/Layout') {
rn.element = rn.guard ? (
<RouteGuard onEnter={rn.guard} path={rn.path}>
<RouteGuard onEnter={rn.guard} path={rn.path} page={rn.page}>
<Layout />
</RouteGuard>
) : (
@@ -30,7 +30,7 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteNode[]) => {
rn.element = (
<Suspense>
{rn.guard ? (
<RouteGuard onEnter={rn.guard} path={rn.path}>
<RouteGuard onEnter={rn.guard} path={rn.path} page={rn.page}>
<Ctrl />
</RouteGuard>
) : (
+9
View File
@@ -2,6 +2,8 @@ import type { IndexRouteObject, NonIndexRouteObject } from 'react-router-dom';
import { guard } from '@/utils';
import type { TGuardFunc } from '@/utils/guard';
import { editCheck } from '@/services';
import { isEditable } from '@/utils/guard';
type IndexRouteNode = Omit<IndexRouteObject, 'children'>;
type NonIndexRouteNode = Omit<NonIndexRouteObject, 'children'>;
@@ -70,6 +72,13 @@ const routes: RouteNode[] = [
{
path: 'posts/:qid/:aid/edit',
page: 'pages/Questions/EditAnswer',
loader: async ({ params }) => {
const ret = await editCheck(params.aid as string, true);
return ret;
},
guard: (args) => {
return isEditable(args);
},
},
{
path: '/search',
+4 -2
View File
@@ -1,9 +1,11 @@
import request from '@/utils/request';
import * as Type from '@/common/interface';
export const editCheck = (id: string) => {
export const editCheck = (id: string, passingError: boolean = false) => {
const apiUrl = `/answer/api/v1/revisions/edit/check?id=${id}`;
return request.get(apiUrl);
return request.get(apiUrl, {
passingError,
});
};
export const revisionAudit = (id: string, operation: 'approve' | 'reject') => {
+9 -7
View File
@@ -2,24 +2,26 @@ import create from 'zustand';
type codeType = '403' | '404' | '50X' | '';
interface NotFoundType {
interface ErrorCodeType {
code: codeType;
update: (code: codeType) => void;
msg: string;
update: (code: codeType, msg?: string) => void;
reset: () => void;
}
const notFound = create<NotFoundType>((set) => ({
const Index = create<ErrorCodeType>((set) => ({
code: '',
update: (code: codeType) => {
msg: '',
update: (code: codeType, msg: string = '') => {
set(() => {
return { code };
return { code, msg };
});
},
reset: () => {
set(() => {
return { code: '' };
return { code: '', msg: '' };
});
},
}));
export default notFound;
export default Index;
+2 -2
View File
@@ -10,7 +10,7 @@ import pageTagStore from './pageTags';
import customizeStore from './customize';
import themeSettingStore from './themeSetting';
import loginToContinueStore from './loginToContinue';
import errorCode from './errorCode';
import errorCodeStore from './errorCode';
export {
toastStore,
@@ -24,5 +24,5 @@ export {
themeSettingStore,
seoSettingStore,
loginToContinueStore,
errorCode,
errorCodeStore,
};
+2 -2
View File
@@ -7,7 +7,7 @@ interface IProps {
update: (params: AdminSettingsSeo) => void;
}
const siteInfo = create<IProps>((set) => ({
const Index = create<IProps>((set) => ({
seo: {
robots: '',
permalink: 1,
@@ -25,4 +25,4 @@ const siteInfo = create<IProps>((set) => ({
}),
}));
export default siteInfo;
export default Index;
+22 -1
View File
@@ -30,8 +30,16 @@ type TLoginState = {
export type TGuardResult = {
ok: boolean;
redirect?: string;
error?: {
code?: number | string;
msg?: string;
};
};
export type TGuardFunc = () => TGuardResult;
export type TGuardFunc = (args: {
loaderData?: any;
path?: string;
page?: string;
}) => TGuardResult;
export const deriveLoginState = (): TLoginState => {
const ls: TLoginState = {
@@ -189,6 +197,19 @@ export const isAdminOrModerator = () => {
return gr;
};
export const isEditable = (args) => {
const loaderData = args?.loaderData || {};
const gr: TGuardResult = { ok: true };
if (loaderData.code === 400) {
gr.ok = false;
gr.error = {
code: '403',
msg: loaderData.msg,
};
}
return gr;
};
export const allowNewRegistration = () => {
const gr: TGuardResult = { ok: true };
const loginSetting = loginSettingStore.getState().login;
+21 -16
View File
@@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
import { Modal } from '@/components';
import { loggedUserInfoStore, toastStore, errorCode } from '@/stores';
import { loggedUserInfoStore, toastStore, errorCodeStore } from '@/stores';
import { LOGGED_TOKEN_STORAGE_KEY, IGNORE_PATH_LIST } from '@/common/constants';
import { RouteAlias } from '@/router/alias';
import { getCurrentLang } from '@/utils/localize';
@@ -16,8 +16,11 @@ const baseConfig = {
withCredentials: true,
};
interface APIconfig extends AxiosRequestConfig {
allow404: boolean;
interface ApiConfig extends AxiosRequestConfig {
// Configure whether to allow takeover of 404 errors
allow404?: boolean;
// Configure whether to pass errors directly
passingError?: boolean;
}
class Request {
@@ -52,14 +55,17 @@ class Request {
return data;
},
(error) => {
const { status, data: respData } = error.response || {};
const { data = {}, msg = '', reason = '' } = respData || {};
// console.log('response error:', error);
const {
status,
data: errModel,
config: errConfig,
} = error.response || {};
const { data = {}, msg = '' } = errModel || {};
if (status === 400) {
// show error message
if (data instanceof Object && data.err_type) {
if (data.err_type && errConfig?.passingError) {
return errModel;
}
if (data.err_type) {
if (data.err_type === 'toast') {
// toast error message
toastStore.getState().show({
@@ -90,7 +96,6 @@ class Request {
return Promise.reject({
code: status,
msg,
reason,
isError: true,
list: data,
});
@@ -107,7 +112,7 @@ class Request {
// 401: Re-login required
if (status === 401) {
// clear userinfo
errorCode.getState().reset();
errorCodeStore.getState().reset();
loggedUserInfoStore.getState().clear();
floppyNavigation.navigateToLogin();
return Promise.reject(false);
@@ -140,7 +145,7 @@ class Request {
return Promise.reject(false);
}
if (error.config?.url.includes('/admin/api')) {
errorCode.getState().update('403');
errorCodeStore.getState().update('403');
return Promise.reject(false);
}
@@ -157,14 +162,14 @@ class Request {
if (isIgnoredPath(IGNORE_PATH_LIST)) {
return Promise.reject(false);
}
errorCode.getState().update('404');
errorCodeStore.getState().update('404');
return Promise.reject(false);
}
if (status >= 500) {
if (isIgnoredPath(IGNORE_PATH_LIST)) {
return Promise.reject(false);
}
errorCode.getState().update('50X');
errorCodeStore.getState().update('50X');
console.error(
`Request failed with status code ${status}, ${msg || ''}`,
);
@@ -178,7 +183,7 @@ class Request {
return this.instance.request(config);
}
public get<T = any>(url: string, config?: APIconfig): Promise<T> {
public get<T = any>(url: string, config?: ApiConfig): Promise<T> {
return this.instance.get(url, config);
}