fix: required tags and reserved tags

This commit is contained in:
shuai 2022-11-16 12:06:33 +08:00
parent bc904d8d59
commit 93b26cc383
12 changed files with 114 additions and 48 deletions

View File

@ -2,7 +2,6 @@
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -24,6 +24,8 @@ export interface ReportParams {
export interface TagBase {
display_name: string;
slug_name: string;
recommend: boolean;
reserved: boolean;
}
export interface Tag extends TagBase {
@ -303,8 +305,9 @@ export interface AdminSettingsLegal {
}
export interface AdminSettingsWrite {
recommend_tags: string;
recommend_tags: string[];
required_tag: string;
reserved_tags: string[];
}
/**

View File

@ -90,7 +90,21 @@ const SchemaForm: FC<IProps> = ({
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
const data = { ...formData, [name]: { ...formData[name], value } };
const data = {
...formData,
[name]: { ...formData[name], value, isInvalid: false },
};
if (onChange instanceof Function) {
onChange(data);
}
};
const handleSwitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;
const data = {
...formData,
[name]: { ...formData[name], value: checked, isInvalid: false },
};
if (onChange instanceof Function) {
onChange(data);
}
@ -155,6 +169,7 @@ const SchemaForm: FC<IProps> = ({
const errors = requiredValidator();
if (errors.length > 0) {
formData = errors.reduce((acc, cur) => {
console.log('schema.properties[cur]', cur);
acc[cur] = {
...formData[cur],
isInvalid: true,
@ -262,17 +277,21 @@ const SchemaForm: FC<IProps> = ({
}
if (widget === 'switch') {
console.log(formData[key]?.value, 'switch=====');
return (
<Form.Group key={title} className="mb-3" controlId={key}>
<Form.Label>{title}</Form.Label>
<Form.Check
required
id={title}
name={key}
type="switch"
label={title}
checked={formData[key]?.value}
feedback={formData[key]?.errorMsg}
feedbackType="invalid"
isInvalid={formData[key].isInvalid}
onChange={handleSwitchChange}
/>
<Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg}

View File

@ -14,7 +14,14 @@ const Index: FC<IProps> = ({ className = '', href, data }) => {
href =
href || `/tags/${data.main_tag_slug_name || data.slug_name}`.toLowerCase();
return (
<a href={href} className={classNames('badge-tag rounded-1', className)}>
<a
href={href}
className={classNames(
'badge-tag rounded-1',
data.reserved && 'badge-tag-reserved',
data.recommend && 'badge-tag-required',
className,
)}>
{data.slug_name}
</a>
);

View File

@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { FC, useState, useEffect } from 'react';
import { Dropdown, FormControl, Button, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
@ -95,17 +96,16 @@ const TagSelector: FC<IProps> = ({
}
}, [value]);
useEffect(() => {
if (!tag) {
setTags(null);
return;
}
queryTags(tag).then((res) => {
const fetchTags = (str) => {
queryTags(str).then((res) => {
const tagArray: Type.Tag[] = filterTags(res || []);
setTags(tagArray);
});
}, [tag]);
};
useEffect(() => {
fetchTags(tag);
}, [visibleMenu]);
const handleClick = (val: Type.Tag) => {
const findIndex = initialValue.findIndex(
@ -143,7 +143,9 @@ const TagSelector: FC<IProps> = ({
};
const handleSearch = async (e: React.ChangeEvent<HTMLInputElement>) => {
setTag(e.currentTarget.value.replace(';', ''));
const searchStr = e.currentTarget.value.replace(';', '');
setTag(searchStr);
fetchTags(searchStr);
};
const handleSelect = (eventKey) => {
@ -186,7 +188,9 @@ const TagSelector: FC<IProps> = ({
'm-1 text-nowrap d-flex align-items-center',
index === repeatIndex && 'warning',
)}
variant="outline-secondary"
variant={`outline-${
item.reserved ? 'danger' : item.recommend ? 'dark' : 'secondary'
}`}
size="sm">
{item.slug_name}
<span className="ms-1" onMouseUp={() => handleRemove(item)}>
@ -220,6 +224,14 @@ const TagSelector: FC<IProps> = ({
</Form>
</Dropdown.Header>
)}
{tags && tags.filter((v) => v.recommend)?.length > 0 && (
<Dropdown.Item
disabled
style={{ fontWeight: 500 }}
className="text-secondary">
Required tag (at least one)
</Dropdown.Item>
)}
{tags?.map((item, index) => {
return (

View File

@ -69,20 +69,32 @@ a {
padding: 1px 0.5rem 2px;
color: $blue-700;
height: 24px;
border: 1px solid rgba($blue-100, 0.5);
&:hover {
background: rgba($blue-100, 1);
}
}
.badge-tag-required {
background: rgba($gray-400, 0.5);
background: rgba($gray-200, 0.5);
color: $gray-700;
border: 1px solid $gray-400;
&:hover {
color: $gray-700;
background: rgba($gray-400, 1);
}
}
.badge-tag-reserved {
background: rgba($orange-100, 0.5);
color: $orange-700;
border: 1px solid $orange-400;
&:hover {
color: $orange-700;
background: rgba($orange-400, 1);
}
}
.divide-line {
border-bottom: 1px solid rgba(33, 37, 41, 0.25);
}

View File

@ -3,9 +3,11 @@ import { useTranslation } from 'react-i18next';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import type * as Type from '@/common/interface';
// import { useToast } from '@/hooks';
// import { siteInfoStore } from '@/stores';
import { useGeneralSetting } from '@/services';
import { useToast } from '@/hooks';
import {
getRequireAndReservedTag,
postRequireAndReservedTag,
} from '@/services';
import '../index.scss';
@ -13,13 +15,11 @@ const Legal: FC = () => {
const { t } = useTranslation('translation', {
keyPrefix: 'admin.write',
});
// const Toast = useToast();
const Toast = useToast();
// const updateSiteInfo = siteInfoStore((state) => state.update);
const { data: setting } = useGeneralSetting();
const schema: JSONSchema = {
title: t('page_title'),
required: ['terms_of_service', 'privacy_policy'],
properties: {
recommend_tags: {
type: 'string',
@ -62,38 +62,40 @@ const Legal: FC = () => {
evt.stopPropagation();
const reqParams: Type.AdminSettingsWrite = {
recommend_tags: formData.recommend_tags.value,
recommend_tags: formData.recommend_tags.value.trim().split('\n'),
required_tag: formData.required_tag.value,
reserved_tags: formData.reserved_tags.value.trim().split('\n'),
};
console.log(reqParams);
// updateGeneralSetting(reqParams)
// .then(() => {
// Toast.onShow({
// msg: t('update', { keyPrefix: 'toast' }),
// variant: 'success',
// });
// updateSiteInfo(reqParams);
// })
// .catch((err) => {
// if (err.isError && err.key) {
// formData[err.key].isInvalid = true;
// formData[err.key].errorMsg = err.value;
// }
// setFormData({ ...formData });
// });
postRequireAndReservedTag(reqParams)
.then(() => {
Toast.onShow({
msg: t('update', { keyPrefix: 'toast' }),
variant: 'success',
});
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
}
setFormData({ ...formData });
});
};
const initData = () => {
getRequireAndReservedTag().then((res) => {
formData.recommend_tags.value = res.recommend_tags.join('\n');
formData.required_tag.value = res.required_tag;
formData.reserved_tags.value = res.reserved_tags.join('\n');
setFormData({ ...formData });
});
};
useEffect(() => {
if (!setting) {
return;
}
const formMeta = {};
Object.keys(setting).forEach((k) => {
formMeta[k] = { ...formData[k], value: setting[k] };
});
setFormData({ ...formData, ...formMeta });
}, [setting]);
initData();
}, []);
const handleOnChange = (data) => {
setFormData(data);

View File

@ -4,12 +4,13 @@ import { Helmet, HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
import { siteInfoStore, toastStore } from '@/stores';
import { siteInfoStore, toastStore, brandingStore } from '@/stores';
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 closeToast = () => {
toastClear();
};
@ -17,6 +18,7 @@ const Layout: FC = () => {
return (
<HelmetProvider>
<Helmet>
<link rel="icon" href={favicon || '/favicon.ico'} />
{siteInfo && <meta name="description" content={siteInfo.description} />}
</Helmet>
<SWRConfig

View File

@ -159,6 +159,8 @@ const TagIntroduction = () => {
slug_name: tagName || '',
main_tag_slug_name: '',
display_name: '',
recommend: false,
reserved: false,
}}
/>
</div>

View File

@ -96,3 +96,11 @@ export const getBrandSetting = () => {
export const brandSetting = (params: Type.AdmingSettingBranding) => {
return request.put('/answer/admin/api/siteinfo/branding', params);
};
export const getRequireAndReservedTag = () => {
return request.get('/answer/admin/api/siteinfo/write');
};
export const postRequireAndReservedTag = (params) => {
return request.put('/answer/admin/api/siteinfo/write', params);
};

View File

@ -49,7 +49,7 @@ class Request {
},
(error) => {
const { status, data: respData, msg: respMsg } = error.response || {};
const { data, msg = '' } = respData;
const { data = {}, msg = '' } = respData || {};
if (status === 400) {
// show error message
if (data instanceof Object && data.err_type) {