Merge remote-tracking branch 'origin/alpha' into alpha
This commit is contained in:
@@ -69,8 +69,8 @@ func (a *TaskAdaptor) Init(info *relaycommon.TaskRelayInfo) {
|
|||||||
a.ChannelType = info.ChannelType
|
a.ChannelType = info.ChannelType
|
||||||
a.baseURL = info.BaseUrl
|
a.baseURL = info.BaseUrl
|
||||||
|
|
||||||
// apiKey format: "access_key,secret_key"
|
// apiKey format: "access_key|secret_key"
|
||||||
keyParts := strings.Split(info.ApiKey, ",")
|
keyParts := strings.Split(info.ApiKey, "|")
|
||||||
if len(keyParts) == 2 {
|
if len(keyParts) == 2 {
|
||||||
a.accessKey = strings.TrimSpace(keyParts[0])
|
a.accessKey = strings.TrimSpace(keyParts[0])
|
||||||
a.secretKey = strings.TrimSpace(keyParts[1])
|
a.secretKey = strings.TrimSpace(keyParts[1])
|
||||||
@@ -264,7 +264,7 @@ func (a *TaskAdaptor) createJWTToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
|
func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
|
||||||
parts := strings.Split(apiKey, ",")
|
parts := strings.Split(apiKey, "|")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
|
return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,25 +113,31 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
return (
|
return (
|
||||||
<div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
|
<div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
|
||||||
<Timeline mode="alternate">
|
<Timeline mode="alternate">
|
||||||
{processedAnnouncements.map((item, idx) => (
|
{processedAnnouncements.map((item, idx) => {
|
||||||
<Timeline.Item
|
const htmlContent = marked.parse(item.content || '');
|
||||||
key={idx}
|
const htmlExtra = item.extra ? marked.parse(item.extra) : '';
|
||||||
type={item.type}
|
return (
|
||||||
time={item.time}
|
<Timeline.Item
|
||||||
className={item.isUnread ? '' : ''}
|
key={idx}
|
||||||
>
|
type={item.type}
|
||||||
<div>
|
time={item.time}
|
||||||
{item.isUnread ? (
|
className={item.isUnread ? '' : ''}
|
||||||
<span className="shine-text">
|
>
|
||||||
{item.content}
|
<div>
|
||||||
</span>
|
<div
|
||||||
) : (
|
className={item.isUnread ? 'shine-text' : ''}
|
||||||
item.content
|
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
||||||
)}
|
/>
|
||||||
{item.extra && <div className="text-xs text-gray-500">{item.extra}</div>}
|
{item.extra && (
|
||||||
</div>
|
<div
|
||||||
</Timeline.Item>
|
className="text-xs text-gray-500"
|
||||||
))}
|
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Timeline.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1615,6 +1615,7 @@
|
|||||||
"编辑公告": "Edit Notice",
|
"编辑公告": "Edit Notice",
|
||||||
"公告内容": "Notice Content",
|
"公告内容": "Notice Content",
|
||||||
"请输入公告内容": "Please enter the notice content",
|
"请输入公告内容": "Please enter the notice content",
|
||||||
|
"请输入公告内容(支持 Markdown/HTML)": "Please enter the notice content (supports Markdown/HTML)",
|
||||||
"发布日期": "Publish Date",
|
"发布日期": "Publish Date",
|
||||||
"请选择发布日期": "Please select the publish date",
|
"请选择发布日期": "Please select the publish date",
|
||||||
"发布时间": "Publish Time",
|
"发布时间": "Publish Time",
|
||||||
@@ -1630,6 +1631,7 @@
|
|||||||
"请输入问题标题": "Please enter the question title",
|
"请输入问题标题": "Please enter the question title",
|
||||||
"回答内容": "Answer Content",
|
"回答内容": "Answer Content",
|
||||||
"请输入回答内容": "Please enter the answer content",
|
"请输入回答内容": "Please enter the answer content",
|
||||||
|
"请输入回答内容(支持 Markdown/HTML)": "Please enter the answer content (supports Markdown/HTML)",
|
||||||
"确定要删除此问答吗?": "Are you sure you want to delete this FAQ?",
|
"确定要删除此问答吗?": "Are you sure you want to delete this FAQ?",
|
||||||
"系统公告管理,可以发布系统通知和重要消息(最多100个,前端显示最新20条)": "System notice management, you can publish system notices and important messages (maximum 100, display latest 20 on the front end)",
|
"系统公告管理,可以发布系统通知和重要消息(最多100个,前端显示最新20条)": "System notice management, you can publish system notices and important messages (maximum 100, display latest 20 on the front end)",
|
||||||
"常见问答管理,为用户提供常见问题的答案(最多50个,前端显示最新20条)": "FAQ management, providing answers to common questions for users (maximum 50, display latest 20 on the front end)",
|
"常见问答管理,为用户提供常见问题的答案(最多50个,前端显示最新20条)": "FAQ management, providing answers to common questions for users (maximum 50, display latest 20 on the front end)",
|
||||||
@@ -1710,5 +1712,8 @@
|
|||||||
"可信": "Reliable",
|
"可信": "Reliable",
|
||||||
"所有上游数据均可信": "All upstream data is reliable",
|
"所有上游数据均可信": "All upstream data is reliable",
|
||||||
"以下上游数据可能不可信:": "The following upstream data may not be reliable: ",
|
"以下上游数据可能不可信:": "The following upstream data may not be reliable: ",
|
||||||
"按倍率类型筛选": "Filter by ratio type"
|
"按倍率类型筛选": "Filter by ratio type",
|
||||||
|
"内容": "Content",
|
||||||
|
"放大编辑": "Expand editor",
|
||||||
|
"编辑公告内容": "Edit announcement content"
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,8 @@ function type2secretPrompt(type) {
|
|||||||
return '按照如下格式输入:AppId|SecretId|SecretKey';
|
return '按照如下格式输入:AppId|SecretId|SecretKey';
|
||||||
case 33:
|
case 33:
|
||||||
return '按照如下格式输入:Ak|Sk|Region';
|
return '按照如下格式输入:Ak|Sk|Region';
|
||||||
|
case 50:
|
||||||
|
return '按照如下格式输入: AccessKey|SecretKey';
|
||||||
default:
|
default:
|
||||||
return '请输入渠道对应的鉴权密钥';
|
return '请输入渠道对应的鉴权密钥';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useRef, useState, useMemo, useCallback }
|
|||||||
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
|
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle } from 'lucide-react';
|
import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle } from 'lucide-react';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -1267,10 +1268,27 @@ const Detail = (props) => {
|
|||||||
onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)}
|
onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)}
|
||||||
>
|
>
|
||||||
{announcementData.length > 0 ? (
|
{announcementData.length > 0 ? (
|
||||||
<Timeline
|
<Timeline mode="alternate">
|
||||||
mode="alternate"
|
{announcementData.map((item, idx) => (
|
||||||
dataSource={announcementData}
|
<Timeline.Item
|
||||||
/>
|
key={idx}
|
||||||
|
type={item.type || 'default'}
|
||||||
|
time={item.time}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
|
||||||
|
/>
|
||||||
|
{item.extra && (
|
||||||
|
<div
|
||||||
|
className="text-xs text-gray-500"
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Timeline.Item>
|
||||||
|
))}
|
||||||
|
</Timeline>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center items-center py-8">
|
<div className="flex justify-center items-center py-8">
|
||||||
<Empty
|
<Empty
|
||||||
@@ -1321,7 +1339,9 @@ const Detail = (props) => {
|
|||||||
header={item.question}
|
header={item.question}
|
||||||
itemKey={index.toString()}
|
itemKey={index.toString()}
|
||||||
>
|
>
|
||||||
<p>{item.answer}</p>
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parse(item.answer || '') }}
|
||||||
|
/>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
))}
|
))}
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Space,
|
Space,
|
||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
Modal,
|
Modal,
|
||||||
Tag,
|
Tag,
|
||||||
Switch
|
Switch,
|
||||||
|
TextArea,
|
||||||
|
Tooltip
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
IllustrationNoResult,
|
IllustrationNoResult,
|
||||||
@@ -20,7 +22,8 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
Trash2,
|
Trash2,
|
||||||
Save,
|
Save,
|
||||||
Bell
|
Bell,
|
||||||
|
Maximize2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { API, showError, showSuccess, getRelativeTime, formatDateTimeString } from '../../../helpers';
|
import { API, showError, showSuccess, getRelativeTime, formatDateTimeString } from '../../../helpers';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -33,6 +36,7 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
const [announcementsList, setAnnouncementsList] = useState([]);
|
const [announcementsList, setAnnouncementsList] = useState([]);
|
||||||
const [showAnnouncementModal, setShowAnnouncementModal] = useState(false);
|
const [showAnnouncementModal, setShowAnnouncementModal] = useState(false);
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
const [showContentModal, setShowContentModal] = useState(false);
|
||||||
const [deletingAnnouncement, setDeletingAnnouncement] = useState(null);
|
const [deletingAnnouncement, setDeletingAnnouncement] = useState(null);
|
||||||
const [editingAnnouncement, setEditingAnnouncement] = useState(null);
|
const [editingAnnouncement, setEditingAnnouncement] = useState(null);
|
||||||
const [modalLoading, setModalLoading] = useState(false);
|
const [modalLoading, setModalLoading] = useState(false);
|
||||||
@@ -51,6 +55,8 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
// 面板启用状态
|
// 面板启用状态
|
||||||
const [panelEnabled, setPanelEnabled] = useState(true);
|
const [panelEnabled, setPanelEnabled] = useState(true);
|
||||||
|
|
||||||
|
const formApiRef = useRef(null);
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ value: 'default', label: t('默认') },
|
{ value: 'default', label: t('默认') },
|
||||||
{ value: 'ongoing', label: t('进行中') },
|
{ value: 'ongoing', label: t('进行中') },
|
||||||
@@ -76,13 +82,16 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
dataIndex: 'content',
|
dataIndex: 'content',
|
||||||
key: 'content',
|
key: 'content',
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<div style={{
|
<Tooltip content={text} position='topLeft' showArrow>
|
||||||
maxWidth: '300px',
|
<div style={{
|
||||||
wordBreak: 'break-word',
|
maxWidth: '300px',
|
||||||
whiteSpace: 'pre-wrap'
|
overflow: 'hidden',
|
||||||
}}>
|
textOverflow: 'ellipsis',
|
||||||
{text}
|
whiteSpace: 'nowrap'
|
||||||
</div>
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,13 +130,17 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
dataIndex: 'extra',
|
dataIndex: 'extra',
|
||||||
key: 'extra',
|
key: 'extra',
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<div style={{
|
<Tooltip content={text || '-'} showArrow>
|
||||||
maxWidth: '200px',
|
<div style={{
|
||||||
wordBreak: 'break-word',
|
maxWidth: '200px',
|
||||||
color: 'var(--semi-color-text-2)'
|
overflow: 'hidden',
|
||||||
}}>
|
textOverflow: 'ellipsis',
|
||||||
{text || '-'}
|
whiteSpace: 'nowrap',
|
||||||
</div>
|
color: 'var(--semi-color-text-2)'
|
||||||
|
}}>
|
||||||
|
{text || '-'}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -472,16 +485,31 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
className="rounded-xl"
|
className="rounded-xl"
|
||||||
confirmLoading={modalLoading}
|
confirmLoading={modalLoading}
|
||||||
>
|
>
|
||||||
<Form layout='vertical' initValues={announcementForm} key={editingAnnouncement ? editingAnnouncement.id : 'new'}>
|
<Form
|
||||||
|
layout='vertical'
|
||||||
|
initValues={announcementForm}
|
||||||
|
key={editingAnnouncement ? editingAnnouncement.id : 'new'}
|
||||||
|
getFormApi={(api) => (formApiRef.current = api)}
|
||||||
|
>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
field='content'
|
field='content'
|
||||||
label={t('公告内容')}
|
label={t('公告内容')}
|
||||||
placeholder={t('请输入公告内容')}
|
placeholder={t('请输入公告内容(支持 Markdown/HTML)')}
|
||||||
maxCount={500}
|
maxCount={500}
|
||||||
rows={3}
|
rows={3}
|
||||||
rules={[{ required: true, message: t('请输入公告内容') }]}
|
rules={[{ required: true, message: t('请输入公告内容') }]}
|
||||||
onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
|
onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
theme='light'
|
||||||
|
type='tertiary'
|
||||||
|
size='small'
|
||||||
|
icon={<Maximize2 size={14} />}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
onClick={() => setShowContentModal(true)}
|
||||||
|
>
|
||||||
|
{t('放大编辑')}
|
||||||
|
</Button>
|
||||||
<Form.DatePicker
|
<Form.DatePicker
|
||||||
field='publishDate'
|
field='publishDate'
|
||||||
label={t('发布日期')}
|
label={t('发布日期')}
|
||||||
@@ -523,6 +551,33 @@ const SettingsAnnouncements = ({ options, refresh }) => {
|
|||||||
>
|
>
|
||||||
<Text>{t('确定要删除此公告吗?')}</Text>
|
<Text>{t('确定要删除此公告吗?')}</Text>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 公告内容放大编辑 Modal */}
|
||||||
|
<Modal
|
||||||
|
title={t('编辑公告内容')}
|
||||||
|
visible={showContentModal}
|
||||||
|
onOk={() => {
|
||||||
|
// 将内容同步到表单
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValue('content', announcementForm.content);
|
||||||
|
}
|
||||||
|
setShowContentModal(false);
|
||||||
|
}}
|
||||||
|
onCancel={() => setShowContentModal(false)}
|
||||||
|
okText={t('确定')}
|
||||||
|
cancelText={t('取消')}
|
||||||
|
className="rounded-xl"
|
||||||
|
width={800}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
value={announcementForm.content}
|
||||||
|
placeholder={t('请输入公告内容(支持 Markdown/HTML)')}
|
||||||
|
maxCount={500}
|
||||||
|
rows={15}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
Empty,
|
Empty,
|
||||||
Divider,
|
Divider,
|
||||||
Modal,
|
Modal,
|
||||||
Switch
|
Switch,
|
||||||
|
Tooltip
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
IllustrationNoResult,
|
IllustrationNoResult,
|
||||||
@@ -54,13 +55,17 @@ const SettingsFAQ = ({ options, refresh }) => {
|
|||||||
dataIndex: 'question',
|
dataIndex: 'question',
|
||||||
key: 'question',
|
key: 'question',
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<div style={{
|
<Tooltip content={text} showArrow>
|
||||||
maxWidth: '300px',
|
<div style={{
|
||||||
wordBreak: 'break-word',
|
maxWidth: '300px',
|
||||||
fontWeight: 'bold'
|
overflow: 'hidden',
|
||||||
}}>
|
textOverflow: 'ellipsis',
|
||||||
{text}
|
whiteSpace: 'nowrap',
|
||||||
</div>
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,14 +73,17 @@ const SettingsFAQ = ({ options, refresh }) => {
|
|||||||
dataIndex: 'answer',
|
dataIndex: 'answer',
|
||||||
key: 'answer',
|
key: 'answer',
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<div style={{
|
<Tooltip content={text} showArrow>
|
||||||
maxWidth: '400px',
|
<div style={{
|
||||||
wordBreak: 'break-word',
|
maxWidth: '400px',
|
||||||
whiteSpace: 'pre-wrap',
|
overflow: 'hidden',
|
||||||
color: 'var(--semi-color-text-1)'
|
textOverflow: 'ellipsis',
|
||||||
}}>
|
whiteSpace: 'nowrap',
|
||||||
{text}
|
color: 'var(--semi-color-text-1)'
|
||||||
</div>
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -416,7 +424,7 @@ const SettingsFAQ = ({ options, refresh }) => {
|
|||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
field='answer'
|
field='answer'
|
||||||
label={t('回答内容')}
|
label={t('回答内容')}
|
||||||
placeholder={t('请输入回答内容')}
|
placeholder={t('请输入回答内容(支持 Markdown/HTML)')}
|
||||||
maxCount={1000}
|
maxCount={1000}
|
||||||
rows={6}
|
rows={6}
|
||||||
rules={[{ required: true, message: t('请输入回答内容') }]}
|
rules={[{ required: true, message: t('请输入回答内容') }]}
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ const Setting = () => {
|
|||||||
content: <OperationSetting />,
|
content: <OperationSetting />,
|
||||||
itemKey: 'operation',
|
itemKey: 'operation',
|
||||||
});
|
});
|
||||||
|
panes.push({
|
||||||
|
tab: (
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||||
|
<LayoutDashboard size={18} />
|
||||||
|
{t('仪表盘设置')}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
content: <DashboardSetting />,
|
||||||
|
itemKey: 'dashboard',
|
||||||
|
});
|
||||||
panes.push({
|
panes.push({
|
||||||
tab: (
|
tab: (
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||||
@@ -115,16 +125,6 @@ const Setting = () => {
|
|||||||
content: <SystemSetting />,
|
content: <SystemSetting />,
|
||||||
itemKey: 'system',
|
itemKey: 'system',
|
||||||
});
|
});
|
||||||
panes.push({
|
|
||||||
tab: (
|
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
|
||||||
<LayoutDashboard size={18} />
|
|
||||||
{t('仪表盘设置')}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
content: <DashboardSetting />,
|
|
||||||
itemKey: 'dashboard',
|
|
||||||
});
|
|
||||||
panes.push({
|
panes.push({
|
||||||
tab: (
|
tab: (
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user