Merge branch 'feat/ui-0.5.0' into test

This commit is contained in:
haitao(lj) 2022-11-30 10:13:35 +08:00
commit 5451625725
15 changed files with 179 additions and 95 deletions

View File

@ -551,7 +551,7 @@ ui:
footer: footer:
build_on: >- build_on: >-
Built on <1> Answer </1>- the open-source software that power Q&A Built on <1> Answer </1>- the open-source software that power Q&A
communities<br />Made with love © 2022 Answer communities.<br />Made with love © {{cc}}.
upload_img: upload_img:
name: Change name: Change
loading: loading... loading: loading...

View File

@ -1,3 +1,4 @@
export const DEFAULT_SITE_NAME = 'Answer';
export const DEFAULT_LANG = 'en_US'; export const DEFAULT_LANG = 'en_US';
export const CURRENT_LANG_STORAGE_KEY = '_a_lang_'; export const CURRENT_LANG_STORAGE_KEY = '_a_lang_';
export const LANG_RESOURCE_STORAGE_KEY = '_a_lang_r_'; export const LANG_RESOURCE_STORAGE_KEY = '_a_lang_r_';
@ -9,29 +10,29 @@ export const CAPTCHA_CODE_STORAGE_KEY = '_a_captcha_';
export const ADMIN_LIST_STATUS = { export const ADMIN_LIST_STATUS = {
// normal; // normal;
1: { 1: {
variant: 'success', variant: 'text-bg-success',
name: 'normal', name: 'normal',
}, },
// closed; // closed;
2: { 2: {
variant: 'warning', variant: 'text-bg-warning',
name: 'closed', name: 'closed',
}, },
// deleted // deleted
10: { 10: {
variant: 'danger', variant: 'text-bg-danger',
name: 'deleted', name: 'deleted',
}, },
normal: { normal: {
variant: 'success', variant: 'text-bg-success',
name: 'normal', name: 'normal',
}, },
closed: { closed: {
variant: 'warning', variant: 'text-bg-warning',
name: 'closed', name: 'closed',
}, },
deleted: { deleted: {
variant: 'danger', variant: 'text-bg-danger',
name: 'deleted', name: 'deleted',
}, },
}; };

View File

@ -287,9 +287,9 @@ export interface AdminSettingsSmtp {
from_name: string; from_name: string;
smtp_authentication: boolean; smtp_authentication: boolean;
smtp_host: string; smtp_host: string;
smtp_password: string; smtp_password?: string;
smtp_port: number; smtp_port: number;
smtp_username: string; smtp_username?: string;
test_email_recipient?: string; test_email_recipient?: string;
} }

View File

@ -1,5 +1,5 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Accordion, Badge, Button, Stack } from 'react-bootstrap'; import { Accordion, Button, Stack } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate, useMatch } from 'react-router-dom'; import { useNavigate, useMatch } from 'react-router-dom';
@ -33,9 +33,9 @@ function MenuNode({ menu, callback, activeKey, isLeaf = false }) {
{!isLeaf ? <Icon name="chevron-right" className="me-1" /> : null} {!isLeaf ? <Icon name="chevron-right" className="me-1" /> : null}
{t(menu.name)} {t(menu.name)}
{menu.badgeContent ? ( {menu.badgeContent ? (
<Badge bg="dark" className="ms-auto top-0"> <span className="badge text-bg-dark ms-auto top-0">
{menu.badgeContent} {menu.badgeContent}
</Badge> </span>
) : null} ) : null}
</Stack> </Stack>
</Button> </Button>

View File

@ -2,12 +2,20 @@ import React from 'react';
import { Container } from 'react-bootstrap'; import { Container } from 'react-bootstrap';
import { Trans } from 'react-i18next'; import { Trans } from 'react-i18next';
import dayjs from 'dayjs';
import { siteInfoStore } from '@/stores';
import { DEFAULT_SITE_NAME } from '@/common/constants';
const Index = () => { const Index = () => {
const fullYear = dayjs().format('YYYY');
const siteName = siteInfoStore.getState().siteInfo.name || DEFAULT_SITE_NAME;
const cc = `${fullYear} ${siteName}`;
return ( return (
<footer className="bg-light py-3"> <footer className="bg-light py-3">
<Container> <Container>
<p className="text-center mb-0 fs-14 text-secondary"> <p className="text-center mb-0 fs-14 text-secondary">
<Trans i18nKey="footer.build_on"> <Trans i18nKey="footer.build_on" values={{ cc }}>
Built on Built on
{/* eslint-disable-next-line react/jsx-no-target-blank */} {/* eslint-disable-next-line react/jsx-no-target-blank */}
<a href="https://answer.dev/" target="_blank"> <a href="https://answer.dev/" target="_blank">
@ -15,7 +23,7 @@ const Index = () => {
</a> </a>
- the open-source software that powers Q&A communities. - the open-source software that powers Q&A communities.
<br /> <br />
Made with love. © 2022 Answer . Made with love. © 2022 Answer.
</Trans> </Trans>
</p> </p>
</Container> </Container>

View File

@ -20,6 +20,7 @@ import {
import { loggedUserInfoStore, siteInfoStore, brandingStore } from '@/stores'; import { loggedUserInfoStore, siteInfoStore, brandingStore } from '@/stores';
import { logout, useQueryNotificationStatus } from '@/services'; import { logout, useQueryNotificationStatus } from '@/services';
import { RouteAlias } from '@/router/alias'; import { RouteAlias } from '@/router/alias';
import { DEFAULT_SITE_NAME } from '@/common/constants';
import NavItems from './components/NavItems'; import NavItems from './components/NavItems';
@ -88,7 +89,7 @@ const Header: FC = () => {
/> />
</> </>
) : ( ) : (
<span>{siteInfo.name || 'Answer'}</span> <span>{siteInfo.name || DEFAULT_SITE_NAME}</span>
)} )}
</Navbar.Brand> </Navbar.Brand>

View File

@ -2,6 +2,8 @@ import { FC } from 'react';
import { Form, Button, Stack } from 'react-bootstrap'; import { Form, Button, Stack } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import BrandUpload from '../BrandUpload'; import BrandUpload from '../BrandUpload';
import TimeZonePicker from '../TimeZonePicker'; import TimeZonePicker from '../TimeZonePicker';
import type * as Type from '@/common/interface'; import type * as Type from '@/common/interface';
@ -52,7 +54,10 @@ export interface UISchema {
| 'url' | 'url'
| 'week'; | 'week';
empty?: string; empty?: string;
validator?: (value) => Promise<string | true | void> | true | string; validator?: (
value,
formData?,
) => Promise<string | true | void> | true | string;
textRender?: () => React.ReactElement; textRender?: () => React.ReactElement;
imageType?: Type.UploadType; imageType?: Type.UploadType;
acceptType?: string; acceptType?: string;
@ -153,7 +158,7 @@ const SchemaForm: FC<IProps> = ({
const value = formData[key]?.value; const value = formData[key]?.value;
promises.push({ promises.push({
key, key,
promise: validator(value), promise: validator(value, formData),
}); });
} }
}); });
@ -263,7 +268,10 @@ const SchemaForm: FC<IProps> = ({
uiSchema[key] || {}; uiSchema[key] || {};
if (widget === 'select') { if (widget === 'select') {
return ( return (
<Form.Group key={title} controlId={key} className="mb-3"> <Form.Group
key={title}
controlId={key}
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<Form.Select <Form.Select
aria-label={description} aria-label={description}
@ -282,13 +290,18 @@ const SchemaForm: FC<IProps> = ({
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
if (widget === 'checkbox' || widget === 'radio') { if (widget === 'checkbox' || widget === 'radio') {
return ( return (
<Form.Group key={title} className="mb-3" controlId={key}> <Form.Group
key={title}
className={classnames('mb-3', formData[key].hidden && 'd-none')}
controlId={key}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<Stack direction="horizontal"> <Stack direction="horizontal">
{properties[key].enum?.map((item, index) => { {properties[key].enum?.map((item, index) => {
@ -313,14 +326,19 @@ const SchemaForm: FC<IProps> = ({
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
if (widget === 'switch') { if (widget === 'switch') {
return ( return (
<Form.Group key={title} className="mb-3" controlId={key}> <Form.Group
key={title}
className={classnames('mb-3', formData[key].hidden && 'd-none')}
controlId={key}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<Form.Check <Form.Check
required required
@ -337,13 +355,18 @@ const SchemaForm: FC<IProps> = ({
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
if (widget === 'timezone') { if (widget === 'timezone') {
return ( return (
<Form.Group key={title} className="mb-3" controlId={key}> <Form.Group
key={title}
className={classnames('mb-3', formData[key].hidden && 'd-none')}
controlId={key}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<TimeZonePicker <TimeZonePicker
value={formData[key]?.value} value={formData[key]?.value}
@ -358,14 +381,19 @@ const SchemaForm: FC<IProps> = ({
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
if (widget === 'upload') { if (widget === 'upload') {
return ( return (
<Form.Group key={title} className="mb-3" controlId={key}> <Form.Group
key={title}
className={classnames('mb-3', formData[key].hidden && 'd-none')}
controlId={key}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<BrandUpload <BrandUpload
type={options.imageType || 'avatar'} type={options.imageType || 'avatar'}
@ -381,14 +409,19 @@ const SchemaForm: FC<IProps> = ({
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
if (widget === 'textarea') { if (widget === 'textarea') {
return ( return (
<Form.Group controlId={key} key={key} className="mb-3"> <Form.Group
controlId={key}
key={key}
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<Form.Control <Form.Control
as="textarea" as="textarea"
@ -404,12 +437,17 @@ const SchemaForm: FC<IProps> = ({
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
} }
return ( return (
<Form.Group controlId={key} key={key} className="mb-3"> <Form.Group
controlId={key}
key={key}
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
<Form.Label>{title}</Form.Label> <Form.Label>{title}</Form.Label>
<Form.Control <Form.Control
name={key} name={key}
@ -424,7 +462,9 @@ const SchemaForm: FC<IProps> = ({
{formData[key]?.errorMsg} {formData[key]?.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
<Form.Text>{description}</Form.Text> {description && (
<Form.Text className="text-muted">{description}</Form.Text>
)}
</Form.Group> </Form.Group>
); );
})} })}

View File

@ -1,8 +1,10 @@
import { FC } from 'react'; import { FC } from 'react';
import { Button, Form, Table, Stack, Badge } from 'react-bootstrap'; import { Button, Form, Table, Stack } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { import {
FormatTime, FormatTime,
Icon, Icon,
@ -159,9 +161,13 @@ const Answers: FC = () => {
</Stack> </Stack>
</td> </td>
<td> <td>
<Badge bg={ADMIN_LIST_STATUS[curFilter]?.variant}> <span
className={classNames(
'badge',
ADMIN_LIST_STATUS[curFilter]?.variant,
)}>
{t(ADMIN_LIST_STATUS[curFilter]?.name)} {t(ADMIN_LIST_STATUS[curFilter]?.name)}
</Badge> </span>
</td> </td>
{curFilter !== 'deleted' && ( {curFilter !== 'deleted' && (
<td> <td>

View File

@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { Card, Row, Col, Badge } from 'react-bootstrap'; import { Card, Row, Col } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -29,38 +29,31 @@ const HealthStatus: FC<IProps> = ({ data }) => {
<span className="text-secondary me-1">{t('version')}</span> <span className="text-secondary me-1">{t('version')}</span>
<strong>{version}</strong> <strong>{version}</strong>
{isLatest && ( {isLatest && (
<Badge <a
pill className="ms-1 badge rounded-pill text-bg-success"
bg="success"
className="ms-1"
as="a"
target="_blank" target="_blank"
href="https://github.com/answerdev/answer/releases"> href="https://github.com/answerdev/answer/releases"
rel="noreferrer">
{t('latest')} {t('latest')}
</Badge> </a>
)} )}
{!isLatest && hasNewerVersion && ( {!isLatest && hasNewerVersion && (
<Badge <a
pill className="ms-1 badge rounded-pill text-bg-warning"
bg="warning"
text="dark"
className="ms-1"
as="a"
target="_blank" target="_blank"
href="https://github.com/answerdev/answer/releases"> href="https://github.com/answerdev/answer/releases"
rel="noreferrer">
{t('update_to')} {remote_version} {t('update_to')} {remote_version}
</Badge> </a>
)} )}
{!isLatest && !remote_version && ( {!isLatest && !remote_version && (
<Badge <a
pill className="ms-1 badge rounded-pill text-bg-danger"
bg="danger"
className="ms-1"
as="a"
target="_blank" target="_blank"
href="https://github.com/answerdev/answer/releases"> href="https://github.com/answerdev/answer/releases"
rel="noreferrer">
{t('check_failed')} {t('check_failed')}
</Badge> </a>
)} )}
</Col> </Col>
<Col xs={6} className="mb-1"> <Col xs={6} className="mb-1">

View File

@ -1,8 +1,10 @@
import { FC } from 'react'; import { FC } from 'react';
import { Button, Form, Table, Stack, Badge } from 'react-bootstrap'; import { Button, Form, Table, Stack } from 'react-bootstrap';
import { Link, useSearchParams } from 'react-router-dom'; import { Link, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { import {
FormatTime, FormatTime,
Icon, Icon,
@ -167,9 +169,13 @@ const Questions: FC = () => {
</Stack> </Stack>
</td> </td>
<td> <td>
<Badge bg={ADMIN_LIST_STATUS[curFilter]?.variant}> <span
className={classNames(
'badge',
ADMIN_LIST_STATUS[curFilter]?.variant,
)}>
{t(ADMIN_LIST_STATUS[curFilter]?.name)} {t(ADMIN_LIST_STATUS[curFilter]?.name)}
</Badge> </span>
</td> </td>
{curFilter !== 'deleted' && ( {curFilter !== 'deleted' && (
<td> <td>

View File

@ -37,8 +37,8 @@ const Smtp: FC = () => {
type: 'boolean', type: 'boolean',
title: t('encryption.label'), title: t('encryption.label'),
description: t('encryption.text'), description: t('encryption.text'),
enum: ['SSL', ''], enum: ['SSL', 'None'],
enumNames: ['SSL', ''], enumNames: ['SSL', 'None'],
}, },
smtp_port: { smtp_port: {
type: 'string', type: 'string',
@ -54,12 +54,10 @@ const Smtp: FC = () => {
smtp_username: { smtp_username: {
type: 'string', type: 'string',
title: t('smtp_username.label'), title: t('smtp_username.label'),
description: t('smtp_username.text'),
}, },
smtp_password: { smtp_password: {
type: 'string', type: 'string',
title: t('smtp_password.label'), title: t('smtp_password.label'),
description: t('smtp_password.text'),
}, },
test_email_recipient: { test_email_recipient: {
type: 'string', type: 'string',
@ -70,15 +68,35 @@ const Smtp: FC = () => {
}; };
const uiSchema: UISchema = { const uiSchema: UISchema = {
encryption: { encryption: {
'ui:widget': 'radio', 'ui:widget': 'select',
},
smtp_username: {
'ui:options': {
validator: (value: string, formData) => {
if (formData.smtp_authentication.value) {
if (!value) {
return t('smtp_username.msg');
}
}
return true;
},
},
}, },
smtp_password: { smtp_password: {
'ui:options': { 'ui:options': {
type: 'password', type: 'password',
validator: (value: string, formData) => {
if (formData.smtp_authentication.value) {
if (!value) {
return t('smtp_password.msg');
}
}
return true;
},
}, },
}, },
smtp_authentication: { smtp_authentication: {
'ui:widget': 'radio', 'ui:widget': 'switch',
}, },
smtp_port: { smtp_port: {
'ui:options': { 'ui:options': {
@ -116,8 +134,12 @@ const Smtp: FC = () => {
encryption: formData.encryption.value, encryption: formData.encryption.value,
smtp_port: Number(formData.smtp_port.value), smtp_port: Number(formData.smtp_port.value),
smtp_authentication: formData.smtp_authentication.value, smtp_authentication: formData.smtp_authentication.value,
smtp_username: formData.smtp_username.value, ...(formData.smtp_authentication.value
smtp_password: formData.smtp_password.value, ? { smtp_username: formData.smtp_username.value }
: {}),
...(formData.smtp_authentication.value
? { smtp_password: formData.smtp_password.value }
: {}),
test_email_recipient: formData.test_email_recipient.value, test_email_recipient: formData.test_email_recipient.value,
}; };
@ -151,6 +173,22 @@ const Smtp: FC = () => {
setFormData(formState); setFormData(formState);
}, [setting]); }, [setting]);
useEffect(() => {
if (formData.smtp_authentication.value) {
setFormData({
...formData,
smtp_username: { ...formData.smtp_username, hidden: false },
smtp_password: { ...formData.smtp_password, hidden: false },
});
} else {
setFormData({
...formData,
smtp_username: { ...formData.smtp_username, hidden: true },
smtp_password: { ...formData.smtp_password, hidden: true },
});
}
}, [formData.smtp_authentication]);
const handleOnChange = (data) => { const handleOnChange = (data) => {
setFormData(data); setFormData(data);
}; };

View File

@ -1,8 +1,10 @@
import { FC } from 'react'; import { FC } from 'react';
import { Button, Form, Table, Badge } from 'react-bootstrap'; import { Button, Form, Table } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { import {
Pagination, Pagination,
FormatTime, FormatTime,
@ -24,10 +26,10 @@ const UserFilterKeys: Type.UserFilterBy[] = [
]; ];
const bgMap = { const bgMap = {
normal: 'success', normal: 'text-bg-success',
suspended: 'danger', suspended: 'text-bg-danger',
deleted: 'danger', deleted: 'text-bg-danger',
inactive: 'secondary', inactive: 'text-bg-secondary',
}; };
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
@ -132,7 +134,9 @@ const Users: FC = () => {
</td> </td>
)} )}
<td> <td>
<Badge bg={bgMap[user.status]}>{t(user.status)}</Badge> <span className={classNames('badge', bgMap[user.status])}>
{t(user.status)}
</span>
</td> </td>
{curFilter !== 'deleted' ? ( {curFilter !== 'deleted' ? (
<td> <td>

View File

@ -1,13 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { import { Container, Row, Col, Alert, Stack, Button } from 'react-bootstrap';
Container,
Row,
Col,
Alert,
Badge,
Stack,
Button,
} from 'react-bootstrap';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -120,9 +112,7 @@ const Index: FC = () => {
<Col lg={{ span: 7, offset: 1 }}> <Col lg={{ span: 7, offset: 1 }}>
<Alert variant="secondary"> <Alert variant="secondary">
<Stack className="align-items-start"> <Stack className="align-items-start">
<Badge bg="secondary" className="mb-2"> <span className="badge text-bg-secondary">{editBadge}</span>
{editBadge}
</Badge>
<Link to={itemLink} target="_blank"> <Link to={itemLink} target="_blank">
{itemTitle} {itemTitle}
</Link> </Link>

View File

@ -1,5 +1,5 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { ListGroupItem, Badge } from 'react-bootstrap'; import { ListGroupItem } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, Tag, FormatTime, BaseUserCard } from '@/components'; import { Icon, Tag, FormatTime, BaseUserCard } from '@/components';
@ -21,12 +21,11 @@ const Index: FC<Props> = ({ data }) => {
return ( return (
<ListGroupItem className="py-3 px-0"> <ListGroupItem className="py-3 px-0">
<div className="mb-2 clearfix"> <div className="mb-2 clearfix">
<Badge <span
bg="dark" className="float-start me-2 badge text-bg-dark"
className="me-2 float-start"
style={{ marginTop: '2px' }}> style={{ marginTop: '2px' }}>
{data.object_type === 'question' ? 'Q' : 'A'} {data.object_type === 'question' ? 'Q' : 'A'}
</Badge> </span>
<a className="h5 mb-0 link-dark text-break" href={itemUrl}> <a className="h5 mb-0 link-dark text-break" href={itemUrl}>
{data.object.title} {data.object.title}
{data.object.status === 'closed' {data.object.status === 'closed'

View File

@ -1,5 +1,5 @@
import { FC, memo } from 'react'; import { FC, memo } from 'react';
import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap'; import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -42,9 +42,7 @@ const Index: FC<Props> = ({ data }) => {
<OverlayTrigger <OverlayTrigger
placement="top" placement="top"
overlay={<Tooltip>{t('mod_long')}</Tooltip>}> overlay={<Tooltip>{t('mod_long')}</Tooltip>}>
<Badge bg="light" className="text-body"> <span className="badge text-bg-light">{t('mod_short')}</span>
{t('mod_short')}
</Badge>
</OverlayTrigger> </OverlayTrigger>
</div> </div>
)} )}