mirror of https://gitee.com/answerdev/answer.git
refactor: AccordionNav, SchemaForm
This commit is contained in:
parent
c62436a703
commit
10afa0a47b
|
@ -1104,8 +1104,9 @@ ui:
|
|||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
css-html: CSS/HTML
|
||||
css_html: CSS/HTML
|
||||
login: Login
|
||||
privileges: Privileges
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
|
@ -1308,9 +1309,6 @@ ui:
|
|||
label: Timezone
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
avatar:
|
||||
label: Default Avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
|
@ -1454,7 +1452,13 @@ ui:
|
|||
deactivate: Deactivate
|
||||
activate: Activate
|
||||
settings: Settings
|
||||
|
||||
settings_users:
|
||||
title: Users
|
||||
avatar:
|
||||
label: Default Avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
profile_editable:
|
||||
title: Profile Editable
|
||||
|
||||
form:
|
||||
optional: (optional)
|
||||
|
|
|
@ -74,7 +74,8 @@ export const ADMIN_NAV_MENUS = [
|
|||
name: 'themes',
|
||||
},
|
||||
{
|
||||
name: 'css-html',
|
||||
name: 'css_html',
|
||||
path: 'css-html',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -89,6 +90,8 @@ export const ADMIN_NAV_MENUS = [
|
|||
{ name: 'write' },
|
||||
{ name: 'seo' },
|
||||
{ name: 'login' },
|
||||
{ name: 'users', path: 'settings-users' },
|
||||
{ name: 'privileges' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -96,6 +99,7 @@ export const ADMIN_NAV_MENUS = [
|
|||
children: [
|
||||
{
|
||||
name: 'installed_plugins',
|
||||
path: 'installed-plugins',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -312,7 +312,6 @@ export interface HelmetUpdate extends Omit<HelmetBase, 'pageTitle'> {
|
|||
export interface AdminSettingsInterface {
|
||||
language: string;
|
||||
time_zone?: string;
|
||||
default_avatar?: string;
|
||||
}
|
||||
|
||||
export interface AdminSettingsSmtp {
|
||||
|
|
|
@ -18,12 +18,12 @@ function MenuNode({
|
|||
}) {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'nav_menus' });
|
||||
const isLeaf = !menu.children.length;
|
||||
const href = isLeaf ? `${path}${menu.name}` : '#';
|
||||
const href = isLeaf ? `${path}${menu.path}` : '#';
|
||||
|
||||
return (
|
||||
<Nav.Item key={menu.name} className="w-100">
|
||||
<Nav.Item key={menu.path} className="w-100">
|
||||
<Nav.Link
|
||||
eventKey={menu.name}
|
||||
eventKey={menu.path}
|
||||
as={isLeaf ? 'a' : 'button'}
|
||||
onClick={(evt) => {
|
||||
callback(evt, menu, href, isLeaf);
|
||||
|
@ -31,7 +31,7 @@ function MenuNode({
|
|||
href={href}
|
||||
className={classNames(
|
||||
'text-nowrap d-flex flex-nowrap align-items-center w-100',
|
||||
{ expanding, 'link-dark': activeKey !== menu.name },
|
||||
{ expanding, 'link-dark': activeKey !== menu.path },
|
||||
)}>
|
||||
<span className="me-auto text-truncate">
|
||||
{menu.displayName ? menu.displayName : t(menu.name)}
|
||||
|
@ -44,7 +44,7 @@ function MenuNode({
|
|||
)}
|
||||
</Nav.Link>
|
||||
{menu.children.length ? (
|
||||
<Accordion.Collapse eventKey={menu.name} className="ms-3">
|
||||
<Accordion.Collapse eventKey={menu.path} className="ms-3">
|
||||
<>
|
||||
{menu.children.map((leaf) => {
|
||||
return (
|
||||
|
@ -53,7 +53,7 @@ function MenuNode({
|
|||
callback={callback}
|
||||
activeKey={activeKey}
|
||||
path={path}
|
||||
key={leaf.name}
|
||||
key={leaf.path}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -73,17 +73,24 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
const pathMatch = useMatch(`${path}*`);
|
||||
// auto set menu fields
|
||||
menus.forEach((m) => {
|
||||
if (!m.path) {
|
||||
m.path = m.name;
|
||||
}
|
||||
if (!Array.isArray(m.children)) {
|
||||
m.children = [];
|
||||
}
|
||||
m.children.forEach((sm) => {
|
||||
if (!sm.path) {
|
||||
sm.path = sm.name;
|
||||
}
|
||||
if (!Array.isArray(sm.children)) {
|
||||
sm.children = [];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const splat = pathMatch && pathMatch.params['*'];
|
||||
let activeKey = menus[0].name;
|
||||
let activeKey = menus[0].path;
|
||||
if (splat) {
|
||||
activeKey = splat;
|
||||
}
|
||||
|
@ -92,10 +99,10 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
menus.forEach((li) => {
|
||||
if (li.children.length) {
|
||||
const matchedChild = li.children.find((el) => {
|
||||
return el.name === activeKey;
|
||||
return el.path === activeKey;
|
||||
});
|
||||
if (matchedChild) {
|
||||
openKey = li.name;
|
||||
openKey = li.path;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -111,7 +118,7 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
navigate(href);
|
||||
}
|
||||
} else {
|
||||
setOpenKey(openKey === menu.name ? '' : menu.name);
|
||||
setOpenKey(openKey === menu.path ? '' : menu.path);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
|
@ -127,8 +134,8 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
path={path}
|
||||
callback={menuClick}
|
||||
activeKey={activeKey}
|
||||
expanding={openKey === li.name}
|
||||
key={li.name}
|
||||
expanding={openKey === li.path}
|
||||
key={li.path}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form, Stack } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
type: 'radio' | 'checkbox';
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLInputElement>, ...rest) => void;
|
||||
enumValues: (string | boolean | number)[];
|
||||
enumNames: string[];
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
type = 'radio',
|
||||
title,
|
||||
desc,
|
||||
fieldName,
|
||||
onChange,
|
||||
enumValues,
|
||||
enumNames,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Stack direction="horizontal">
|
||||
{enumValues?.map((item, index) => {
|
||||
return (
|
||||
<Form.Check
|
||||
key={String(item)}
|
||||
inline
|
||||
required
|
||||
type={type}
|
||||
name={fieldName}
|
||||
id={`form-${String(item)}`}
|
||||
label={enumNames?.[index]}
|
||||
checked={(fieldObject?.value || '') === item}
|
||||
feedback={fieldObject?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
onChange={(evt) => onChange(evt, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,45 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
type: string | undefined;
|
||||
placeholder: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLInputElement>, ...rest) => void;
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
type = 'text',
|
||||
desc,
|
||||
placeholder = '',
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
name={fieldName}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={onChange}
|
||||
style={type === 'color' ? { width: '6rem' } : {}}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,11 @@
|
|||
import { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
const Index: FC<Props> = ({ title }) => {
|
||||
return <Form.Label>{title}</Form.Label>;
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,50 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
enumValues: (string | boolean | number)[];
|
||||
enumNames: string[];
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
desc,
|
||||
fieldName,
|
||||
onChange,
|
||||
enumValues,
|
||||
enumNames,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Select
|
||||
aria-label={desc}
|
||||
name={fieldName}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={onChange}
|
||||
isInvalid={fieldObject?.isInvalid}>
|
||||
{enumValues?.map((item, index) => {
|
||||
return (
|
||||
<option value={String(item)} key={String(item)}>
|
||||
{enumNames?.[index]}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,46 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
label: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLInputElement>, ...rest) => void;
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
desc,
|
||||
fieldName,
|
||||
onChange,
|
||||
label,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Check
|
||||
required
|
||||
id={`switch-${title}`}
|
||||
name={fieldName}
|
||||
type="switch"
|
||||
label={label}
|
||||
checked={fieldObject?.value || ''}
|
||||
feedback={fieldObject?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={fieldObject.isInvalid}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,50 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
placeholder: string | undefined;
|
||||
rows: number | undefined;
|
||||
className: classnames.Argument;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLInputElement>, ...rest) => void;
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
desc,
|
||||
placeholder = '',
|
||||
rows = 3,
|
||||
className,
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name={fieldName}
|
||||
placeholder={placeholder}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={onChange}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
rows={rows}
|
||||
className={classnames(className)}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,33 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import TimeZonePicker from '@/components/TimeZonePicker';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
desc: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (evt: React.ChangeEvent<HTMLSelectElement>, ...rest) => void;
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({ title, desc, fieldName, onChange, formData }) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<TimeZonePicker
|
||||
value={fieldObject?.value || ''}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
name={fieldName}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,48 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import BrandUpload from '@/components/BrandUpload';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
type: Type.UploadType | undefined;
|
||||
acceptType: string | undefined;
|
||||
desc: string | undefined;
|
||||
fieldName: string;
|
||||
onChange: (key, val) => void;
|
||||
formData: Type.FormDataType;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
type = 'avatar',
|
||||
acceptType = '',
|
||||
desc,
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
return (
|
||||
<>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<BrandUpload
|
||||
type={type}
|
||||
acceptType={acceptType}
|
||||
value={fieldObject?.value}
|
||||
onChange={(value) => onChange(fieldName, value)}
|
||||
/>
|
||||
<Form.Control
|
||||
name={fieldName}
|
||||
className="d-none"
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{fieldObject?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{desc ? <Form.Text className="text-muted">{desc}</Form.Text> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,10 @@
|
|||
import Legend from './Legend';
|
||||
import Select from './Select';
|
||||
import Check from './Check';
|
||||
import Switch from './Switch';
|
||||
import Timezone from './Timezone';
|
||||
import Upload from './Upload';
|
||||
import Textarea from './Textarea';
|
||||
import Input from './Input';
|
||||
|
||||
export { Legend, Select, Check, Switch, Timezone, Upload, Textarea, Input };
|
|
@ -4,15 +4,24 @@ import {
|
|||
useImperativeHandle,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Form, Button, Stack } from 'react-bootstrap';
|
||||
import { Form, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import BrandUpload from '../BrandUpload';
|
||||
import TimeZonePicker from '../TimeZonePicker';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
import {
|
||||
Legend,
|
||||
Select,
|
||||
Check,
|
||||
Switch,
|
||||
Timezone,
|
||||
Upload,
|
||||
Textarea,
|
||||
Input,
|
||||
} from './components';
|
||||
|
||||
export interface JSONSchema {
|
||||
title: string;
|
||||
description?: string;
|
||||
|
@ -32,6 +41,7 @@ export interface JSONSchema {
|
|||
export interface BaseUIOptions {
|
||||
empty?: string;
|
||||
className?: string | string[];
|
||||
simplify?: boolean;
|
||||
validator?: (
|
||||
value,
|
||||
formData?,
|
||||
|
@ -96,7 +106,8 @@ export type UIWidget =
|
|||
| 'select'
|
||||
| 'upload'
|
||||
| 'timezone'
|
||||
| 'switch';
|
||||
| 'switch'
|
||||
| 'legend';
|
||||
export interface UISchema {
|
||||
[key: string]: {
|
||||
'ui:widget'?: UIWidget;
|
||||
|
@ -117,6 +128,11 @@ interface IRef {
|
|||
validator: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 1. Normalize and document `formData[key].hidden && 'd-none'`
|
||||
*/
|
||||
|
||||
/**
|
||||
* json schema form
|
||||
* @param schema json schema
|
||||
|
@ -349,217 +365,317 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
|||
return (
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
{keys.map((key) => {
|
||||
const { title, description } = properties[key];
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
enum: enumValues = [],
|
||||
enumNames = [],
|
||||
} = properties[key];
|
||||
const { 'ui:widget': widget = 'input', 'ui:options': uiOpt } =
|
||||
uiSchema[key] || {};
|
||||
if (widget === 'select') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
controlId={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Select
|
||||
aria-label={description}
|
||||
name={key}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleSelectChange}
|
||||
isInvalid={formData[key].isInvalid}>
|
||||
{properties[key].enum?.map((item, index) => {
|
||||
return (
|
||||
<option value={String(item)} key={String(item)}>
|
||||
{properties[key].enumNames?.[index]}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'checkbox' || widget === 'radio') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Stack direction="horizontal">
|
||||
{properties[key].enum?.map((item, index) => {
|
||||
return (
|
||||
<Form.Check
|
||||
key={String(item)}
|
||||
inline
|
||||
required
|
||||
type={widget}
|
||||
name={key}
|
||||
id={`form-${String(item)}`}
|
||||
label={properties[key].enumNames?.[index]}
|
||||
checked={(formData[key]?.value || '') === item}
|
||||
feedback={formData[key]?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
onChange={(e) => handleInputCheck(e, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'switch') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Check
|
||||
required
|
||||
id={`switch-${title}`}
|
||||
name={key}
|
||||
type="switch"
|
||||
label={(uiOpt as SwitchOptions)?.label}
|
||||
checked={formData[key]?.value || ''}
|
||||
feedback={formData[key]?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
if (widget === 'timezone') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<TimeZonePicker
|
||||
value={formData[key]?.value || ''}
|
||||
name={key}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
<Form.Control
|
||||
name={key}
|
||||
className="d-none"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'upload') {
|
||||
const options: UploadOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<BrandUpload
|
||||
type={options.imageType || 'avatar'}
|
||||
acceptType={options.acceptType || ''}
|
||||
value={formData[key]?.value}
|
||||
onChange={(value) => handleUploadChange(key, value)}
|
||||
/>
|
||||
<Form.Control
|
||||
name={key}
|
||||
className="d-none"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'textarea') {
|
||||
const options: TextareaOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
|
||||
return (
|
||||
<Form.Group
|
||||
controlId={`form-${key}`}
|
||||
key={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name={key}
|
||||
placeholder={options?.placeholder || ''}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleInputChange}
|
||||
isInvalid={formData[key].isInvalid}
|
||||
rows={options?.rows || 3}
|
||||
className={classnames(options.className)}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const options: InputOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
controlId={key}
|
||||
key={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
name={key}
|
||||
placeholder={options?.placeholder || ''}
|
||||
type={options?.inputType || 'text'}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleInputChange}
|
||||
style={options?.inputType === 'color' ? { width: '6rem' } : {}}
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
className={classnames({
|
||||
'mb-3': widget !== 'legend' && !uiOpt?.simplify,
|
||||
'd-none': formData[key].hidden,
|
||||
})}>
|
||||
{widget === 'legend' ? <Legend title={title} /> : null}
|
||||
{widget === 'select' ? (
|
||||
<Select
|
||||
title={title}
|
||||
desc={description}
|
||||
fieldName={key}
|
||||
onChange={handleSelectChange}
|
||||
enumValues={enumValues}
|
||||
enumNames={enumNames}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'radio' || widget === 'checkbox' ? (
|
||||
<Check
|
||||
type={widget}
|
||||
title={title}
|
||||
desc={description}
|
||||
fieldName={key}
|
||||
onChange={handleInputCheck}
|
||||
enumValues={enumValues}
|
||||
enumNames={enumNames}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'switch' ? (
|
||||
<Switch
|
||||
title={title}
|
||||
desc={description}
|
||||
label={uiOpt && 'label' in uiOpt ? uiOpt.label : ''}
|
||||
fieldName={key}
|
||||
onChange={handleSwitchChange}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'timezone' ? (
|
||||
<Timezone
|
||||
title={title}
|
||||
desc={description}
|
||||
fieldName={key}
|
||||
onChange={handleSelectChange}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'upload' ? (
|
||||
<Upload
|
||||
title={title}
|
||||
type={
|
||||
uiOpt && 'imageType' in uiOpt ? uiOpt.imageType : undefined
|
||||
}
|
||||
acceptType={
|
||||
uiOpt && 'acceptType' in uiOpt ? uiOpt.acceptType : ''
|
||||
}
|
||||
desc={description}
|
||||
fieldName={key}
|
||||
onChange={handleUploadChange}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'textarea' ? (
|
||||
<Textarea
|
||||
title={title}
|
||||
desc={description}
|
||||
placeholder={
|
||||
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
|
||||
}
|
||||
rows={uiOpt && 'rows' in uiOpt ? uiOpt.rows : 3}
|
||||
className={uiOpt && 'className' in uiOpt ? uiOpt.className : ''}
|
||||
fieldName={key}
|
||||
onChange={handleInputChange}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'input' ? (
|
||||
<Input
|
||||
title={title}
|
||||
desc={description}
|
||||
type={uiOpt && 'inputType' in uiOpt ? uiOpt.inputType : 'text'}
|
||||
placeholder={
|
||||
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
|
||||
}
|
||||
fieldName={key}
|
||||
onChange={handleInputChange}
|
||||
formData={formData}
|
||||
/>
|
||||
) : null}
|
||||
</Form.Group>
|
||||
);
|
||||
// if (widget === 'select') {
|
||||
// return (
|
||||
// <Form.Group
|
||||
// key={title}
|
||||
// controlId={key}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <Form.Select
|
||||
// aria-label={description}
|
||||
// name={key}
|
||||
// value={formData[key]?.value || ''}
|
||||
// onChange={handleSelectChange}
|
||||
// isInvalid={formData[key].isInvalid}>
|
||||
// {properties[key].enum?.map((item, index) => {
|
||||
// return (
|
||||
// <option value={String(item)} key={String(item)}>
|
||||
// {properties[key].enumNames?.[index]}
|
||||
// </option>
|
||||
// );
|
||||
// })}
|
||||
// </Form.Select>
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (widget === 'checkbox' || widget === 'radio') {
|
||||
// return (
|
||||
// <Form.Group
|
||||
// key={title}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
// controlId={key}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <Stack direction="horizontal">
|
||||
// {properties[key].enum?.map((item, index) => {
|
||||
// return (
|
||||
// <Form.Check
|
||||
// key={String(item)}
|
||||
// inline
|
||||
// required
|
||||
// type={widget}
|
||||
// name={key}
|
||||
// id={`form-${String(item)}`}
|
||||
// label={properties[key].enumNames?.[index]}
|
||||
// checked={(formData[key]?.value || '') === item}
|
||||
// feedback={formData[key]?.errorMsg}
|
||||
// feedbackType="invalid"
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// onChange={(e) => handleInputCheck(e, index)}
|
||||
// />
|
||||
// );
|
||||
// })}
|
||||
// </Stack>
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (widget === 'switch') {
|
||||
// return (
|
||||
// <Form.Group
|
||||
// key={title}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
// controlId={key}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <Form.Check
|
||||
// required
|
||||
// id={`switch-${title}`}
|
||||
// name={key}
|
||||
// type="switch"
|
||||
// label={(uiOpt as SwitchOptions)?.label}
|
||||
// checked={formData[key]?.value || ''}
|
||||
// feedback={formData[key]?.errorMsg}
|
||||
// feedbackType="invalid"
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// onChange={handleSwitchChange}
|
||||
// />
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
// if (widget === 'timezone') {
|
||||
// return (
|
||||
// <Form.Group
|
||||
// key={title}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
// controlId={key}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <TimeZonePicker
|
||||
// value={formData[key]?.value || ''}
|
||||
// name={key}
|
||||
// onChange={handleSelectChange}
|
||||
// />
|
||||
// <Form.Control
|
||||
// name={key}
|
||||
// className="d-none"
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// />
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (widget === 'upload') {
|
||||
// const options: UploadOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
// return (
|
||||
// <Form.Group
|
||||
// key={title}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
// controlId={key}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <BrandUpload
|
||||
// type={options.imageType || 'avatar'}
|
||||
// acceptType={options.acceptType || ''}
|
||||
// value={formData[key]?.value}
|
||||
// onChange={(value) => handleUploadChange(key, value)}
|
||||
// />
|
||||
// <Form.Control
|
||||
// name={key}
|
||||
// className="d-none"
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// />
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (widget === 'textarea') {
|
||||
// const options: TextareaOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
//
|
||||
// return (
|
||||
// <Form.Group
|
||||
// controlId={`form-${key}`}
|
||||
// key={key}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <Form.Control
|
||||
// as="textarea"
|
||||
// name={key}
|
||||
// placeholder={options?.placeholder || ''}
|
||||
// value={formData[key]?.value || ''}
|
||||
// onChange={handleInputChange}
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// rows={options?.rows || 3}
|
||||
// className={classnames(options.className)}
|
||||
// />
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
//
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
// }
|
||||
|
||||
// const options: InputOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
//
|
||||
// return (
|
||||
// <Form.Group
|
||||
// controlId={key}
|
||||
// key={key}
|
||||
// className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
// <Form.Label>{title}</Form.Label>
|
||||
// <Form.Control
|
||||
// name={key}
|
||||
// placeholder={options?.placeholder || ''}
|
||||
// type={options?.inputType || 'text'}
|
||||
// value={formData[key]?.value || ''}
|
||||
// onChange={handleInputChange}
|
||||
// style={options?.inputType === 'color' ? { width: '6rem' } : {}}
|
||||
// isInvalid={formData[key].isInvalid}
|
||||
// />
|
||||
// <Form.Control.Feedback type="invalid">
|
||||
// {formData[key]?.errorMsg}
|
||||
// </Form.Control.Feedback>
|
||||
//
|
||||
// {description && (
|
||||
// <Form.Text className="text-muted">{description}</Form.Text>
|
||||
// )}
|
||||
// </Form.Group>
|
||||
// );
|
||||
})}
|
||||
{!hiddenSubmit && (
|
||||
<Button variant="primary" type="submit">
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@/common/interface';
|
||||
import { interfaceStore, loggedUserInfoStore } from '@/stores';
|
||||
import { JSONSchema, SchemaForm, UISchema } from '@/components';
|
||||
import { DEFAULT_TIMEZONE, SYSTEM_AVATAR_OPTIONS } from '@/common/constants';
|
||||
import { DEFAULT_TIMEZONE } from '@/common/constants';
|
||||
import {
|
||||
updateInterfaceSetting,
|
||||
useInterfaceSetting,
|
||||
|
@ -48,13 +48,6 @@ const Interface: FC = () => {
|
|||
description: t('time_zone.text'),
|
||||
default: setting?.time_zone || DEFAULT_TIMEZONE,
|
||||
},
|
||||
default_avatar: {
|
||||
type: 'string',
|
||||
title: t('avatar.label'),
|
||||
description: t('avatar.text'),
|
||||
enum: SYSTEM_AVATAR_OPTIONS?.map((v) => v.value),
|
||||
enumNames: SYSTEM_AVATAR_OPTIONS?.map((v) => v.label),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -69,11 +62,6 @@ const Interface: FC = () => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
default_avatar: {
|
||||
value: setting?.default_avatar || 'system',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
|
||||
const uiSchema: UISchema = {
|
||||
|
@ -83,9 +71,6 @@ const Interface: FC = () => {
|
|||
time_zone: {
|
||||
'ui:widget': 'timezone',
|
||||
},
|
||||
default_avatar: {
|
||||
'ui:widget': 'select',
|
||||
},
|
||||
};
|
||||
const getLangs = async () => {
|
||||
const res: LangsType[] = await loadLanguageOptions(true);
|
||||
|
@ -118,7 +103,6 @@ const Interface: FC = () => {
|
|||
const reqParams: AdminSettingsInterface = {
|
||||
language: formData.language.value,
|
||||
time_zone: formData.time_zone.value,
|
||||
default_avatar: formData.default_avatar.value,
|
||||
};
|
||||
|
||||
updateInterfaceSetting(reqParams)
|
||||
|
@ -147,9 +131,6 @@ const Interface: FC = () => {
|
|||
const formMeta = {};
|
||||
Object.keys(setting).forEach((k) => {
|
||||
formMeta[k] = { ...formData[k], value: setting[k] };
|
||||
if (k === 'default_avatar') {
|
||||
formMeta[k].value = setting[k] || 'system';
|
||||
}
|
||||
});
|
||||
setFormData({ ...formData, ...formMeta });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import { FC, FormEvent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useToast } from '@/hooks';
|
||||
import {
|
||||
LangsType,
|
||||
FormDataType,
|
||||
AdminSettingsInterface,
|
||||
} from '@/common/interface';
|
||||
import { interfaceStore, loggedUserInfoStore } from '@/stores';
|
||||
import { JSONSchema, SchemaForm, UISchema } from '@/components';
|
||||
import { DEFAULT_TIMEZONE, SYSTEM_AVATAR_OPTIONS } from '@/common/constants';
|
||||
import {
|
||||
updateInterfaceSetting,
|
||||
useInterfaceSetting,
|
||||
getLoggedUserInfo,
|
||||
} from '@/services';
|
||||
import {
|
||||
setupAppLanguage,
|
||||
loadLanguageOptions,
|
||||
setupAppTimeZone,
|
||||
} from '@/utils/localize';
|
||||
import { handleFormError } from '@/utils';
|
||||
|
||||
const Interface: FC = () => {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'admin.interface',
|
||||
});
|
||||
const storeInterface = interfaceStore.getState().interface;
|
||||
const Toast = useToast();
|
||||
const [langs, setLangs] = useState<LangsType[]>();
|
||||
const { data: setting } = useInterfaceSetting();
|
||||
|
||||
const schema: JSONSchema = {
|
||||
title: t('page_title'),
|
||||
properties: {
|
||||
language: {
|
||||
type: 'string',
|
||||
title: t('language.label'),
|
||||
description: t('language.text'),
|
||||
enum: langs?.map((lang) => lang.value),
|
||||
enumNames: langs?.map((lang) => lang.label),
|
||||
default: setting?.language || storeInterface.language,
|
||||
},
|
||||
time_zone: {
|
||||
type: 'string',
|
||||
title: t('time_zone.label'),
|
||||
description: t('time_zone.text'),
|
||||
default: setting?.time_zone || DEFAULT_TIMEZONE,
|
||||
},
|
||||
default_avatar: {
|
||||
type: 'string',
|
||||
title: t('avatar.label'),
|
||||
description: t('avatar.text'),
|
||||
enum: SYSTEM_AVATAR_OPTIONS?.map((v) => v.value),
|
||||
enumNames: SYSTEM_AVATAR_OPTIONS?.map((v) => v.label),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [formData, setFormData] = useState<FormDataType>({
|
||||
language: {
|
||||
value: setting?.language || storeInterface.language,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
time_zone: {
|
||||
value: setting?.time_zone || DEFAULT_TIMEZONE,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
default_avatar: {
|
||||
value: 'system',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
|
||||
const uiSchema: UISchema = {
|
||||
language: {
|
||||
'ui:widget': 'select',
|
||||
},
|
||||
time_zone: {
|
||||
'ui:widget': 'timezone',
|
||||
},
|
||||
default_avatar: {
|
||||
'ui:widget': 'select',
|
||||
},
|
||||
};
|
||||
const getLangs = async () => {
|
||||
const res: LangsType[] = await loadLanguageOptions(true);
|
||||
setLangs(res);
|
||||
};
|
||||
|
||||
const checkValidated = (): boolean => {
|
||||
let ret = true;
|
||||
const { language } = formData;
|
||||
const formCheckData = { ...formData };
|
||||
if (!language.value) {
|
||||
ret = false;
|
||||
formCheckData.language = {
|
||||
value: '',
|
||||
isInvalid: true,
|
||||
errorMsg: t('language.msg'),
|
||||
};
|
||||
}
|
||||
setFormData({
|
||||
...formCheckData,
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
const onSubmit = (evt: FormEvent) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (checkValidated() === false) {
|
||||
return;
|
||||
}
|
||||
const reqParams: AdminSettingsInterface = {
|
||||
language: formData.language.value,
|
||||
time_zone: formData.time_zone.value,
|
||||
};
|
||||
|
||||
updateInterfaceSetting(reqParams)
|
||||
.then(() => {
|
||||
interfaceStore.getState().update(reqParams);
|
||||
setupAppLanguage();
|
||||
setupAppTimeZone();
|
||||
getLoggedUserInfo().then((info) => {
|
||||
loggedUserInfoStore.getState().update(info);
|
||||
});
|
||||
Toast.onShow({
|
||||
msg: t('update', { keyPrefix: 'toast' }),
|
||||
variant: 'success',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (setting) {
|
||||
const formMeta = {};
|
||||
Object.keys(setting).forEach((k) => {
|
||||
formMeta[k] = { ...formData[k], value: setting[k] };
|
||||
if (k === 'default_avatar') {
|
||||
formMeta[k].value = setting[k] || 'system';
|
||||
}
|
||||
});
|
||||
setFormData({ ...formData, ...formMeta });
|
||||
}
|
||||
}, [setting]);
|
||||
useEffect(() => {
|
||||
getLangs();
|
||||
}, []);
|
||||
|
||||
const handleOnChange = (data) => {
|
||||
setFormData(data);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<h3 className="mb-4">{t('page_title')}</h3>
|
||||
<SchemaForm
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
formData={formData}
|
||||
onSubmit={onSubmit}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Interface;
|
|
@ -0,0 +1,110 @@
|
|||
import { FC, FormEvent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useToast } from '@/hooks';
|
||||
import { FormDataType } from '@/common/interface';
|
||||
import { JSONSchema, SchemaForm, UISchema, initFormData } from '@/components';
|
||||
import { SYSTEM_AVATAR_OPTIONS } from '@/common/constants';
|
||||
import {
|
||||
getUsersSetting,
|
||||
putUsersSetting,
|
||||
AdminSettingsUsers,
|
||||
} from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import * as Type from '@/common/interface';
|
||||
|
||||
const Interface: FC = () => {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'admin.settings_users',
|
||||
});
|
||||
const Toast = useToast();
|
||||
|
||||
const schema: JSONSchema = {
|
||||
title: t('title'),
|
||||
properties: {
|
||||
default_avatar: {
|
||||
type: 'string',
|
||||
title: t('avatar.label'),
|
||||
description: t('avatar.text'),
|
||||
enum: SYSTEM_AVATAR_OPTIONS?.map((v) => v.value),
|
||||
enumNames: SYSTEM_AVATAR_OPTIONS?.map((v) => v.label),
|
||||
default: 'system',
|
||||
},
|
||||
profile_editable: {
|
||||
type: 'string',
|
||||
title: t('profile_editable.title'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [formData, setFormData] = useState<FormDataType>(initFormData(schema));
|
||||
|
||||
const uiSchema: UISchema = {
|
||||
default_avatar: {
|
||||
'ui:widget': 'select',
|
||||
},
|
||||
profile_editable: {
|
||||
'ui:widget': 'legend',
|
||||
},
|
||||
profile_displayname: {
|
||||
'ui:widget': 'legend',
|
||||
},
|
||||
};
|
||||
|
||||
const onSubmit = (evt: FormEvent) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
// @ts-ignore
|
||||
const reqParams: AdminSettingsUsers = {
|
||||
default_avatar: '',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
putUsersSetting(reqParams)
|
||||
.then(() => {
|
||||
Toast.onShow({
|
||||
msg: t('update', { keyPrefix: 'toast' }),
|
||||
variant: 'success',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getUsersSetting().then((resp) => {
|
||||
const formMeta: Type.FormDataType = {};
|
||||
Object.keys(formData).forEach((k) => {
|
||||
let v = resp[k];
|
||||
if (k === 'default_avatar' && !v) {
|
||||
v = 'system';
|
||||
}
|
||||
formMeta[k] = { ...formData[k], value: v };
|
||||
});
|
||||
setFormData({ ...formData, ...formMeta });
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleOnChange = (data) => {
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<SchemaForm
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
formData={formData}
|
||||
onSubmit={onSubmit}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Interface;
|
|
@ -19,7 +19,7 @@ const g10Paths = [
|
|||
'answers',
|
||||
'users',
|
||||
'flags',
|
||||
'installed_plugins',
|
||||
'installed-plugins',
|
||||
];
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'page_title' });
|
||||
|
@ -80,7 +80,7 @@ const Index: FC = () => {
|
|||
<Col lg={2}>
|
||||
<AccordionNav menus={menus} path="/admin/" />
|
||||
</Col>
|
||||
<Col lg={g10Paths.find((v) => curPath.includes(v)) ? 10 : 6}>
|
||||
<Col lg={g10Paths.find((v) => curPath === v) ? 10 : 6}>
|
||||
<Outlet />
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -345,7 +345,15 @@ const routes: RouteNode[] = [
|
|||
page: 'pages/Admin/Login',
|
||||
},
|
||||
{
|
||||
path: 'installed_plugins',
|
||||
path: 'settings-users',
|
||||
page: 'pages/Admin/SettingsUsers',
|
||||
},
|
||||
{
|
||||
path: 'privileges',
|
||||
page: 'pages/Admin/Privileges',
|
||||
},
|
||||
{
|
||||
path: 'installed-plugins',
|
||||
page: 'pages/Admin/Plugins/Installed',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -3,6 +3,16 @@ import useSWR from 'swr';
|
|||
import request from '@/utils/request';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
export interface AdminSettingsUsers {
|
||||
allow_update_avatar: boolean;
|
||||
allow_update_bio: boolean;
|
||||
allow_update_display_name: boolean;
|
||||
allow_update_location: boolean;
|
||||
allow_update_username: boolean;
|
||||
allow_update_website: boolean;
|
||||
default_avatar: string;
|
||||
}
|
||||
|
||||
export const useGeneralSetting = () => {
|
||||
const apiUrl = `/answer/admin/api/siteinfo/general`;
|
||||
const { data, error } = useSWR<Type.AdminSettingsGeneral, Error>(
|
||||
|
@ -126,3 +136,11 @@ export const getLoginSetting = () => {
|
|||
export const putLoginSetting = (params: Type.AdminSettingsLogin) => {
|
||||
return request.put('/answer/admin/api/siteinfo/login', params);
|
||||
};
|
||||
|
||||
export const getUsersSetting = () => {
|
||||
return request.get<AdminSettingsUsers>('/answer/admin/api/siteinfo/users');
|
||||
};
|
||||
|
||||
export const putUsersSetting = (params: AdminSettingsUsers) => {
|
||||
return request.put('/answer/admin/api/siteinfo/users', params);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue