Merge branch 'feat/ui-v0.4' into 'test'

Feat/ui v0.4

See merge request opensource/answer!253
This commit is contained in:
Li Shuailing 2022-11-17 11:08:43 +00:00
commit d39759007d
30 changed files with 178 additions and 107 deletions

View File

@ -82,8 +82,8 @@ const Header: FC = () => {
/>
<img
className="logo d-block d-lg-none rounded-1 me-0"
src={brandingInfo.mobile_logo || brandingInfo.mobile_logo}
className="lg-none logo rounded-1 me-0"
src={brandingInfo.mobile_logo || brandingInfo.logo}
alt=""
/>
</>

View File

@ -11,8 +11,10 @@ interface IProps {
}
const Index: FC<IProps> = ({ className = '', href, data }) => {
href =
href || `/tags/${data.main_tag_slug_name || data.slug_name}`.toLowerCase();
href ||= `/tags/${encodeURIComponent(
data.main_tag_slug_name || data.slug_name,
)}`.toLowerCase();
return (
<a
href={href}

View File

@ -13,6 +13,11 @@
display: none;
}
}
.dropdown-item.active {
color: #212529;
background-color: #e9ecef;
}
@-webkit-keyframes tag-input-warning {
0% {
background-color: #ffc107;

View File

@ -9,6 +9,7 @@ import { loggedUserInfoStore } from '@/stores';
import { resendEmail, checkImgCode } from '@/services';
import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants';
import Storage from '@/utils/storage';
import { handleFormError } from '@/utils';
interface IProps {
visible: boolean;
@ -58,11 +59,10 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
setModalState(false);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err.list, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
})
.finally(() => {
getImgCode();

View File

@ -66,29 +66,28 @@ a {
display: inline-block;
font-size: 14px;
background: rgba($blue-100, 0.5);
padding: 1px 0.5rem 2px;
padding: 1px 7px;
color: $blue-700;
height: 24px;
border: 1px solid rgba($blue-100, 0.5);
border: 1px solid transparent;
&:hover {
background: rgba($blue-100, 1);
background: $blue-100;
}
}
.badge-tag-required {
background: rgba($gray-200, 0.5);
background: $gray-200;
color: $gray-700;
border: 1px solid $gray-400;
&:hover {
color: $gray-700;
background: rgba($gray-200, 1);
background: $gray-300;
}
}
.badge-tag-reserved {
background: rgba($orange-100, 0.5);
color: $orange-700;
border: 1px solid $orange-400;
border: 1px solid $orange-100;
&:hover {
color: $orange-700;
background: rgba($orange-100, 1);

View File

@ -6,6 +6,7 @@ import { FormDataType } from '@/common/interface';
import { brandSetting, getBrandSetting } from '@/services';
import { brandingStore } from '@/stores';
import { useToast } from '@/hooks';
import { handleFormError } from '@/utils';
const uploadType = 'branding';
const Index: FC = () => {
@ -113,11 +114,9 @@ const Index: FC = () => {
});
})
.catch((err) => {
console.log(err);
if (err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
setFormData({ ...formData });
if (err.isError) {
const data = handleFormError(err.list, formData);
setFormData({ ...data });
}
});
};

View File

@ -7,6 +7,7 @@ import { useToast } from '@/hooks';
import { siteInfoStore } from '@/stores';
import { useGeneralSetting, updateGeneralSetting } from '@/services';
import Pattern from '@/common/pattern';
import { handleFormError } from '@/utils';
import '../index.scss';
@ -106,11 +107,10 @@ const General: FC = () => {
updateSiteInfo(reqParams);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -20,6 +20,7 @@ import {
loadLanguageOptions,
setupAppTimeZone,
} from '@/utils/localize';
import { handleFormError } from '@/utils';
const Interface: FC = () => {
const { t } = useTranslation('translation', {
@ -162,11 +163,10 @@ const Interface: FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};
// const imgUpload = (file: any) => {

View File

@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { marked } from 'marked';
@ -7,6 +7,7 @@ import type * as Type from '@/common/interface';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
import { getLegalSetting, putLegalSetting } from '@/services';
import { handleFormError } from '@/utils';
import '../index.scss';
const Legal: FC = () => {
@ -68,11 +69,10 @@ const Legal: FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -7,6 +7,7 @@ import { useSmtpSetting, updateSmtpSetting } from '@/services';
import pattern from '@/common/pattern';
import { SchemaForm, JSONSchema, UISchema } from '@/components';
import { initFormData } from '../../../components/SchemaForm/index';
import { handleFormError } from '@/utils';
const Smtp: FC = () => {
const { t } = useTranslation('translation', {
@ -128,11 +129,10 @@ const Smtp: FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -8,6 +8,7 @@ import {
getRequireAndReservedTag,
postRequireAndReservedTag,
} from '@/services';
import { handleFormError } from '@/utils';
import '../index.scss';
@ -76,11 +77,10 @@ const Legal: FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -11,7 +11,7 @@ import {
installBaseInfo,
checkConfigFileExists,
} from '@/services';
import { Storage } from '@/utils';
import { Storage, handleFormError } from '@/utils';
import { CURRENT_LANG_STORAGE_KEY } from '@/common/constants';
import {
@ -175,10 +175,9 @@ const Index: FC = () => {
handleNext();
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
setFormData({ ...formData });
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
} else {
handleErr(err);
}

View File

@ -10,7 +10,8 @@ import { Header, Footer, Toast } from '@/components';
const Layout: FC = () => {
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
const { siteInfo } = siteInfoStore.getState();
const { favicon } = brandingStore((state) => state.branding);
const { favicon, square_icon } = brandingStore((state) => state.branding);
const closeToast = () => {
toastClear();
};
@ -18,7 +19,14 @@ const Layout: FC = () => {
return (
<HelmetProvider>
<Helmet>
<link rel="icon" href={favicon || '/favicon.ico'} />
<link
rel="icon"
type="image/png"
href={favicon || square_icon || '/favicon.ico'}
/>
<link rel="icon" type="image/png" sizes="192x192" href={square_icon} />
<link rel="apple-touch-icon" type="image/png" href={square_icon} />
{siteInfo && <meta name="description" content={siteInfo.description} />}
</Helmet>
<SWRConfig

View File

@ -4,6 +4,18 @@ import { useLegalPrivacy } from '@/services';
const Index: FC = () => {
const { data: privacy } = useLegalPrivacy();
const contentText = privacy?.privacy_policy_original_text;
let matchUrl: URL | undefined;
try {
if (contentText) {
matchUrl = new URL(contentText);
}
// eslint-disable-next-line no-empty
} catch (ex) {}
if (matchUrl) {
window.location.replace(matchUrl.toString());
return null;
}
return (
<div
className="fmt fs-14"

View File

@ -4,6 +4,18 @@ import { useLegalTos } from '@/services';
const Index: FC = () => {
const { data: tos } = useLegalTos();
const contentText = tos?.terms_of_service_original_text;
let matchUrl: URL | undefined;
try {
if (contentText) {
matchUrl = new URL(contentText);
}
// eslint-disable-next-line no-empty
} catch (ex) {}
if (matchUrl) {
window.location.replace(matchUrl.toString());
return null;
}
return (
<div
className="fmt fs-14"

View File

@ -16,6 +16,7 @@ import {
postAnswer,
useQueryQuestionByTitle,
} from '@/services';
import { handleFormError } from '@/utils';
import SearchQuestion from './components/SearchQuestion';
@ -209,19 +210,17 @@ const Ask = () => {
navigate(`/questions/${qid}`);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
} else {
const res = await saveQuestion(params).catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
const id = res?.id;
@ -236,11 +235,10 @@ const Ask = () => {
navigate(`/questions/${id}`);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
} else {
navigate(`/questions/${id}`);

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
import { Pagination, PageTitle, Labels } from '@/components';
import { Pagination, PageTitle } from '@/components';
import { loggedUserInfoStore } from '@/stores';
import { scrollTop } from '@/utils';
import { usePageUsers } from '@/hooks';
@ -167,7 +167,6 @@ const Index = () => {
)}
</Col>
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
<Labels className="mb-4" />
<RelatedQuestions id={question?.id || ''} />
</Col>
</Row>

View File

@ -14,7 +14,7 @@ const Questions: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'tags' });
const navigate = useNavigate();
const routeParams = useParams();
const curTagName = routeParams.tagName;
const curTagName = routeParams.tagName || '';
const [tagInfo, setTagInfo] = useState<any>({});
const [tagFollow, setTagFollow] = useState<Type.FollowParams>();
const { data: tagResp } = useTagInfo({ name: curTagName });
@ -71,7 +71,11 @@ const Questions: FC = () => {
<p className="text-break">
{escapeRemove(tagInfo.excerpt) || t('no_description')}
<Link to={`/tags/${curTagName}/info`}> [{t('more')}]</Link>
<Link
to={`/tags/${encodeURIComponent(curTagName)}/info`}
className="ms-1">
[{t('more')}]
</Link>
</p>
<div className="box-ft">

View File

@ -154,7 +154,6 @@ const TagIntroduction = () => {
{t('synonyms.text')}{' '}
<Tag
className="me-2 mb-2"
href="#"
data={{
slug_name: tagName || '',
main_tag_slug_name: '',

View File

@ -9,6 +9,7 @@ import type {
} from '@/common/interface';
import { resetPassword, checkImgCode } from '@/services';
import { PicAuthCodeModal } from '@/components/Modal';
import { handleFormError } from '@/utils';
interface IProps {
visible: boolean;
@ -83,14 +84,21 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
setModalState(false);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.key.indexOf('captcha') < 0) {
// if (err.isError && err.key) {
// formData[err.key].isInvalid = true;
// formData[err.key].errorMsg = err.value;
// if (err.key.indexOf('captcha') < 0) {
// setModalState(false);
// }
// }
if (err.isError) {
const data = handleFormError(err, formData);
if (err.list.filter((v) => v.error_field.indexOf('captcha') < 0)) {
setModalState(false);
}
setFormData({ ...data });
}
setFormData({ ...formData });
})
.finally(() => {
getImgCode();

View File

@ -11,6 +11,7 @@ import type {
import { loggedUserInfoStore } from '@/stores';
import { changeEmail, checkImgCode } from '@/services';
import { PicAuthCodeModal } from '@/components/Modal';
import { handleFormError } from '@/utils';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
@ -84,14 +85,21 @@ const Index: FC = () => {
setModalState(false);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.key.indexOf('captcha') < 0) {
// if (err.isError && err.key) {
// formData[err.key].isInvalid = true;
// formData[err.key].errorMsg = err.value;
// if (err.key.indexOf('captcha') < 0) {
// setModalState(false);
// }
// }
// setFormData({ ...formData });
if (err.isError) {
const data = handleFormError(err, formData);
if (err.list.filter((v) => v.error_field.indexOf('captcha') < 0)) {
setModalState(false);
}
setFormData({ ...data });
}
setFormData({ ...formData });
})
.finally(() => {
getImgCode();

View File

@ -10,7 +10,7 @@ import type {
} from '@/common/interface';
import { PageTitle, Unactivate } from '@/components';
import { loggedUserInfoStore } from '@/stores';
import { guard, floppyNavigation } from '@/utils';
import { guard, floppyNavigation, handleFormError } from '@/utils';
import { login, checkImgCode } from '@/services';
import { REDIRECT_PATH_STORAGE_KEY } from '@/common/constants';
import { RouteAlias } from '@/router/alias';
@ -122,14 +122,22 @@ const Index: React.FC = () => {
setModalState(false);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.key.indexOf('captcha') < 0) {
// if (err.isError && err.key) {
// formData[err.key].isInvalid = true;
// formData[err.key].errorMsg = err.value;
// if (err.key.indexOf('captcha') < 0) {
// setModalState(false);
// }
// }
if (err.isError) {
console.log('err===', err);
const data = handleFormError(err, formData);
console.log('err===', data);
if (err.list.filter((v) => v.error_field.indexOf('captcha') < 0)) {
setModalState(false);
}
setFormData({ ...data });
}
setFormData({ ...formData });
setRefresh((pre) => pre + 1);
});
};

View File

@ -7,6 +7,7 @@ import { loggedUserInfoStore } from '@/stores';
import type { FormDataType } from '@/common/interface';
import { replacementPassword } from '@/services';
import { PageTitle } from '@/components';
import { handleFormError } from '@/utils';
const Index: React.FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'password_reset' });
@ -105,11 +106,10 @@ const Index: React.FC = () => {
setStep(2);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -6,6 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
import type { FormDataType } from '@/common/interface';
import { register, useLegalTos, useLegalPrivacy } from '@/services';
import userStore from '@/stores/userInfo';
import { handleFormError } from '@/utils';
interface Props {
callback: () => void;
@ -119,11 +120,10 @@ const Index: React.FC<Props> = ({ callback }) => {
callback();
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import type * as Type from '@/common/interface';
import { useToast } from '@/hooks';
import { getLoggedUserInfo, changeEmail } from '@/services';
import { handleFormError } from '@/utils';
const reg = /(?<=.{2}).+(?=@)/gi;
@ -74,11 +75,10 @@ const Index: FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData.e_mail.isInvalid = true;
formData.e_mail.errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useToast } from '@/hooks';
import type { FormDataType } from '@/common/interface';
import { modifyPassword } from '@/services';
import { handleFormError } from '@/utils';
const Index: FC = () => {
const { t } = useTranslation('translation', {
@ -118,11 +119,10 @@ const Index: FC = () => {
handleFormState();
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -10,6 +10,7 @@ import { UploadImg, Avatar } from '@/components';
import { loggedUserInfoStore } from '@/stores';
import { useToast } from '@/hooks';
import { modifyUserInfo, getLoggedUserInfo } from '@/services';
import { handleFormError } from '@/utils';
const Index: React.FC = () => {
const { t } = useTranslation('translation', {
@ -174,11 +175,10 @@ const Index: React.FC = () => {
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
setFormData({ ...formData });
});
};

View File

@ -41,6 +41,7 @@ export const useTagInfo = ({ id = '', name = '' }) => {
if (id) {
apiUrl = `/answer/api/v1/tag/?id=${id}`;
} else if (name) {
name = encodeURIComponent(name);
apiUrl = `/answer/api/v1/tag/?name=${name}`;
}
const { data, error } = useSWR<Type.TagInfo>(apiUrl, request.instance.get);

View File

@ -150,6 +150,19 @@ function labelStyle(color, hover) {
};
}
function handleFormError(
error: { list: Array<{ error_field: string; error_msg: string }> },
data: any,
) {
if (error.list?.length > 0) {
error.list.forEach((item) => {
data[item.error_field].isInvalid = true;
data[item.error_field].errorMsg = item.error_msg;
});
}
return data;
}
export {
thousandthDivision,
formatCount,
@ -161,4 +174,5 @@ export {
mixColor,
colorRgb,
labelStyle,
handleFormError,
};

View File

@ -78,13 +78,9 @@ class Request {
return Promise.reject(false);
}
if (
data instanceof Object &&
Object.keys(data).length > 0 &&
data.key
) {
if (data instanceof Array && data.length > 0) {
// handle form error
return Promise.reject({ ...data, isError: true });
return Promise.reject({ isError: true, list: data });
}
if (!data || Object.keys(data).length <= 0) {